2021-07-14 12:37:45 -05:00
use std ::convert ::TryFrom ;
2021-08-25 14:30:27 -05:00
use std ::io ::{ BufRead , Write } ;
2021-07-14 12:37:45 -05:00
use clap ::ArgMatches ;
2021-08-25 14:30:27 -05:00
use chrono ::{ Utc , Duration , Local } ;
2021-07-14 12:37:45 -05:00
use itertools ::Itertools ;
2021-08-24 23:01:56 -05:00
use ansi_term ::Style ;
2021-07-14 12:37:45 -05:00
use crate ::error ::{ Error , Result } ;
use crate ::database ::Database ;
2021-07-15 00:22:38 -05:00
use crate ::tabulate ::{ Tabulate , Col , Align ::* } ;
use crate ::formatters ::text ::format_duration ;
2021-07-16 12:45:27 -05:00
use crate ::models ::Entry ;
use crate ::old ::{ entries_or_warning , warn_if_needed } ;
2021-08-25 14:30:27 -05:00
use crate ::io ::Streams ;
2021-07-14 12:37:45 -05:00
2021-08-25 14:30:27 -05:00
use super ::{ Command , Facts } ;
2021-07-14 12:37:45 -05:00
#[ derive(Default) ]
pub struct Args {
all : bool ,
}
impl < ' a > TryFrom < & ' a ArgMatches < ' a > > for Args {
type Error = Error ;
fn try_from ( matches : & ArgMatches ) -> Result < Args > {
Ok ( Args {
all : matches . is_present ( " all " ) ,
} )
}
}
pub struct ListCommand { }
impl < ' a > Command < ' a > for ListCommand {
type Args = Args ;
2021-08-25 14:30:27 -05:00
fn handle < D , I , O , E > ( args : Args , streams : & mut Streams < D , I , O , E > , facts : & Facts ) -> Result < ( ) >
2021-07-14 12:37:45 -05:00
where
D : Database ,
2021-08-25 14:30:27 -05:00
I : BufRead ,
2021-07-14 12:37:45 -05:00
O : Write ,
E : Write ,
{
2021-08-25 14:30:27 -05:00
let today = facts . now . with_timezone ( & Local ) . date ( ) . and_hms ( 0 , 0 , 0 ) . with_timezone ( & Utc ) ;
2021-07-16 12:45:27 -05:00
let entries = if args . all {
2021-08-25 14:30:27 -05:00
streams . db . entries_full ( None , None ) ?
2021-07-15 16:36:24 -05:00
} else {
2021-08-25 14:30:27 -05:00
streams . db . entries_all_visible ( None , None ) ?
2021-07-14 12:37:45 -05:00
} ;
2021-08-25 14:30:27 -05:00
let ( mut entries , needs_warning ) = entries_or_warning ( entries , & streams . db ) ? ;
2021-07-15 11:48:33 -05:00
2021-08-25 14:30:27 -05:00
let current = streams . db . current_sheet ( ) ? ;
let last = streams . db . last_sheet ( ) ? ;
2021-07-14 12:37:45 -05:00
2021-07-16 12:45:27 -05:00
// introducte two fake entries to make both current and last show up
2022-05-07 22:36:03 -05:00
entries . push ( Entry {
id : 1 , sheet : current . clone ( ) , start : facts . now , end : Some ( facts . now ) , note : None ,
} ) ;
2021-07-16 12:45:27 -05:00
entries . sort_unstable_by_key ( | e | e . sheet . clone ( ) ) ;
2021-08-24 23:01:56 -05:00
let mut total_running = Duration ::seconds ( 0 ) ;
let mut total_today = Duration ::seconds ( 0 ) ;
let mut total = Duration ::seconds ( 0 ) ;
2021-07-16 12:45:27 -05:00
let sheets : Vec < _ > = entries
2021-07-14 12:37:45 -05:00
. into_iter ( )
. group_by ( | e | e . sheet . clone ( ) )
. into_iter ( )
. map ( | ( key , group ) | {
2021-07-15 13:11:55 -05:00
let entries : Vec < _ > = group . into_iter ( ) . collect ( ) ;
2021-08-25 14:30:27 -05:00
let s_running = facts . now - entries . iter ( ) . find ( | e | e . end . is_none ( ) ) . map ( | e | e . start ) . unwrap_or ( facts . now ) ;
2021-08-24 23:01:56 -05:00
let s_today = entries . iter ( ) . filter ( | e | e . start > today ) . fold ( Duration ::seconds ( 0 ) , | acc , e | {
2021-08-25 14:30:27 -05:00
acc + ( e . end . unwrap_or ( facts . now ) - e . start )
2021-08-24 23:01:56 -05:00
} ) ;
let s_total = entries . into_iter ( ) . fold ( Duration ::seconds ( 0 ) , | acc , e | {
2021-08-25 14:30:27 -05:00
acc + ( e . end . unwrap_or ( facts . now ) - e . start )
2021-08-24 23:01:56 -05:00
} ) ;
total_running = total_running + s_running ;
total_today = total_today + s_today ;
total = total + s_total ;
2021-07-15 13:11:55 -05:00
2021-07-15 00:22:38 -05:00
(
2022-05-07 22:36:03 -05:00
if current = = key {
2022-07-21 08:35:45 -05:00
" * "
2021-08-02 19:10:34 -05:00
} else if last . as_ref ( ) = = Some ( & key ) {
2022-07-21 08:35:45 -05:00
" - "
2021-07-15 13:11:55 -05:00
} else {
2022-07-21 08:35:45 -05:00
" "
2021-07-15 13:11:55 -05:00
} ,
2021-08-24 23:01:56 -05:00
2021-07-15 00:22:38 -05:00
key ,
2021-07-15 13:11:55 -05:00
2021-08-24 23:01:56 -05:00
format_duration ( s_running ) ,
2021-07-15 13:11:55 -05:00
2021-08-24 23:01:56 -05:00
format_duration ( s_today ) ,
2021-07-15 11:48:33 -05:00
2021-08-24 23:01:56 -05:00
format_duration ( s_total ) ,
2021-07-15 00:22:38 -05:00
)
2021-07-14 12:37:45 -05:00
} )
. collect ( ) ;
2021-07-15 00:22:38 -05:00
let mut tabs = Tabulate ::with_columns ( vec! [
2021-08-02 18:45:54 -05:00
// indicator of current or prev sheet
2022-07-21 08:35:45 -05:00
Col ::new ( ) . min_width ( 1 ) . and_alignment ( Right ) ,
2021-07-15 11:48:33 -05:00
// sheet name
2022-07-21 08:35:45 -05:00
Col ::new ( ) . min_width ( 9 ) . and_alignment ( Left ) ,
2021-07-15 11:48:33 -05:00
// running time
2022-07-21 08:35:45 -05:00
Col ::new ( ) . min_width ( 9 ) . and_alignment ( Right )
2021-08-25 16:50:07 -05:00
. color_if ( Style ::new ( ) . dimmed ( ) , | s | s = = " 0:00:00 " )
. color_if ( Style ::new ( ) . bold ( ) , | s | s ! = " 0:00:00 " ) ,
2021-07-15 11:48:33 -05:00
// today
2022-07-21 08:35:45 -05:00
Col ::new ( ) . min_width ( 9 ) . and_alignment ( Right )
2021-08-25 16:50:07 -05:00
. color_if ( Style ::new ( ) . dimmed ( ) , | s | s = = " 0:00:00 " )
. color_if ( Style ::new ( ) . bold ( ) , | s | s ! = " 0:00:00 " ) ,
2021-07-15 11:48:33 -05:00
// accumulated
2022-07-21 08:35:45 -05:00
Col ::new ( ) . min_width ( 12 ) . and_alignment ( Right ) ,
2021-07-15 00:22:38 -05:00
] ) ;
2022-07-21 08:35:45 -05:00
tabs . feed ( vec! [ " " , " Timesheet " , " Running " , " Today " , " Total Time " ] ) ;
2021-07-15 16:36:24 -05:00
tabs . separator ( ' ' ) ;
2021-07-15 11:48:33 -05:00
2021-07-14 12:37:45 -05:00
for sheet in sheets {
2022-07-21 08:35:45 -05:00
tabs . feed ( vec! [ sheet . 0. to_string ( ) , sheet . 1 , sheet . 2 , sheet . 3 , sheet . 4 ] ) ;
2021-07-14 12:37:45 -05:00
}
2021-08-24 23:01:56 -05:00
tabs . separator ( '-' ) ;
tabs . feed ( vec! [
2022-07-21 08:35:45 -05:00
" " . to_string ( ) ,
" " . to_string ( ) ,
2021-08-24 23:01:56 -05:00
format_duration ( total_running ) ,
format_duration ( total_today ) ,
format_duration ( total ) ,
] ) ;
2021-08-25 14:43:50 -05:00
streams . out . write_all ( tabs . print ( facts . env . stdout_is_tty ) . as_bytes ( ) ) ? ;
2021-07-15 00:22:38 -05:00
2021-08-25 14:30:27 -05:00
warn_if_needed ( & mut streams . err , needs_warning , & facts . env ) ? ;
2021-07-16 12:45:27 -05:00
2021-07-14 12:37:45 -05:00
Ok ( ( ) )
}
}
#[ cfg(test) ]
mod tests {
use chrono ::{ Utc , TimeZone } ;
use pretty_assertions ::assert_eq ;
use crate ::database ::{ SqliteDatabase , Database } ;
use super ::* ;
#[ test ]
fn list_sheets ( ) {
2021-08-11 17:54:37 -05:00
std ::env ::set_var ( " TZ " , " CST+6 " ) ;
2021-07-16 12:45:27 -05:00
2021-07-14 12:37:45 -05:00
let args = Default ::default ( ) ;
2021-08-25 14:30:27 -05:00
let mut streams = Streams ::fake ( b " " ) ;
streams . db . set_current_sheet ( " sheet2 " ) . unwrap ( ) ;
streams . db . set_last_sheet ( " sheet4 " ) . unwrap ( ) ;
2021-12-13 14:20:56 -06:00
streams . db . entry_insert ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 0 , 0 , 0 ) , Some ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 1 , 0 , 0 ) ) , None , " _archived " ) . unwrap ( ) ;
streams . db . entry_insert ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 0 , 0 , 0 ) , Some ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 10 , 13 , 55 ) ) , None , " sheet1 " ) . unwrap ( ) ;
streams . db . entry_insert ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 0 , 0 , 0 ) , Some ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 7 , 39 , 18 ) ) , None , " sheet3 " ) . unwrap ( ) ;
streams . db . entry_insert ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 12 , 0 , 0 ) , Some ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 13 , 52 , 45 ) ) , None , " sheet3 " ) . unwrap ( ) ;
streams . db . entry_insert ( Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 12 , 0 , 0 ) , None , None , " sheet4 " ) . unwrap ( ) ;
2021-07-14 12:37:45 -05:00
2021-07-16 12:45:27 -05:00
let now = Utc . ymd ( 2021 , 1 , 1 ) . and_hms ( 13 , 52 , 45 ) ;
2021-08-25 14:30:27 -05:00
let facts = Facts ::new ( ) . with_now ( now ) ;
2021-07-14 12:37:45 -05:00
2021-08-25 14:30:27 -05:00
ListCommand ::handle ( args , & mut streams , & facts ) . unwrap ( ) ;
2021-07-14 12:37:45 -05:00
2022-07-19 05:38:37 -05:00
assert_eq! ( & String ::from_utf8_lossy ( & streams . out ) , " Timesheet Running Today Total Time
2021-07-16 12:45:27 -05:00
2021-08-25 14:43:50 -05:00
sheet1 0 :00 :00 0 :00 :00 10 :13 :55
* sheet2 0 :00 :00 0 :00 :00 0 :00 :00
sheet3 0 :00 :00 1 :52 :45 9 :32 :03
2021-07-16 12:45:27 -05:00
- sheet4 1 :52 :45 1 :52 :45 1 :52 :45
2021-08-24 23:01:56 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 :52 :45 3 :45 :30 21 :38 :43
2022-07-19 05:38:37 -05:00
" );
2021-07-14 12:37:45 -05:00
2021-07-16 12:45:27 -05:00
// now show all the sheets
2021-08-25 14:30:27 -05:00
streams . reset_io ( ) ;
2021-07-16 12:45:27 -05:00
let args = Args {
all : true ,
} ;
2021-07-15 16:36:24 -05:00
2021-08-25 14:30:27 -05:00
ListCommand ::handle ( args , & mut streams , & facts ) . unwrap ( ) ;
2021-07-15 16:36:24 -05:00
2022-07-19 05:38:37 -05:00
assert_eq! ( & String ::from_utf8_lossy ( & streams . out ) , " Timesheet Running Today Total Time
2021-07-16 12:45:27 -05:00
2021-08-25 14:43:50 -05:00
_archived 0 :00 :00 0 :00 :00 1 :00 :00
sheet1 0 :00 :00 0 :00 :00 10 :13 :55
* sheet2 0 :00 :00 0 :00 :00 0 :00 :00
sheet3 0 :00 :00 1 :52 :45 9 :32 :03
2021-07-16 12:45:27 -05:00
- sheet4 1 :52 :45 1 :52 :45 1 :52 :45
2021-08-24 23:01:56 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 :52 :45 3 :45 :30 22 :38 :43
2022-07-19 05:38:37 -05:00
" );
2021-07-14 12:37:45 -05:00
}
2021-07-15 11:48:33 -05:00
#[ test ]
2021-07-16 12:45:27 -05:00
fn old_database ( ) {
let args = Default ::default ( ) ;
2021-08-25 14:30:27 -05:00
let mut streams = Streams ::fake ( b " " ) . with_db (
SqliteDatabase ::from_path ( " assets/test_list_old_database.db " ) . unwrap ( )
) ;
2021-07-16 12:45:27 -05:00
let now = Local . ymd ( 2021 , 7 , 16 ) . and_hms ( 11 , 30 , 45 ) ;
2021-08-25 14:30:27 -05:00
let facts = Facts ::new ( ) . with_now ( now . with_timezone ( & Utc ) ) ;
2021-07-16 12:45:27 -05:00
2021-08-25 14:30:27 -05:00
ListCommand ::handle ( args , & mut streams , & facts ) . unwrap ( ) ;
2021-07-16 12:45:27 -05:00
2022-07-19 05:38:37 -05:00
assert_eq! ( & String ::from_utf8_lossy ( & streams . out ) , " Timesheet Running Today Total Time
2021-07-16 12:45:27 -05:00
* default 0 :10 :24 0 :10 :26 0 :10 :26
2021-08-24 23:01:56 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0 :10 :24 0 :10 :26 0 :10 :26
2022-07-19 05:38:37 -05:00
" );
2021-07-16 12:45:27 -05:00
assert_eq! (
2021-08-25 14:30:27 -05:00
String ::from_utf8_lossy ( & streams . err ) ,
2022-08-29 18:03:11 -05:00
" [WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate. To supress this warning set TIEMPO_SUPRESS_TIMETRAP_WARNING=1 \n "
2021-07-16 12:45:27 -05:00
) ;
2021-07-15 11:48:33 -05:00
}
2021-07-14 12:37:45 -05:00
}