From c512e43c96aea94afaa427bda5055fcc3331a91d Mon Sep 17 00:00:00 2001 From: Abraham Toriz Date: Wed, 7 Jul 2021 11:58:13 -0500 Subject: [PATCH] add yesterday command --- src/commands.rs | 1 + src/commands/yesterday.rs | 97 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 22 ++++++++- 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/commands/yesterday.rs diff --git a/src/commands.rs b/src/commands.rs index 45d9919..cb865a8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -10,6 +10,7 @@ use crate::config::Config; pub mod r#in; pub mod display; pub mod today; +pub mod yesterday; pub mod sheet; pub trait Command<'a> { diff --git a/src/commands/yesterday.rs b/src/commands/yesterday.rs new file mode 100644 index 0000000..bbc959f --- /dev/null +++ b/src/commands/yesterday.rs @@ -0,0 +1,97 @@ +use std::convert::TryFrom; +use std::io::Write; + +use clap::ArgMatches; +use chrono::{Utc, Local, Duration}; +use regex::Regex; + +use crate::error::{Result, Error}; +use crate::database::Database; +use crate::formatters::Formatter; +use crate::config::Config; +use crate::regex::parse_regex; + +use super::{Command, display::{Sheet, entries_for_display}}; + +#[derive(Default)] +pub struct Args { + ids: bool, + format: Formatter, + grep: Option, + sheet: Option, +} + +impl<'a> TryFrom<&'a ArgMatches<'a>> for Args { + type Error = Error; + + fn try_from(matches: &'a ArgMatches) -> Result { + Ok(Args { + ids: matches.is_present("ids"), + format: matches.value_of("format").unwrap().parse()?, + grep: matches.value_of("grep").map(parse_regex).transpose()?, + sheet: matches.value_of("sheet").map(|s| s.parse()).transpose()?, + }) + } +} + +pub struct YesterdayCommand { } + +impl<'a> Command<'a> for YesterdayCommand { + type Args = Args; + + fn handle(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> Result<()> + where + D: Database, + O: Write, + E: Write, + { + let start = Some((Local::today() - Duration::days(1)).and_hms(0, 0, 0).with_timezone(&Utc)); + let end = Some(Local::today().and_hms(0, 0, 0).with_timezone(&Utc)); + + entries_for_display(start, end, args.sheet, db, out, err, args.format, args.ids, args.grep) + } +} + +#[cfg(test)] +mod tests { + use chrono::Duration; + use pretty_assertions::assert_eq; + + use crate::database::SqliteDatabase; + use crate::test_utils::PrettyString; + + use super::*; + + #[test] + fn returns_yesterday_entries_only() { + let args = Args { + format: Formatter::Csv, + ..Default::default() + }; + let mut db = SqliteDatabase::from_memory().unwrap(); + let mut out = Vec::new(); + let mut err = Vec::new(); + let config = Default::default(); + + db.init().unwrap(); + + let two_days_ago = Local::now().date() - Duration::days(2); + let yesterday = Local::now().date() - Duration::days(1); + let today = Local::now().date(); + + db.entry_insert(two_days_ago.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default".into()).unwrap(); + db.entry_insert(yesterday.and_hms(1, 2, 3).with_timezone(&Utc), None, Some("This!".into()), "default".into()).unwrap(); + db.entry_insert(today.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default".into()).unwrap(); + + YesterdayCommand::handle(args, &mut db, &mut out, &mut err, &config).unwrap(); + + assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString(&format!("start,end,note,sheet +{},,This!,default +", yesterday.and_hms(1, 2, 3).with_timezone(&Utc).to_rfc3339_opts(chrono::SecondsFormat::Secs, true)))); + + assert_eq!( + String::from_utf8_lossy(&err), + String::new(), + ); + } +} diff --git a/src/main.rs b/src/main.rs index f77ec70..76a6174 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use tiempo::database::SqliteDatabase; use tiempo::config::Config; use tiempo::commands::{ Command, r#in::InCommand, display::DisplayCommand, sheet::SheetCommand, - today::TodayCommand, + today::TodayCommand, yesterday::YesterdayCommand, }; fn error_trap(matches: ArgMatches) -> error::Result<()> { @@ -24,6 +24,7 @@ fn error_trap(matches: ArgMatches) -> error::Result<()> { ("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), ("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), + ("yesterday", Some(matches)) => YesterdayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), ("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config), @@ -164,6 +165,25 @@ fn main() { .help("The sheet to display. Pass 'all' to see entries from all sheets or 'full' to see hidden entries")) ) + .subcommand(SubCommand::with_name("yesterday") + .visible_alias("y") + .about("Display entries that started yesterday") + .arg(Arg::with_name("ids") + .short("v").long("ids") + .help("Print database ids (for use with edit)")) + .arg(Arg::with_name("format") + .short("f").long("format") + .takes_value(true).value_name("FORMAT").default_value("text") + .help("The output format. Valid built-in formats are ical, csv, json, ids, factor, and text. Documentation on defining custom formats can be found in the README included in this distribution.")) + .arg(Arg::with_name("grep") + .short("g").long("grep") + .takes_value(true).value_name("REGEXP") + .help("Include entries where the note matches this regexp.")) + .arg(Arg::with_name("sheet") + .takes_value(true).value_name("SHEET") + .help("The sheet to display. Pass 'all' to see entries from all sheets or 'full' to see hidden entries")) + ) + .subcommand(SubCommand::with_name("in") .visible_alias("i") .about("Start an activity in the current timesheet")