2021-06-21 17:38:51 -05:00
use std ::str ::FromStr ;
use std ::io ::Write ;
2021-06-28 19:32:29 -05:00
use std ::borrow ::Cow ;
2021-06-21 17:38:51 -05:00
2021-06-18 11:27:19 -05:00
use serde ::{ Serialize , Deserialize } ;
2021-06-22 11:27:50 -05:00
use itertools ::Itertools ;
2021-06-25 13:44:40 -05:00
use chrono ::{
DateTime , Utc , Offset , TimeZone , Duration , NaiveTime , Timelike ,
NaiveDateTime ,
} ;
2021-06-18 11:27:19 -05:00
2021-06-21 17:38:51 -05:00
use crate ::error ;
use crate ::models ::Entry ;
2021-06-24 00:28:10 -05:00
fn format_duration ( dur : Duration ) -> String {
format! ( " {} : {:02} : {:02} " , dur . num_hours ( ) , dur . num_minutes ( ) % 60 , dur . num_seconds ( ) % 60 )
}
2021-06-25 13:44:40 -05:00
fn format_start ( t : NaiveTime ) -> String {
format! ( " {:02} : {:02} : {:02} - " , t . hour ( ) , t . minute ( ) , t . second ( ) )
}
fn lpad ( s : & str , len : usize ) -> String {
let padding = " " . repeat ( len . saturating_sub ( s . len ( ) ) ) ;
padding + s
}
fn rpad ( s : & str , len : usize ) -> String {
let padding = " " . repeat ( len . saturating_sub ( s . len ( ) ) ) ;
s . to_string ( ) + & padding
}
fn format_end ( start : NaiveDateTime , end : NaiveDateTime ) -> String {
let extra_days = ( end - start ) . num_days ( ) ;
let d = if extra_days > 0 { format! ( " + {} d " , extra_days ) } else { " " . into ( ) } ;
format! (
" {:02}:{:02}:{:02}{} " ,
end . hour ( ) ,
end . minute ( ) % 60 ,
end . second ( ) % 60 ,
d
)
2021-06-24 09:02:28 -05:00
}
2021-06-28 19:32:29 -05:00
fn constrained_lines ( text : & str , width : usize ) -> Vec < Cow < '_ , str > > {
textwrap ::wrap ( text , width )
}
2021-06-18 11:27:19 -05:00
#[ derive(Debug, Serialize, Deserialize) ]
#[ serde(rename_all = " lowercase " ) ]
pub enum Formatter {
Text ,
2021-06-21 17:38:51 -05:00
Custom ( String ) ,
}
impl Formatter {
2021-06-24 00:07:52 -05:00
/// Prints the given entries to the specified output device.
///
/// the current time is given as the `now` argument and the offset from UTC
/// to the local timezone is given in `offset` to prevent this function from
/// using a secondary effect to retrieve the time and conver dates. This
/// also makes it easier to test.
2021-06-28 19:32:29 -05:00
pub fn print_formatted < W : Write , O : Offset > ( & self , entries : Vec < Entry > , out : & mut W , now : DateTime < Utc > , offset : O , ids : bool , term_width : usize ) -> error ::Result < ( ) > {
2021-06-21 17:38:51 -05:00
match & self {
2021-06-28 19:32:29 -05:00
Formatter ::Text = > self . print_formatted_text ( entries , out , now , offset , ids , term_width ) ? ,
2021-06-21 17:38:51 -05:00
Formatter ::Custom ( name ) = > {
}
}
Ok ( ( ) )
}
2021-06-22 11:27:50 -05:00
/// Print in the default text format. Assume entries are sorted by sheet and
/// then by start
2021-06-28 19:32:29 -05:00
fn print_formatted_text < W : Write , O : Offset > ( & self , entries : Vec < Entry > , out : & mut W , now : DateTime < Utc > , offset : O , ids : bool , term_width : usize ) -> error ::Result < ( ) > {
2021-06-22 11:27:50 -05:00
let grouped_entries = entries . into_iter ( ) . group_by ( | e | e . sheet . to_string ( ) ) ;
2021-06-24 00:07:52 -05:00
// Build a timezone based on the offset given to this function. This
// will later be used to properly group the entries by date in the local
// timezone.
let fixed_offset = offset . fix ( ) ;
2021-06-22 11:27:50 -05:00
for ( key , group ) in grouped_entries . into_iter ( ) {
writeln! ( out , " Timesheet: {} " , key ) ? ;
2021-06-25 13:44:40 -05:00
// End Duration Notes")?;
// A vector of lines to be printed, with all the components
let mut lines = Vec ::new ( ) ;
2021-06-24 00:07:52 -05:00
2021-06-24 00:28:10 -05:00
let entries_by_date = group . group_by ( | e | fixed_offset . from_utc_datetime ( & e . start . naive_utc ( ) ) . date ( ) ) ;
let mut total = Duration ::seconds ( 0 ) ;
for ( date , entries ) in entries_by_date . into_iter ( ) {
let mut daily = Duration ::seconds ( 0 ) ;
2021-06-24 00:07:52 -05:00
2021-06-25 14:05:27 -05:00
for ( i , entry ) in entries . into_iter ( ) . enumerate ( ) {
2021-06-25 13:44:40 -05:00
let start = format_start ( fixed_offset . from_utc_datetime ( & entry . start . naive_utc ( ) ) . time ( ) ) ;
let end = entry . end . map ( | t | {
format_end (
fixed_offset . from_utc_datetime ( & entry . start . naive_utc ( ) ) . naive_local ( ) ,
fixed_offset . from_utc_datetime ( & t . naive_utc ( ) ) . naive_local ( )
)
} ) . unwrap_or ( " " . into ( ) ) ;
2021-06-24 00:07:52 -05:00
let duration = entry . end . unwrap_or ( now ) - entry . start ;
2021-06-24 00:28:10 -05:00
daily = daily + duration ;
let duration = format_duration ( duration ) ;
2021-06-24 00:07:52 -05:00
2021-06-25 14:05:27 -05:00
let id = if ids { entry . id . to_string ( ) } else { " " . into ( ) } ;
2021-06-24 00:07:52 -05:00
if i = = 0 {
let date = date . format ( " %a %b %d, %Y " ) . to_string ( ) ;
2021-06-25 14:05:27 -05:00
lines . push ( [ id , date , start , end , duration , entry . note ] ) ;
2021-06-24 00:07:52 -05:00
} else {
2021-06-25 14:05:27 -05:00
lines . push ( [ id , " " . into ( ) , start , end , duration , entry . note ] ) ;
2021-06-24 00:07:52 -05:00
}
}
2021-06-24 00:28:10 -05:00
total = total + daily ;
2021-06-25 13:44:40 -05:00
lines . push ( [ " " . into ( ) , " " . into ( ) , " " . into ( ) , " " . into ( ) , format_duration ( daily ) , " " . into ( ) ] ) ;
}
// compute some column widths before printing
// When array_map is stabilized this can be shortened
let lengths = lines
. iter ( )
. map ( | [ i , d , s , e , du , n ] | [ i . len ( ) , d . len ( ) , s . len ( ) , e . len ( ) , du . len ( ) , n . len ( ) ] )
. reduce ( | [ a , b , c , d , e , f ] , [ g , h , i , j , k , l ] | {
[ a . max ( g ) , b . max ( h ) , c . max ( i ) , d . max ( j ) , e . max ( k ) , f . max ( l ) ]
} ) . unwrap ( ) ;
writeln! ( out ,
" {} {} {} {} {} {} " ,
2021-06-25 14:05:27 -05:00
if ids { lpad ( " ID " , 3. max ( lengths [ 0 ] ) ) } else { lpad ( " " , 3. max ( lengths [ 0 ] ) ) } ,
2021-06-25 13:44:40 -05:00
rpad ( " Day " , 18 ) ,
rpad ( " Start " , 10 ) ,
2021-06-25 13:56:48 -05:00
rpad ( " End " , 10. max ( lengths [ 3 ] ) ) ,
2021-06-25 13:44:40 -05:00
rpad ( " Duration " , 8. max ( lengths [ 4 ] ) ) ,
" Notes " ,
) ? ;
2021-06-28 19:32:29 -05:00
let mut max_note_length = 0 ;
2021-06-25 13:44:40 -05:00
for [ id , date , start , end , duration , note ] in lines {
2021-06-28 19:32:29 -05:00
let first_line = format! (
2021-06-29 07:20:52 -05:00
" {} {} {} {} {} " ,
2021-06-25 13:44:40 -05:00
lpad ( & id , 3. max ( lengths [ 0 ] ) ) ,
rpad ( & date , 18 ) ,
rpad ( & start , 10 ) ,
2021-06-25 13:56:48 -05:00
rpad ( & end , 10. max ( lengths [ 3 ] ) ) ,
lpad ( & duration , 8. max ( lengths [ 4 ] ) ) ,
2021-06-28 19:32:29 -05:00
) ;
2021-06-29 07:20:52 -05:00
let space_left = term_width . saturating_sub ( first_line . len ( ) + 1 ) . max ( 40 ) ;
2021-06-28 19:32:29 -05:00
let note_lines = constrained_lines ( & note , space_left ) ;
for ( i , note_line ) in note_lines . into_iter ( ) . enumerate ( ) {
if i = = 0 {
2021-06-29 07:20:52 -05:00
if note_line . len ( ) ! = 0 {
writeln! ( out , " {} {} " , first_line , note_line ) ? ;
} else {
writeln! ( out , " {} " , first_line ) ? ;
}
2021-06-28 19:32:29 -05:00
} else {
2021-06-29 07:20:52 -05:00
writeln! ( out , " {} {} " , " " . repeat ( first_line . len ( ) ) , note_line ) ? ;
2021-06-28 19:32:29 -05:00
}
if note_line . len ( ) > max_note_length {
max_note_length = note_line . len ( ) ;
}
}
2021-06-24 00:07:52 -05:00
}
2021-06-24 00:28:10 -05:00
2021-06-25 13:44:40 -05:00
writeln! ( out ,
" {} {}-{}-{}-{}-{} " ,
lpad ( " " , 3. max ( lengths [ 0 ] ) ) ,
" - " . repeat ( 18 ) ,
" - " . repeat ( 10 ) ,
2021-06-25 13:56:48 -05:00
" - " . repeat ( 10. max ( lengths [ 3 ] ) ) ,
2021-06-25 13:44:40 -05:00
" - " . repeat ( 8. max ( lengths [ 4 ] ) ) ,
2021-06-28 19:32:29 -05:00
" - " . repeat ( 4. max ( max_note_length ) ) ,
2021-06-25 13:44:40 -05:00
) ? ;
writeln! ( out ,
" {} {} {} {} {} " ,
lpad ( " " , 3. max ( lengths [ 0 ] ) ) ,
rpad ( " Total " , 18 ) ,
rpad ( " " , 10 ) ,
2021-06-25 13:56:48 -05:00
rpad ( " " , 10. max ( lengths [ 3 ] ) ) ,
lpad ( & format_duration ( total ) , 8. max ( lengths [ 4 ] ) ) ,
2021-06-25 13:44:40 -05:00
) ? ;
2021-06-22 11:27:50 -05:00
}
Ok ( ( ) )
}
2021-06-21 17:38:51 -05:00
}
impl FromStr for Formatter {
type Err = error ::Error ;
fn from_str ( s : & str ) -> error ::Result < Formatter > {
let lower = s . to_lowercase ( ) ;
Ok ( match & * lower {
" text " = > Formatter ::Text ,
custom_format = > Formatter ::Custom ( custom_format . into ( ) ) ,
} )
}
2021-06-18 11:27:19 -05:00
}
2021-06-21 19:54:10 -05:00
#[ cfg(test) ]
mod tests {
2021-06-22 10:18:16 -05:00
use pretty_assertions ::assert_eq ;
2021-06-24 00:07:52 -05:00
use chrono ::TimeZone ;
2021-06-22 10:18:16 -05:00
2021-06-29 12:00:46 -05:00
use crate ::test_utils ::PrettyString ;
2021-06-28 19:32:29 -05:00
2021-06-29 12:00:46 -05:00
use super ::* ;
2021-06-22 10:18:16 -05:00
2021-06-29 12:00:46 -05:00
const LONG_NOTE : & 'static str = " chatting with bob about upcoming task, district sharing of images, how the user settings currently works etc. Discussing the fingerprinting / cache busting issue with CKEDITOR, suggesting perhaps looking into forking the rubygem and seeing if we can work in our own changes, however hard that might be. " ;
2021-06-22 10:18:16 -05:00
2021-06-28 19:32:29 -05:00
#[ test ]
fn test_constrained_lines_long_text ( ) {
assert_eq! ( constrained_lines ( LONG_NOTE , 46 ) , vec! [
" chatting with bob about upcoming task, " ,
" district sharing of images, how the user " ,
" settings currently works etc. Discussing the " ,
" fingerprinting / cache busting issue with " ,
" CKEDITOR, suggesting perhaps looking into " ,
" forking the rubygem and seeing if we can work " ,
" in our own changes, however hard that might " ,
" be. " ,
] ) ;
}
2021-06-21 19:54:10 -05:00
#[ test ]
fn test_text_output ( ) {
let formatter = Formatter ::Text ;
let mut output = Vec ::new ( ) ;
let entries = vec! [
2021-06-24 00:07:52 -05:00
Entry ::new_sample ( 1 , Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 12 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 14 , 0 , 0 ) ) ) ,
2021-06-24 00:28:10 -05:00
Entry ::new_sample ( 2 , Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 16 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 18 , 0 , 0 ) ) ) ,
Entry ::new_sample ( 3 , Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 16 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 18 , 0 , 0 ) ) ) ,
2021-06-24 00:07:52 -05:00
Entry ::new_sample ( 4 , Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 18 , 0 , 0 ) , None ) ,
2021-06-21 19:54:10 -05:00
] ;
2021-06-24 00:07:52 -05:00
let now = Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 20 , 0 , 0 ) ;
let offset = Utc ;
2021-06-28 19:32:29 -05:00
formatter . print_formatted ( entries , & mut output , now , offset , false , 100 ) . unwrap ( ) ;
2021-06-21 19:54:10 -05:00
2021-06-22 10:18:16 -05:00
assert_eq! ( PrettyString ( & String ::from_utf8_lossy ( & output ) ) , PrettyString ( " Timesheet: default
2021-06-25 13:56:48 -05:00
Day Start End Duration Notes
Fri Oct 03 , 2008 12 :00 :00 - 14 :00 :00 2 :00 :00 entry 1
16 :00 :00 - 18 :00 :00 2 :00 :00 entry 2
2021-06-29 07:20:52 -05:00
4 :00 :00
2021-06-25 13:56:48 -05:00
Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 entry 3
18 :00 :00 - 2 :00 :00 entry 4
2021-06-29 07:20:52 -05:00
4 :00 :00
2021-06-25 13:56:48 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 8 :00 :00
2021-06-24 09:02:28 -05:00
" ));
}
#[ test ]
fn test_text_output_with_millis ( ) {
let formatter = Formatter ::Text ;
let mut output = Vec ::new ( ) ;
let entries = vec! [
Entry ::new_sample ( 1 , Utc . ymd ( 2008 , 10 , 3 ) . and_hms_milli ( 12 , 0 , 0 , 432 ) , Some ( Utc . ymd ( 2008 , 10 , 3 ) . and_hms_milli ( 14 , 0 , 0 , 312 ) ) ) ,
] ;
let now = Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 20 , 0 , 0 ) ;
let offset = Utc ;
2021-06-28 19:32:29 -05:00
formatter . print_formatted ( entries , & mut output , now , offset , false , 100 ) . unwrap ( ) ;
2021-06-24 09:02:28 -05:00
assert_eq! ( PrettyString ( & String ::from_utf8_lossy ( & output ) ) , PrettyString ( " Timesheet: default
2021-06-25 13:56:48 -05:00
Day Start End Duration Notes
Fri Oct 03 , 2008 12 :00 :00 - 14 :00 :00 1 :59 :59 entry 1
2021-06-29 07:20:52 -05:00
1 :59 :59
2021-06-25 13:56:48 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 1 :59 :59
2021-06-25 13:44:40 -05:00
" ));
}
#[ test ]
fn test_text_output_long_duration ( ) {
let formatter = Formatter ::Text ;
let mut output = Vec ::new ( ) ;
let entries = vec! [
Entry ::new_sample ( 1 , Utc . ymd ( 2008 , 10 , 1 ) . and_hms ( 12 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 14 , 0 , 0 ) ) ) ,
Entry ::new_sample ( 2 , Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 12 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 14 , 0 , 0 ) ) ) ,
] ;
let now = Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 20 , 0 , 0 ) ;
let offset = Utc ;
2021-06-28 19:32:29 -05:00
formatter . print_formatted ( entries , & mut output , now , offset , false , 100 ) . unwrap ( ) ;
2021-06-25 13:44:40 -05:00
assert_eq! ( PrettyString ( & String ::from_utf8_lossy ( & output ) ) , PrettyString ( " Timesheet: default
Day Start End Duration Notes
Wed Oct 01 , 2008 12 :00 :00 - 14 :00 :00 + 2 d 50 :00 :00 entry 1
2021-06-29 07:20:52 -05:00
50 :00 :00
2021-06-25 13:44:40 -05:00
Fri Oct 03 , 2008 12 :00 :00 - 14 :00 :00 2 :00 :00 entry 2
2021-06-29 07:20:52 -05:00
2 :00 :00
2021-06-25 13:44:40 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 52 :00 :00
2021-06-25 14:05:27 -05:00
" ));
}
#[ test ]
fn test_text_output_with_ids ( ) {
let formatter = Formatter ::Text ;
let mut output = Vec ::new ( ) ;
let entries = vec! [
Entry ::new_sample ( 1 , Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 12 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 14 , 0 , 0 ) ) ) ,
Entry ::new_sample ( 2 , Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 16 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 3 ) . and_hms ( 18 , 0 , 0 ) ) ) ,
Entry ::new_sample ( 3 , Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 16 , 0 , 0 ) , Some ( Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 18 , 0 , 0 ) ) ) ,
Entry ::new_sample ( 4 , Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 18 , 0 , 0 ) , None ) ,
] ;
let now = Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 20 , 0 , 0 ) ;
let offset = Utc ;
2021-06-28 19:32:29 -05:00
formatter . print_formatted ( entries , & mut output , now , offset , true , 100 ) . unwrap ( ) ;
2021-06-25 14:05:27 -05:00
assert_eq! ( PrettyString ( & String ::from_utf8_lossy ( & output ) ) , PrettyString ( " Timesheet: default
ID Day Start End Duration Notes
1 Fri Oct 03 , 2008 12 :00 :00 - 14 :00 :00 2 :00 :00 entry 1
2 16 :00 :00 - 18 :00 :00 2 :00 :00 entry 2
2021-06-29 07:20:52 -05:00
4 :00 :00
2021-06-25 14:05:27 -05:00
3 Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 entry 3
4 18 :00 :00 - 2 :00 :00 entry 4
2021-06-29 07:20:52 -05:00
4 :00 :00
2021-06-25 14:05:27 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 8 :00 :00
2021-06-28 19:32:29 -05:00
" ));
}
#[ test ]
fn test_text_output_long_note_default_with ( ) {
let formatter = Formatter ::Text ;
let mut output = Vec ::new ( ) ;
let entries = vec! [
Entry {
id : 1 ,
sheet : " default " . into ( ) ,
start : Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 16 , 0 , 0 ) ,
end : Some ( Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 18 , 0 , 0 ) ) ,
note : LONG_NOTE . into ( ) ,
} ,
] ;
let now = Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 20 , 0 , 0 ) ;
let offset = Utc ;
formatter . print_formatted ( entries , & mut output , now , offset , false , 100 ) . unwrap ( ) ;
assert_eq! ( PrettyString ( & String ::from_utf8_lossy ( & output ) ) , PrettyString ( " Timesheet: default
Day Start End Duration Notes
Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 chatting with bob about upcoming task ,
district sharing of images , how the user
settings currently works etc . Discussing the
fingerprinting / cache busting issue with
CKEDITOR , suggesting perhaps looking into
forking the rubygem and seeing if we can work
in our own changes , however hard that might
be .
2021-06-29 07:20:52 -05:00
2 :00 :00
2021-06-28 19:32:29 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 2 :00 :00
" ));
}
#[ test ]
fn test_text_output_long_note_with_ids ( ) {
let formatter = Formatter ::Text ;
let mut output = Vec ::new ( ) ;
let entries = vec! [
Entry {
id : 60000 ,
sheet : " default " . into ( ) ,
start : Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 16 , 0 , 0 ) ,
end : Some ( Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 18 , 0 , 0 ) ) ,
note : LONG_NOTE . into ( ) ,
} ,
] ;
let now = Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 20 , 0 , 0 ) ;
let offset = Utc ;
formatter . print_formatted ( entries , & mut output , now , offset , true , 100 ) . unwrap ( ) ;
assert_eq! ( PrettyString ( & String ::from_utf8_lossy ( & output ) ) , PrettyString ( " Timesheet: default
ID Day Start End Duration Notes
60000 Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 chatting with bob about upcoming task ,
district sharing of images , how the user
settings currently works etc . Discussing the
fingerprinting / cache busting issue with
CKEDITOR , suggesting perhaps looking into
forking the rubygem and seeing if we can
work in our own changes , however hard that
might be .
2021-06-29 07:20:52 -05:00
2 :00 :00
2021-06-28 19:32:29 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 2 :00 :00
" ));
}
#[ test ]
fn test_text_output_note_with_line_breaks ( ) {
let formatter = Formatter ::Text ;
let mut output = Vec ::new ( ) ;
let entries = vec! [
Entry {
id : 1 ,
sheet : " default " . into ( ) ,
start : Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 16 , 0 , 0 ) ,
end : Some ( Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 18 , 0 , 0 ) ) ,
note : " first line \n and a second line " . into ( ) ,
} ,
] ;
let now = Utc . ymd ( 2008 , 10 , 5 ) . and_hms ( 20 , 0 , 0 ) ;
let offset = Utc ;
formatter . print_formatted ( entries , & mut output , now , offset , false , 100 ) . unwrap ( ) ;
assert_eq! ( PrettyString ( & String ::from_utf8_lossy ( & output ) ) , PrettyString ( " Timesheet: default
Day Start End Duration Notes
Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 first line
and a second line
2021-06-29 07:20:52 -05:00
2 :00 :00
2021-06-28 19:32:29 -05:00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 2 :00 :00
2021-06-22 10:18:16 -05:00
" ));
2021-06-21 19:54:10 -05:00
}
}