diff --git a/src/formatters.rs b/src/formatters.rs index e53b885..f23c2b9 100644 --- a/src/formatters.rs +++ b/src/formatters.rs @@ -1,54 +1,13 @@ use std::str::FromStr; use std::io::Write; -use std::borrow::Cow; use serde::{Serialize, Deserialize}; -use itertools::Itertools; -use chrono::{ - DateTime, Utc, TimeZone, Duration, NaiveTime, Timelike, NaiveDateTime, - Local, -}; +use chrono::{DateTime, Utc}; use crate::error; use crate::models::Entry; -fn format_duration(dur: Duration) -> String { - format!("{}:{:02}:{:02}", dur.num_hours(), dur.num_minutes() % 60, dur.num_seconds() % 60) -} - -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 - ) -} - -fn constrained_lines(text: &str, width: usize) -> Vec> { - textwrap::wrap(text, width) -} - +pub mod text; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] @@ -72,132 +31,13 @@ impl Formatter { /// also makes it easier to test. pub fn print_formatted(&self, entries: Vec, out: &mut W, now: DateTime, ids: bool, term_width: usize) -> error::Result<()> { match &self { - Formatter::Text => self.print_formatted_text(entries, out, now, ids, term_width)?, + Formatter::Text => text::print_formatted(entries, out, now, ids, term_width)?, Formatter::Custom(name) => { } } Ok(()) } - - /// Print in the default text format. Assume entries are sorted by sheet and - /// then by start - fn print_formatted_text(&self, entries: Vec, out: &mut W, now: DateTime, ids: bool, term_width: usize) -> error::Result<()> { - let grouped_entries = entries.into_iter().group_by(|e| e.sheet.to_string()); - - for (key, group) in grouped_entries.into_iter() { - writeln!(out, "Timesheet: {}", key)?; - - // End Duration Notes")?; - // A vector of lines to be printed, with all the components - let mut lines = Vec::new(); - - let entries_by_date = group.group_by(|e| Local.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); - - for (i, entry) in entries.into_iter().enumerate() { - let start = format_start(Local.from_utc_datetime(&entry.start.naive_utc()).time()); - let end = entry.end.map(|t| { - format_end( - Local.from_utc_datetime(&entry.start.naive_utc()).naive_local(), - Local.from_utc_datetime(&t.naive_utc()).naive_local() - ) - }).unwrap_or(" ".into()); - let duration = entry.end.unwrap_or(now) - entry.start; - daily = daily + duration; - let duration = format_duration(duration); - - let id = if ids { entry.id.to_string() } else { "".into() }; - - if i == 0 { - let date = date.format("%a %b %d, %Y").to_string(); - - lines.push([id, date, start, end, duration, entry.note]); - } else { - lines.push([id, "".into(), start, end, duration, entry.note]); - } - } - - total = total + daily; - - 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, - "{} {} {} {} {} {}", - if ids { lpad("ID", 3.max(lengths[0])) } else { lpad(" ", 3.max(lengths[0])) }, - rpad("Day", 18), - rpad("Start", 10), - rpad("End", 10.max(lengths[3])), - rpad("Duration", 8.max(lengths[4])), - "Notes", - )?; - - let mut max_note_length = 0; - - for [id, date, start, end, duration, note] in lines { - let first_line = format!( - "{} {} {} {} {}", - lpad(&id, 3.max(lengths[0])), - rpad(&date, 18), - rpad(&start, 10), - rpad(&end, 10.max(lengths[3])), - lpad(&duration, 8.max(lengths[4])), - ); - - let space_left = term_width.saturating_sub(first_line.len() + 1).max(40); - let note_lines = constrained_lines(¬e, space_left); - - for (i, note_line) in note_lines.into_iter().enumerate() { - if i == 0 { - if note_line.len() != 0 { - writeln!(out, "{} {}", first_line, note_line)?; - } else { - writeln!(out, "{}", first_line)?; - } - } else { - writeln!(out, "{} {}", " ".repeat(first_line.len()), note_line)?; - } - - if note_line.len() > max_note_length { - max_note_length = note_line.len(); - } - } - } - - writeln!(out, - "{} {}-{}-{}-{}-{}", - lpad(" ", 3.max(lengths[0])), - "-".repeat(18), - "-".repeat(10), - "-".repeat(10.max(lengths[3])), - "-".repeat(8.max(lengths[4])), - "-".repeat(4.max(max_note_length)), - )?; - writeln!(out, - "{} {} {} {} {}", - lpad(" ", 3.max(lengths[0])), - rpad("Total", 18), - rpad("", 10), - rpad("", 10.max(lengths[3])), - lpad(&format_duration(total), 8.max(lengths[4])), - )?; - } - - Ok(()) - } } impl FromStr for Formatter { @@ -212,234 +52,3 @@ impl FromStr for Formatter { }) } } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_eq; - use chrono::TimeZone; - - use crate::test_utils::PrettyString; - - use super::*; - - 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."; - - #[test] - fn test_constrained_lines_long_text() { - std::env::set_var("TZ", "UTC"); - 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.", - ]); - } - - #[test] - fn test_text_output() { - std::env::set_var("TZ", "UTC"); - 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); - - formatter.print_formatted(entries, &mut output, now, false, 100).unwrap(); - - assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default - 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 - 4:00:00 - Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 entry 3 - 18:00:00 - 2:00:00 entry 4 - 4:00:00 - --------------------------------------------------------- - Total 8:00:00 -")); - } - - #[test] - fn test_text_output_with_millis() { - std::env::set_var("TZ", "UTC"); - 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); - - formatter.print_formatted(entries, &mut output, now, false, 100).unwrap(); - - assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default - Day Start End Duration Notes - Fri Oct 03, 2008 12:00:00 - 14:00:00 1:59:59 entry 1 - 1:59:59 - --------------------------------------------------------- - Total 1:59:59 -")); - } - - #[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; - - formatter.print_formatted(entries, &mut output, now, false, 100).unwrap(); - - 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+2d 50:00:00 entry 1 - 50:00:00 - Fri Oct 03, 2008 12:00:00 - 14:00:00 2:00:00 entry 2 - 2:00:00 - ---------------------------------------------------------- - Total 52:00: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; - - formatter.print_formatted(entries, &mut output, now, true, 100).unwrap(); - - 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 - 4:00: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 - 4:00:00 - --------------------------------------------------------- - Total 8:00: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, 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. - 2:00: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, 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. - 2:00: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\nand 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, 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 - 2:00:00 - ------------------------------------------------------------------- - Total 2:00:00 -")); - } -} diff --git a/src/formatters/text.rs b/src/formatters/text.rs new file mode 100644 index 0000000..fcdae12 --- /dev/null +++ b/src/formatters/text.rs @@ -0,0 +1,383 @@ +use std::io::Write; +use std::borrow::Cow; + +use itertools::Itertools; +use chrono::{ + DateTime, Utc, TimeZone, Duration, Local, NaiveTime, Timelike, + NaiveDateTime, +}; + +use crate::models::Entry; +use crate::error::Result; + +fn format_duration(dur: Duration) -> String { + format!("{}:{:02}:{:02}", dur.num_hours(), dur.num_minutes() % 60, dur.num_seconds() % 60) +} + +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 + ) +} + +fn constrained_lines(text: &str, width: usize) -> Vec> { + textwrap::wrap(text, width) +} + +/// Print in the default text format. Assume entries are sorted by sheet and +/// then by start +pub fn print_formatted(entries: Vec, out: &mut W, now: DateTime, ids: bool, term_width: usize) -> Result<()> { + let grouped_entries = entries.into_iter().group_by(|e| e.sheet.to_string()); + + for (key, group) in grouped_entries.into_iter() { + writeln!(out, "Timesheet: {}", key)?; + + // End Duration Notes")?; + // A vector of lines to be printed, with all the components + let mut lines = Vec::new(); + + let entries_by_date = group.group_by(|e| Local.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); + + for (i, entry) in entries.into_iter().enumerate() { + let start = format_start(Local.from_utc_datetime(&entry.start.naive_utc()).time()); + let end = entry.end.map(|t| { + format_end( + Local.from_utc_datetime(&entry.start.naive_utc()).naive_local(), + Local.from_utc_datetime(&t.naive_utc()).naive_local() + ) + }).unwrap_or(" ".into()); + let duration = entry.end.unwrap_or(now) - entry.start; + daily = daily + duration; + let duration = format_duration(duration); + + let id = if ids { entry.id.to_string() } else { "".into() }; + + if i == 0 { + let date = date.format("%a %b %d, %Y").to_string(); + + lines.push([id, date, start, end, duration, entry.note]); + } else { + lines.push([id, "".into(), start, end, duration, entry.note]); + } + } + + total = total + daily; + + 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, + "{} {} {} {} {} {}", + if ids { lpad("ID", 3.max(lengths[0])) } else { lpad(" ", 3.max(lengths[0])) }, + rpad("Day", 18), + rpad("Start", 10), + rpad("End", 10.max(lengths[3])), + rpad("Duration", 8.max(lengths[4])), + "Notes", + )?; + + let mut max_note_length = 0; + + for [id, date, start, end, duration, note] in lines { + let first_line = format!( + "{} {} {} {} {}", + lpad(&id, 3.max(lengths[0])), + rpad(&date, 18), + rpad(&start, 10), + rpad(&end, 10.max(lengths[3])), + lpad(&duration, 8.max(lengths[4])), + ); + + let space_left = term_width.saturating_sub(first_line.len() + 1).max(40); + let note_lines = constrained_lines(¬e, space_left); + + for (i, note_line) in note_lines.into_iter().enumerate() { + if i == 0 { + if note_line.len() != 0 { + writeln!(out, "{} {}", first_line, note_line)?; + } else { + writeln!(out, "{}", first_line)?; + } + } else { + writeln!(out, "{} {}", " ".repeat(first_line.len()), note_line)?; + } + + if note_line.len() > max_note_length { + max_note_length = note_line.len(); + } + } + } + + writeln!(out, + "{} {}-{}-{}-{}-{}", + lpad(" ", 3.max(lengths[0])), + "-".repeat(18), + "-".repeat(10), + "-".repeat(10.max(lengths[3])), + "-".repeat(8.max(lengths[4])), + "-".repeat(4.max(max_note_length)), + )?; + writeln!(out, + "{} {} {} {} {}", + lpad(" ", 3.max(lengths[0])), + rpad("Total", 18), + rpad("", 10), + rpad("", 10.max(lengths[3])), + lpad(&format_duration(total), 8.max(lengths[4])), + )?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::test_utils::PrettyString; + + 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."; + + #[test] + fn test_constrained_lines_long_text() { + std::env::set_var("TZ", "UTC"); + 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.", + ]); + } + + #[test] + fn test_text_output() { + std::env::set_var("TZ", "UTC"); + 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); + + print_formatted(entries, &mut output, now, false, 100).unwrap(); + + assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default + 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 + 4:00:00 + Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 entry 3 + 18:00:00 - 2:00:00 entry 4 + 4:00:00 + --------------------------------------------------------- + Total 8:00:00 +")); + } + + #[test] + fn test_text_output_with_millis() { + std::env::set_var("TZ", "UTC"); + 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); + + print_formatted(entries, &mut output, now, false, 100).unwrap(); + + assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default + Day Start End Duration Notes + Fri Oct 03, 2008 12:00:00 - 14:00:00 1:59:59 entry 1 + 1:59:59 + --------------------------------------------------------- + Total 1:59:59 +")); + } + + #[test] + fn test_text_output_long_duration() { + 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); + + print_formatted(entries, &mut output, now, false, 100).unwrap(); + + 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+2d 50:00:00 entry 1 + 50:00:00 + Fri Oct 03, 2008 12:00:00 - 14:00:00 2:00:00 entry 2 + 2:00:00 + ---------------------------------------------------------- + Total 52:00:00 +")); + } + + #[test] + fn test_text_output_with_ids() { + 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); + + print_formatted(entries, &mut output, now, true, 100).unwrap(); + + 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 + 4:00: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 + 4:00:00 + --------------------------------------------------------- + Total 8:00:00 +")); + } + + #[test] + fn test_text_output_long_note_default_with() { + 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); + + print_formatted(entries, &mut output, now, 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. + 2:00:00 + ----------------------------------------------------------------------------------------------- + Total 2:00:00 +")); + } + + #[test] + fn test_text_output_long_note_with_ids() { + 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); + + print_formatted(entries, &mut output, now, 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. + 2:00:00 + ---------------------------------------------------------------------------------------------- + Total 2:00:00 +")); + } + + #[test] + fn test_text_output_note_with_line_breaks() { + 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\nand a second line".into(), + }, + ]; + + let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0); + + print_formatted(entries, &mut output, now, 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 + 2:00:00 + ------------------------------------------------------------------- + Total 2:00:00 +")); + } +}