141 lines
5.3 KiB
Rust
141 lines
5.3 KiB
Rust
use std::str::FromStr;
|
|
use std::io::Write;
|
|
|
|
use serde::{Serialize, Deserialize};
|
|
use itertools::Itertools;
|
|
use chrono::{DateTime, Utc, Offset, TimeZone};
|
|
|
|
use crate::error;
|
|
use crate::models::Entry;
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum Formatter {
|
|
Text,
|
|
Custom(String),
|
|
}
|
|
|
|
impl Formatter {
|
|
/// 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.
|
|
pub fn print_formatted<W: Write, O: Offset>(&self, entries: Vec<Entry>, out: &mut W, now: DateTime<Utc>, offset: O) -> error::Result<()> {
|
|
match &self {
|
|
Formatter::Text => self.print_formatted_text(entries, out, now, offset)?,
|
|
Formatter::Custom(name) => {
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Print in the default text format. Assume entries are sorted by sheet and
|
|
/// then by start
|
|
fn print_formatted_text<W: Write, O: Offset>(&self, entries: Vec<Entry>, out: &mut W, now: DateTime<Utc>, offset: O) -> error::Result<()> {
|
|
let grouped_entries = entries.into_iter().group_by(|e| e.sheet.to_string());
|
|
|
|
// 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();
|
|
|
|
for (key, group) in grouped_entries.into_iter() {
|
|
writeln!(out, "Timesheet: {}", key)?;
|
|
writeln!(out, " Day Start End Duration Notes")?;
|
|
|
|
let by_day = group.group_by(|e| fixed_offset.from_utc_datetime(&e.start.naive_utc()).date());
|
|
|
|
for (date, entries) in by_day.into_iter() {
|
|
for (i,entry) in entries.into_iter().enumerate() {
|
|
let start = fixed_offset.from_utc_datetime(&entry.start.naive_utc()).time().to_string();
|
|
let end = entry.end.map(|t| fixed_offset.from_utc_datetime(&t.naive_utc()).time().to_string()).unwrap_or(" ".into());
|
|
let duration = entry.end.unwrap_or(now) - entry.start;
|
|
let duration = format!("{}:{:02}:{:02}", duration.num_hours(), duration.num_minutes() % 60, duration.num_seconds() & 60);
|
|
|
|
if i == 0 {
|
|
let date = date.format("%a %b %d, %Y").to_string();
|
|
|
|
writeln!(
|
|
out,
|
|
" {date} {start} - {end} {duration} {note}",
|
|
date=date, start=start, end=end, duration=duration, note=entry.note,
|
|
)?;
|
|
} else {
|
|
writeln!(
|
|
out,
|
|
" {start} - {end} {duration} {note}",
|
|
start=start, end=end, duration=duration, note=entry.note,
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
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()),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::fmt;
|
|
|
|
use pretty_assertions::assert_eq;
|
|
use chrono::TimeZone;
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
pub struct PrettyString<'a>(pub &'a str);
|
|
|
|
/// Make diff to display string as multi-line string
|
|
impl<'a> fmt::Debug for PrettyString<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(self.0)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_text_output() {
|
|
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(12, 0, 0), Some(Utc.ymd(2008, 10, 3).and_hms(14, 0, 0))),
|
|
Entry::new_sample(3, Utc.ymd(2008, 10, 5).and_hms(16, 0, 0), Some(Utc.ymd(2008, 10, 3).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, offset).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
|
|
"));
|
|
}
|
|
}
|