@ -2,7 +2,7 @@ use super::listview::ListView;
use itertools ::Itertools ;
use serde ::{ Deserialize , Serialize } ;
use serde_json ::from_reader ;
use std ::{ collections , default , fmt , fs , io , iter };
use std ::{ collections , default , fmt , fs , io };
use time ::{ Duration , OffsetDateTime , Time } ;
pub struct TimeSheet {
@ -13,12 +13,13 @@ pub struct TimeSheet {
const MAIN_PAUSE_TEXT : & str = "pause" ;
const PAUSE_TEXTS : [ & str ; 4 ] = [ MAIN_PAUSE_TEXT , "lunch" , "mittag" , "break" ] ;
const END_TEXT : & str = "end" ;
lazy_static ! {
static ref OVERRIDE_REGEX : regex ::Regex = regex ::Regex ::new ( "\\[(.*)\\]" ) . unwrap ( ) ;
}
#[ derive(Serialize, Deserialize, Clone, Debug )]
#[ derive(Serialize, Deserialize, Clone, Debug , PartialEq, Eq )]
pub struct TimePoint {
text : String ,
time : Time ,
@ -28,11 +29,16 @@ impl TimePoint {
pub fn new ( text : & str ) -> Self {
Self {
text : String ::from ( text ) ,
time : OffsetDateTime:: now_local( ) . time ( ) ,
time : now( ) ,
}
}
}
fn now ( ) -> Time {
let raw_time = OffsetDateTime ::now_local ( ) . time ( ) ;
Time ::try_from_hms ( raw_time . hour ( ) , raw_time . minute ( ) , 0 ) . unwrap ( )
}
impl fmt ::Display for TimePoint {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
write! ( f , "[{}] {}" , self . time . format ( "%H:%M" ) , self . text )
@ -72,9 +78,11 @@ fn effective_text(s: String) -> String {
impl TimeSheet {
pub fn open_or_create ( path : & str ) -> Self {
let times = read_times ( path ) . unwrap_or_else ( | | vec! [ TimePoint ::new ( "start" ) ] ) ;
let selected = times . len ( ) . saturating_sub ( 1 ) ;
Self {
times : read_times ( path ) . unwrap_or_else ( | | vec! [ TimePoint ::new ( "start" ) ] ) ,
selected : 0 ,
times ,
selected ,
register : None ,
}
}
@ -90,17 +98,21 @@ impl TimeSheet {
pub fn shift_current ( & mut self , minutes : i64 ) {
let time = & mut self . times [ self . selected ] . time ;
* time + = Duration ::minutes ( minutes ) ;
* time - = Duration ::minutes ( time . minute ( ) as i64 % 5 )
* time - = Duration ::minutes ( time . minute ( ) as i64 % 5 ) ;
let timepoint = self . times [ self . selected ] . clone ( ) ;
self . times . sort_by_key ( | tp | tp . time ) ;
self . selected = self . times . iter ( ) . position ( | tp | tp = = & timepoint ) . unwrap ( ) ;
}
fn current ( & self ) -> & TimePoint {
& self . times [ self . selected ]
}
fn grouped_times ( & self ) -> impl Iterator < Item = ( String , Duration ) > {
fn grouped_times ( & self ) -> collections ::BTreeMap < String , Duration > {
let last_time = self . times . last ( ) ;
self . times
. iter ( )
. chain ( iter::once ( & TimePoint ::new ( "end" ) ) )
. chain ( TimeSheet::maybe_end_time ( last_time ) . iter ( ) )
. tuple_windows ( )
. map ( | ( prev , next ) | ( prev . text . clone ( ) , next . time - prev . time ) )
// Fold into a map to group by description.
@ -111,26 +123,45 @@ impl TimeSheet {
. or_insert_with ( Duration ::zero ) + = duration ;
map
} )
. into_iter ( )
}
fn maybe_end_time ( last_time : Option < & TimePoint > ) -> Option < TimePoint > {
match last_time {
Some ( tp ) if PAUSE_TEXTS . contains ( & & tp . text [ .. ] ) = > None ,
Some ( tp ) if tp . text = = END_TEXT = > None ,
Some ( tp ) if tp . time > now ( ) = > None ,
_ = > Some ( TimePoint ::new ( END_TEXT ) ) ,
}
}
pub fn time_by_tasks ( & self ) -> String {
self . grouped_times ( )
. into_iter ( )
. filter ( | ( text , _ ) | text ! = MAIN_PAUSE_TEXT )
. map ( | ( text , duration ) | format! ( "{}: {}" , text , format_duration ( & duration ) ) )
. join ( " | " )
. join ( " \n ")
}
pub fn sum_as_str ( & self ) -> String {
let total = self
. grouped_times ( )
let total = self . grouped_times ( )
. into_iter ( )
. filter ( | ( text , _ ) | text ! = MAIN_PAUSE_TEXT )
. fold ( Duration ::zero ( ) , | total , ( _ , d ) | total + d ) ;
format_duration ( & total )
}
pub fn pause_time ( & self ) -> String {
let times = self . grouped_times ( ) ;
let duration = times
. get ( MAIN_PAUSE_TEXT )
. map ( Duration ::clone )
. unwrap_or_else ( Duration ::zero ) ;
format! ( "{}: {}" , MAIN_PAUSE_TEXT , format_duration ( & duration ) )
}
}
fn format_duration ( d : & Duration ) -> String {
format! ( "{}:{:02}" , d . whole_hours ( ) , d . whole_minutes ( ) . max ( 1 ) % 60 )
format! ( "{}:{:02}" , d . whole_hours ( ) , d . whole_minutes ( ) % 60 )
}
impl ListView < TimePoint > for TimeSheet {