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
//! `begin` subcommand

use abscissa_core::{status_err, Application, Command, Runnable, Shutdown};
use clap::Parser;
use eyre::Result;

use crate::prelude::PACE_APP;

use pace_core::{
    extract_time_or_now, get_storage_from_config, Activity, ActivityKind, ActivityStateManagement,
    ActivityStorage, ActivityStore, PaceConfig, SyncStorage,
};

/// `begin` subcommand
#[derive(Command, Debug, Parser)]
pub struct BeginCmd {
    /// The Category of the activity you want to start
    ///
    /// You can use the separator you setup in the configuration file
    /// to specify a subcategory.
    #[clap(short, long)]
    category: Option<String>,

    /// The time the activity has been started at
    #[clap(long)]
    time: Option<String>,

    /// The description of the activity you want to start
    description: String,

    /// The tags you want to associate with the activity
    #[clap(short, long)]
    tags: Option<Vec<String>>,

    /// TODO: The project you want to start tracking time for
    /// FIXME: involves parsing the project configuration first
    #[clap(skip)]
    _projects: Option<Vec<String>>,
}

impl Runnable for BeginCmd {
    /// Start the application.
    fn run(&self) {
        if let Err(err) = self.inner_run(&PACE_APP.config()) {
            status_err!("{}", err);
            PACE_APP.shutdown(Shutdown::Crash);
        };
    }
}

impl BeginCmd {
    /// Inner run implementation for the begin command
    pub fn inner_run(&self, config: &PaceConfig) -> Result<()> {
        let Self {
            category,
            time,
            description,
            ..
        } = self;

        // parse time from string or get now
        let date_time = extract_time_or_now(time)?;

        // TODO: Parse categories and subcategories from string
        // let (category, subcategory) = if let Some(ref category) = category {
        //     let separator = config.general().category_separator();
        //     extract_categories(category.as_str(), separator.as_str())
        // } else {
        //     // if no category is given, use the default category
        //     // FIXME: This should be the default category from the project configuration
        //     // but for now, we'll just use category defaults
        //     //
        //     // FIXME: We might also want to merge the project configuration with the general configuration first to have precedence
        //     //
        //     // let category = if let Some(category) = PACE_APP.config().general().default_category() {
        //     //     category
        //     // } else {
        //     // &Category::default()
        //     // };

        //     (Category::default(), None)
        // };

        let activity = Activity::builder()
            .description(description.clone())
            .begin(date_time.into())
            .kind(ActivityKind::default())
            .category(category.clone())
            .build();

        let activity_store = ActivityStore::new(get_storage_from_config(config)?);

        activity_store.setup_storage()?;
        let activity_id = activity_store.begin_activity(activity.clone())?;

        if let Some(og_activity_id) = activity.guid() {
            if activity_id == *og_activity_id {
                activity_store.sync()?;
                println!("{activity}");
                return Ok(());
            }
        }

        eyre::bail!("Failed to start {activity}");
    }
}