use std::convert::TryFrom; use std::io::{BufRead, Write}; use clap::ArgMatches; use crate::error::{Result, Error}; use crate::database::Database; use crate::old::{entries_or_warning, warn_if_needed}; use crate::tabulate::{Tabulate, Col, Align::*}; use crate::formatters::text::format_duration; use crate::io::Streams; use super::{Command, Facts}; #[derive(Default)] pub struct Args { } impl<'a> TryFrom<&'a ArgMatches<'a>> for Args { type Error = Error; fn try_from(_matches: &'a ArgMatches) -> Result { Ok(Args { }) } } pub struct NowCommand { } impl<'a> Command<'a> for NowCommand { type Args = Args; fn handle(_args: Self::Args, streams: &mut Streams, facts: &Facts) -> Result<()> where D: Database, I: BufRead, O: Write, E: Write, { let entries = streams.db.running_entries()?; let (entries, needs_warning) = entries_or_warning(entries, &streams.db)?; if entries.is_empty() { streams.out.write_all(b"No running entries\n")?; } else { let current = streams.db.current_sheet()?; let last = streams.db.last_sheet()?; let mut tabs = Tabulate::with_columns(vec![ // indicator of current or prev sheet Col::new().min_width(1).and_alignment(Right), // sheet name Col::new().min_width(9).and_alignment(Left), // running time Col::new().min_width(9).and_alignment(Right), // activity Col::new().min_width(0).and_alignment(Left), ]); tabs.feed(vec!["", "Timesheet", "Running", "Activity"]); tabs.separator(' '); for entry in entries { tabs.feed(vec![ if current == entry.sheet { "*" } else if last.as_ref() == Some(&entry.sheet) { "-" } else { "" }.to_string(), entry.sheet, format_duration(facts.now - entry.start), entry.note.unwrap_or_default(), ]); } streams.out.write_all(tabs.print(facts.env.stdout_is_tty).as_bytes())?; } warn_if_needed(&mut streams.err, needs_warning, &facts.env)?; Ok(()) } } #[cfg(test)] mod tests { use chrono::{Utc, TimeZone, Local}; use pretty_assertions::assert_str_eq; use crate::database::{SqliteDatabase, Database}; use super::*; #[test] fn list_sheets() { std::env::set_var("TZ", "CST+6"); let mut streams = Streams::fake(b""); let now = Utc.ymd(2021, 1, 1).and_hms(13, 52, 45); let facts = Facts::new().with_now(now); streams.db.set_current_sheet("sheet2").unwrap(); streams.db.set_last_sheet("sheet4").unwrap(); 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, Some("some".to_string()), "sheet4").unwrap(); NowCommand::handle(Default::default(), &mut streams, &facts).unwrap(); assert_str_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Activity - sheet4 1:52:45 some "); } #[test] fn old_database() { let mut streams = Streams::fake(b"").with_db( SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap() ); let now = Local.ymd(2021, 7, 16).and_hms(11, 30, 45); let facts = Facts::new().with_now(now.with_timezone(&Utc)); NowCommand::handle(Default::default(), &mut streams, &facts).unwrap(); assert_str_eq!(&String::from_utf8_lossy(&streams.out), " Timesheet Running Activity * default 0:10:24 que "); assert_str_eq!( String::from_utf8_lossy(&streams.err), "[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" ); } #[test] fn with_no_running_entries_display_nicer_message() { std::env::set_var("TZ", "CST+6"); let mut streams = Streams::fake(b""); let now = Utc.ymd(2021, 1, 1).and_hms(13, 52, 45); let facts = Facts::new().with_now(now); NowCommand::handle(Default::default(), &mut streams, &facts).unwrap(); assert_str_eq!(&String::from_utf8_lossy(&streams.out), "No running entries "); } }