diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index f0ad44c7..9a15b1f3 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -9,7 +9,7 @@ use serde_derive::{Deserialize, Serialize}; use directories::ProjectDirs; use crate::{ - domain::priority::ItemPriorityKind, + domain::{priority::ItemPriorityKind, review::ReviewFormatKind}, error::{PaceErrorKind, PaceResult}, }; @@ -149,23 +149,6 @@ impl Default for GeneralConfig { } } -/// The kind of review format -/// Default: `html` -/// -/// Options: `html`, `markdown`, `plain-text` -#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)] -#[serde(rename_all = "kebab-case")] -#[non_exhaustive] -pub enum ReviewFormatKind { - #[default] - Html, - Csv, - #[serde(rename = "md")] - Markdown, - #[serde(rename = "txt")] - PlainText, -} - /// The review configuration for the pace application #[derive(Debug, Deserialize, Default, Serialize, Getters, Clone)] #[getset(get = "pub")] diff --git a/crates/core/src/domain/activity.rs b/crates/core/src/domain/activity.rs index e47bfee9..8e871f14 100644 --- a/crates/core/src/domain/activity.rs +++ b/crates/core/src/domain/activity.rs @@ -6,6 +6,7 @@ use getset::{Getters, MutGetters, Setters}; use merge::Merge; use serde_derive::{Deserialize, Serialize}; use std::fmt::Display; +use strum_macros::EnumString; use typed_builder::TypedBuilder; use ulid::Ulid; @@ -63,7 +64,18 @@ impl From<(ActivityGuid, Activity)> for ActivityItem { /// The kind of activity a user can track #[derive( - Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, + Debug, + Clone, + Serialize, + Deserialize, + Default, + PartialEq, + Eq, + Hash, + Copy, + PartialOrd, + Ord, + EnumString, )] #[serde(rename_all = "kebab-case")] // #[serde(untagged)] diff --git a/crates/core/src/domain/review.rs b/crates/core/src/domain/review.rs index 936183f5..f3d33722 100644 --- a/crates/core/src/domain/review.rs +++ b/crates/core/src/domain/review.rs @@ -1 +1,22 @@ +use serde_derive::{Deserialize, Serialize}; +use strum_macros::EnumString; + pub struct ActivityStats {} + +/// The kind of review format +/// Default: `console` +/// +/// Options: `console`, `html`, `markdown`, `plain-text` +#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, EnumString)] +#[serde(rename_all = "kebab-case")] +#[non_exhaustive] +pub enum ReviewFormatKind { + #[default] + Console, + Html, + Csv, + #[serde(rename = "md")] + Markdown, + #[serde(rename = "txt")] + PlainText, +} diff --git a/crates/core/src/domain/time.rs b/crates/core/src/domain/time.rs index c1e9672d..ec910791 100644 --- a/crates/core/src/domain/time.rs +++ b/crates/core/src/domain/time.rs @@ -164,10 +164,26 @@ impl From for PaceDuration { #[derive(Debug, Serialize, Deserialize, Hash, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] pub struct PaceDate(pub NaiveDate); +impl std::ops::Deref for PaceDate { + type Target = NaiveDate; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// Wrapper for a time of an activity #[derive(Debug, Serialize, Deserialize, Hash, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] pub struct PaceTime(pub NaiveTime); +impl std::ops::Deref for PaceTime { + type Target = NaiveTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// Wrapper for the start and end time of an activity to implement default #[derive(Debug, Serialize, Deserialize, Hash, Clone, Copy, Eq, PartialEq)] pub struct PaceDateTime(NaiveDateTime); diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 6377d4dc..e7505f7b 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -35,6 +35,7 @@ pub use crate::{ activity_log::ActivityLog, filter::{ActivityStatusFilter, FilteredActivities}, intermission::IntermissionAction, + review::ReviewFormatKind, status::ActivityStatus, time::{ calculate_duration, duration_to_str, extract_time_or_now, parse_time_from_user_input, diff --git a/src/commands.rs b/src/commands.rs index 8eaa3b13..64f9e48e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -52,7 +52,7 @@ pub enum PaceCmd { /// 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. + /// Get sophisticated insights on your activities. Review(review::ReviewCmd), // /// Exports your tracked data and reviews in JSON or CSV format, suitable for analysis or record-keeping. // Export(export::ExportCmd), diff --git a/src/commands/review.rs b/src/commands/review.rs index cdbc0792..d9198aec 100644 --- a/src/commands/review.rs +++ b/src/commands/review.rs @@ -1,16 +1,108 @@ //! `review` subcommand -use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; +use std::path::PathBuf; +use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; +use chrono::NaiveDate; use clap::Parser; use eyre::Result; -use pace_core::{get_storage_from_config, ActivityStore, ActivityTracker}; +use pace_core::{ + get_storage_from_config, ActivityKind, ActivityStore, ActivityTracker, ReviewFormatKind, +}; use crate::prelude::PACE_APP; + +#[derive(Debug, Parser)] +#[clap(group = clap::ArgGroup::new("time-flag").multiple(false))] +struct TimeFlags { + /// Show the review for the current day + #[clap(long, group = "time-flag")] + today: bool, + + /// Show the review for the previous day + #[clap(long, group = "time-flag")] + yesterday: bool, + + /// Show the review for the current week + #[clap(long, group = "time-flag")] + current_week: bool, + + /// Show the review for the previous week + #[clap(long, group = "time-flag")] + last_week: bool, + + /// Show the review for the current month + #[clap(long, group = "time-flag")] + current_month: bool, +} + +#[derive(Debug, Parser)] +#[clap(group = clap::ArgGroup::new("date-flag").multiple(true))] +struct DateFlags { + /// Show the review for a specific date, mutually exclusive with `from` and `to` + #[clap(long, group = "date-flag", exclusive = true)] + date: Option, + + /// Start date for the review period in YYYY-MM-DD format + #[clap(long, group = "date-flag")] + from: Option, + + /// End date for the review period in YYYY-MM-DD format + #[clap(long, group = "date-flag")] + to: Option, +} + +#[derive(Debug, Parser)] +struct ExpensiveFlags { + /// Include detailed time logs in the review + #[clap(long)] + detailed: bool, + + /// Enable comparative insights against a previous period + #[clap(long)] + comparative: bool, + + /// Enable personalized recommendations based on review data + #[clap(long)] + recommendations: bool, +} + /// `review` subcommand #[derive(Command, Debug, Parser)] -pub struct ReviewCmd {} +pub struct ReviewCmd { + /// Filter by activity kind (e.g., activity, task) + #[clap(long)] + activity_kind: Option, + + /// Filter by category name, wildcard supported + #[clap(long)] + category: Option, + + /// Specify output format (e.g., text, markdown, pdf) + #[clap(long)] + output_format: Option, + + /// Export the review report to a specified file + #[clap(long)] + export_file: Option, + + /// Time flags + #[clap(flatten, next_help_heading = "Flags for specifying time periods")] + time_flags: TimeFlags, + + /// Date flags + #[clap( + flatten, + next_help_heading = "Date flags for specifying custom date ranges or specific dates" + )] + date_flags: DateFlags, + + /// Expensive flags + /// These flags are expensive to compute and may take longer to generate + #[clap(flatten, next_help_heading = "Expensive flags for detailed insights")] + expensive_flags: ExpensiveFlags, +} impl Runnable for ReviewCmd { /// Start the application. @@ -28,6 +120,8 @@ impl ReviewCmd { let _activity_tracker = ActivityTracker::with_activity_store(activity_store); + println!("{:#?}", self); + Ok(()) } }