1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! Pace Subcommands
//!
//! This is where you specify the subcommands of your application.
//!
//! The default application comes with two subcommands:
//!
//! - `start`: launches the application
//! - `--version`: print application version
//!
//! See the `impl Configurable` below for how to specify the path to the
//! application's configuration file.

pub mod begin;
pub mod end;
// pub mod export;
// TODO: pub mod import;
pub mod craft;
pub mod hold;
pub mod now;
// pub mod pomo;
pub mod resume;
pub mod review;
// pub mod set;
// pub mod tasks;

use abscissa_core::{config::Override, Command, Configurable, FrameworkError, Runnable};
use clap::builder::{styling::AnsiColor, Styles};
use human_panic::setup_panic;
use std::path::PathBuf;

use pace_core::{get_config_paths, PaceConfig};

/// Pace Subcommands
/// Subcommands need to be listed in an enum.
#[derive(clap::Parser, Command, Debug, Runnable)]
pub enum PaceCmd {
    /// Crafts a pace configuration, a new project or shell completions
    Craft(craft::CraftCmd),

    /// Starts tracking time for the specified activity.
    Begin(begin::BeginCmd),

    /// Pauses the time tracking for the most recently active activity.
    Hold(hold::HoldCmd),

    /// Shows you at a glance what you're currently tracking.
    Now(now::NowCmd),

    /// Resumes a previously paused activity, allowing you to continue where you left off.
    Resume(resume::ResumeCmd),

    /// Stops time tracking for the most recent or all activities.
    End(end::EndCmd),

    /// Get insights on your activities. You can specify various time frames or custom date ranges.
    Review(review::ReviewCmd),
    // /// Exports your tracked data and reviews in JSON or CSV format, suitable for analysis or record-keeping.
    // Export(export::ExportCmd),

    // /// Starts a Pomodoro session for the specified task, integrating the Pomodoro technique directly with your tasks.
    // Pomo(pomo::PomoCmd),
    // /// Sets various application configurations, including Pomodoro lengths and preferred review formats.
    // Set(set::SetCmd),

    // /// Lists all tasks with optional filters. Use this to view active, completed, or today's tasks.
    // Tasks(tasks::TasksCmd),
}

/// Define CLI colour styles for the application
const fn cli_colour_styles() -> Styles {
    Styles::styled()
        .header(AnsiColor::BrightBlue.on_default())
        .usage(AnsiColor::BrightYellow.on_default())
        .literal(AnsiColor::BrightGreen.on_default())
        .placeholder(AnsiColor::Magenta.on_default())
}

/// Entry point for the application. It needs to be a struct to allow using subcommands!
#[derive(clap::Parser, Command, Debug)]
#[command(name="pace", author, about, styles=cli_colour_styles(), version)]
pub struct EntryPoint {
    #[command(subcommand)]
    cmd: PaceCmd,

    /// Enable verbose logging
    #[arg(short, long)]
    pub verbose: bool,

    /// Use the specified config file
    #[arg(short, long, env = "PACE_CONFIG_FILE")]
    pub config: Option<PathBuf>,

    /// Use the specified activity log file
    #[arg(short, long, env = "PACE_ACTIVITY_LOG_FILE")]
    pub activity_log_file: Option<PathBuf>,

    /// Pace Home Directory
    #[arg(long, env = "PACE_HOME")]
    pub home: Option<PathBuf>,
}

impl Runnable for EntryPoint {
    fn run(&self) {
        setup_panic!();
        self.cmd.run();
    }
}

impl Override<PaceConfig> for EntryPoint {
    fn override_config(&self, mut config: PaceConfig) -> Result<PaceConfig, FrameworkError> {
        // Override the activity log file if it's set
        if let Some(activity_log_file) = &self.activity_log_file {
            if activity_log_file.exists() {
                *config
                    .general_mut()
                    .activity_log_options_mut()
                    .activity_log_path_mut() = activity_log_file.to_path_buf();
            }
        };

        Ok(config)
    }
}

/// This trait allows you to define how application configuration is loaded.
impl Configurable<PaceConfig> for EntryPoint {
    /// Location of the configuration file
    fn config_path(&self) -> Option<PathBuf> {
        let config_paths = get_config_paths("pace.toml")
            .into_iter()
            .filter(|f| f.exists())
            .collect::<Vec<_>>();

        // Get the first path that exists
        // FIXME: This feels hacky, is this sensible?
        let path = config_paths.first();

        let filename = self
            .config
            .as_ref()
            .and_then(|f| if f.exists() { Some(f) } else { None });

        // If the user has specified a config file, use that
        // otherwise, use the first config file found in specified
        // standard locations
        match (filename, path) {
            (Some(filename), _) => Some(filename.clone()),
            (None, Some(first_path)) => Some(first_path.clone()),
            _ => None,
        }
    }

    /// Apply changes to the config after it's been loaded, e.g. overriding
    /// values in a config file using command-line options.
    ///
    /// This can be safely deleted if you don't want to override config
    /// settings from command-line options.
    fn process_config(&self, config: PaceConfig) -> Result<PaceConfig, FrameworkError> {
        // Override the config file with options from CLI arguments globally
        let config = self.override_config(config)?;

        // You can also override settings based on the subcommand
        // match &self.cmd {
        // PaceCmd::Start(cmd) => cmd.override_config(config),
        //
        // If you don't need special overrides for some
        // subcommands, you can just use a catch all
        // _ => Ok(config),
        // }

        Ok(config)
    }
}