11use boat_lib:: repository:: Id ;
2+ use chrono:: Datelike ;
3+ use chrono:: Local ;
4+ use chrono:: Months ;
25use chrono:: NaiveDate ;
36use clap:: ColorChoice ;
47use clap:: Parser ;
58use clap:: { ArgAction , Args , Subcommand , ValueEnum } ;
9+ use std:: str:: FromStr ;
610
711use crate :: utils;
12+ use crate :: utils:: date:: DateTimeRenderMode ;
813
914#[ derive( Parser ) ]
1015#[ command(
@@ -70,12 +75,12 @@ pub enum Commands {
7075 #[ command( alias = "l" , alias = "ls" ) ]
7176 List ( ListActivityArgs ) ,
7277
73- /// Query boat objects
74- #[ command( alias = "q" ) ]
75- Query {
76- #[ command( subcommand) ]
77- command : QuerySubcommand ,
78- } ,
78+ // // / Query boat objects
79+ // #[command(alias = "q")]
80+ // Query {
81+ // #[command(subcommand)]
82+ // command: QuerySubcommand,
83+ // },
7984
8085 // This is ONLY way I could find to use the 'h' short alias for help.
8186 #[ command( alias = "h" , hide = true ) ]
@@ -120,35 +125,111 @@ pub enum QuerySubcommand {
120125 Tags ( ListArgs ) ,
121126}
122127
128+ #[ derive( Debug , Clone , Copy ) ]
129+ pub enum DateInput {
130+ Single ( NaiveDate ) ,
131+ Range {
132+ start : NaiveDate ,
133+ end : NaiveDate ,
134+ inclusive : bool ,
135+ } ,
136+ }
137+
138+ impl DateInput {
139+ const ERR_MSG : & ' static str =
140+ "Provide either a range (YYYY-MM-DD..YYYY-MM-DD) or a single date (YYYY-MM-DD)" ;
141+ }
142+
143+ impl std:: fmt:: Display for DateInput {
144+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
145+ match self {
146+ DateInput :: Single ( naive_date) => {
147+ let dt = DateTimeRenderMode :: DateOnly . render_naive_date ( naive_date) ;
148+ write ! ( f, "{dt}" )
149+ }
150+ DateInput :: Range {
151+ start,
152+ end,
153+ inclusive,
154+ } => {
155+ let start = DateTimeRenderMode :: DateOnly . render_naive_date ( start) ;
156+ let end = DateTimeRenderMode :: DateOnly . render_naive_date ( end) ;
157+ let inclusion_msg = ( if * inclusive { "included" } else { "excluded" } ) . to_string ( ) ;
158+ write ! ( f, "{start} to {end} ({inclusion_msg})" )
159+ }
160+ }
161+ }
162+ }
163+
164+ impl FromStr for DateInput {
165+ type Err = String ;
166+
167+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
168+ // Match range
169+ if let Some ( ( start, end) ) = s. split_once ( ".." ) {
170+ let start = utils:: date:: parse_date ( start) . map_err ( |_| Self :: ERR_MSG ) ?;
171+ let ( end, inclusive) = match end. strip_prefix ( '=' ) {
172+ Some ( substr) => ( substr, true ) ,
173+ None => ( end, false ) ,
174+ } ;
175+ let end = utils:: date:: parse_date ( end) . map_err ( |_| Self :: ERR_MSG ) ?;
176+
177+ if start > end {
178+ return Err ( "DateInput: start cannot be after end when using range" . to_string ( ) ) ;
179+ }
180+
181+ return Ok ( DateInput :: Range {
182+ start,
183+ end,
184+ inclusive,
185+ } ) ;
186+ }
187+
188+ // Single date
189+ let date = utils:: date:: parse_date ( s) . map_err ( |_| Self :: ERR_MSG ) ?;
190+ Ok ( DateInput :: Single ( date) )
191+ }
192+ }
193+
123194#[ derive( Args , Debug ) ]
124195pub struct ListActivityArgs {
125196 /// Restrict to entries starting in the given <PERIOD>
126- #[ arg( short = 'p' , long = "period" , value_name = "PERIOD" , default_value_t = Period :: ThisWeek , value_enum, conflicts_with_all = [ "from" , "to" , "date" ] ) ]
127- pub period : Period ,
128-
129- /// Restrict to entries starting after <DATE> (YYYY-MM-DD format)
130- #[ arg( short = 'f' , long = "from" , value_name = "DATE" , value_parser = utils:: date:: parse_date, conflicts_with = "date" ) ]
131- pub from : Option < NaiveDate > ,
132-
133- /// Restrict to entries starting before <DATE> (YYYY-MM-DD format)
134- #[ arg( short = 't' , long = "to" , value_name = "DATE" , value_parser = utils:: date:: parse_date, conflicts_with = "date" ) ]
135- pub to : Option < NaiveDate > ,
197+ #[ arg(
198+ short = 'p' ,
199+ long = "period" ,
200+ value_name = "PERIOD" ,
201+ value_enum,
202+ conflicts_with = "date_range"
203+ ) ]
204+ pub period : Option < Period > ,
205+
206+ /// Restrict to entries matching <DATE_RANGE> (YYYY-MM-DD format)
207+ #[ arg(
208+ short = 'd' ,
209+ long = "date" ,
210+ value_name = "DATE_RANGE" ,
211+ conflicts_with = "period"
212+ ) ]
213+ pub date_range : Option < DateInput > ,
136214
137- /// Restrict to entries starting and ending on <DATE> (YYYY-MM-DD format)
138- #[ arg( short = 'd ' , long = "date " , value_name = "DATE" , value_parser = utils :: date :: parse_date , conflicts_with_all = [ "period" , "from" , "to" ] ) ]
139- pub date : Option < NaiveDate > ,
215+ /// Show a per-activity summary instead of listing all logs
216+ #[ arg( short = 's ' , long = "summary " , conflicts_with = "no_grouping" ) ]
217+ pub show_summary : bool ,
140218
141- /// Only show activities, do not include their respective logs
142- #[ arg( short = 'a' , long = "activities-only " , conflicts_with = "no_grouping" ) ]
143- pub activities_only : bool ,
219+ /// Show all activities, even the ones with no log
220+ #[ arg( short = 'a' , long = "all " , conflicts_with = "no_grouping" ) ]
221+ pub show_all : bool ,
144222
145- /// Do not group activities by date
146- #[ arg( short = 'n' , long = "no-grouping" , conflicts_with = "activities_only" ) ]
223+ /// Do not group activity logs by date
224+ #[ arg( short = 'n' , long = "no-grouping" , conflicts_with_all = [ "show_summary" , "show_all" ] ) ]
147225 pub no_grouping : bool ,
148226
149227 /// Output in JSON
150228 #[ arg( short = 'j' , long = "json" ) ]
151229 pub use_json_format : bool ,
230+ // /// Only show tags
231+ // #[arg(short = 't', long = "tags-only", conflicts_with = "no_grouping")]
232+ // pub tags_only: bool,
152233}
153234
154235#[ derive( Args , Debug ) ]
@@ -158,14 +239,18 @@ pub struct ListArgs {
158239 pub use_json_format : bool ,
159240}
160241
161- #[ derive( ValueEnum , Clone , Debug ) ]
242+ #[ derive( ValueEnum , Clone , Copy , Debug , Default ) ]
162243pub enum Period {
163244 #[ value( name = "today" , alias = "td" , alias = "tod" ) ]
164245 Today ,
246+
165247 #[ value( name = "yesterday" , alias = "yd" , alias = "ytd" ) ]
166248 Yesterday ,
249+
167250 #[ value( name = "this-week" , alias = "tw" , alias = "twk" , alias = "wk" ) ]
251+ #[ default]
168252 ThisWeek ,
253+
169254 #[ value(
170255 name = "last-week" ,
171256 alias = "lw" ,
@@ -175,8 +260,10 @@ pub enum Period {
175260 alias = "ywk"
176261 ) ]
177262 LastWeek ,
263+
178264 #[ value( name = "this-month" , alias = "tm" , alias = "tmo" , alias = "mo" ) ]
179265 ThisMonth ,
266+
180267 #[ value(
181268 name = "last-month" ,
182269 alias = "lm" ,
@@ -188,35 +275,35 @@ pub enum Period {
188275 LastMonth ,
189276}
190277
191- #[ derive( Args , Debug ) ]
278+ impl std:: fmt:: Display for Period {
279+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
280+ let now = Local :: now ( ) ;
281+ let last_month = now - Months :: new ( 1 ) ;
282+
283+ let period = match self {
284+ Period :: Today => "Today" . to_string ( ) ,
285+ Period :: Yesterday => "Yesterday" . to_string ( ) ,
286+ Period :: ThisWeek => "This week" . to_string ( ) ,
287+ Period :: LastWeek => "Last week" . to_string ( ) ,
288+ Period :: ThisMonth => format ! ( "{} {}" , now. format( "%B" ) , now. year( ) ) ,
289+ Period :: LastMonth => format ! ( "{} {}" , last_month. format( "%B" ) , last_month. year( ) ) ,
290+ } ;
291+ write ! ( f, "{period}" )
292+ }
293+ }
294+
295+ #[ derive( Args , Debug , Default ) ]
192296#[ group( multiple = false ) ]
193297pub struct PrintActivityArgs {
194- /// Output in pretty format
195- #[ arg( short = 'p' , long = "pretty" ) ]
196- pub use_pretty_format : bool ,
197-
198298 /// Output in JSON
199299 #[ arg( short = 'j' , long = "json" ) ]
200300 pub use_json_format : bool ,
201301}
202302
203- impl Default for PrintActivityArgs {
204- fn default ( ) -> Self {
205- Self {
206- use_pretty_format : true ,
207- use_json_format : false ,
208- }
209- }
210- }
211-
212303#[ derive( Args , Debug ) ]
213304pub struct SelectActivityArgs {
214305 /// ID of the activity
215306 pub activity_id : Id ,
216-
217- /// Output in JSON
218- #[ arg( short = 'j' , long = "json" ) ]
219- pub use_json_format : bool ,
220307}
221308
222309#[ derive( Args , Debug ) ]
0 commit comments