use std::convert::TryFrom; use std::io::{BufRead, Write}; use clap::ArgMatches; use chrono::{DateTime, Utc}; use crate::error::{Error, Result}; use crate::timeparse::parse_time; use crate::database::Database; use crate::models::Entry; use crate::io::Streams; use super::{Command, Facts, r#in, sheet}; #[derive(Default)] pub struct Args { id: Option, at: Option>, } impl<'a> TryFrom<&'a ArgMatches<'a>> for Args { type Error = Error; fn try_from(matches: &'a ArgMatches) -> Result { Ok(Args { at: matches.value_of("at").map(|s| parse_time(s)).transpose()?, id: matches.value_of("id").map(|i| i.parse().unwrap()), }) } } fn resume(args: Args, streams: &mut Streams, facts: &Facts, entry: Entry) -> Result<()> where D: Database, I: BufRead, O: Write, E: Write, { writeln!( streams.out, "Resuming \"{}\" from entry #{}", entry.note.clone().unwrap_or_else(|| "".to_owned()), entry.id )?; r#in::InCommand::handle(r#in::Args { at: args.at, note: entry.note, }, streams, facts) } pub struct ResumeCommand; impl<'a> Command<'a> for ResumeCommand { type Args = Args; fn handle(args: Args, streams: &mut Streams, facts: &Facts) -> Result<()> where D: Database, I: BufRead, O: Write, E: Write, { let current_sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".to_owned()); // First try to process using the given id if let Some(entry_id) = args.id { if let Some(entry) = streams.db.entry_by_id(entry_id)? { if entry.sheet != current_sheet { // first swith to the sheet sheet::SheetCommand::handle(sheet::Args { sheet: Some(entry.sheet.clone()), }, streams, facts)?; } return resume(args, streams, facts, entry); } else { writeln!(streams.out, "The entry with id '{}' could not be found to be resumed. Perhaps it was deleted?", entry_id)?; return Ok(()); } } // No id specified, try to find something suitable to switch to in the // database if let Some(entry) = streams.db.last_checkout_of_sheet(¤t_sheet)? { resume(args, streams, facts, entry) } else { writeln!(streams.out, "No entry to resume in the sheet '{}'. Perhaps start a new one? Hint: use t in", current_sheet)?; Ok(()) } } } #[cfg(test)] mod tests { use chrono::Duration; use pretty_assertions::assert_eq; use crate::test_utils::Ps; use super::*; #[test] fn resume_an_entry() { let args = Default::default(); let mut streams = Streams::fake(b""); let facts = Facts::new(); let one_hour_ago = facts.now - Duration::hours(1); let two_hours_ago = facts.now - Duration::hours(2); streams.db.entry_insert(two_hours_ago, Some(one_hour_ago), Some("fake note".into()), "default").unwrap(); assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 1); ResumeCommand::handle(args, &mut streams, &facts).unwrap(); let all_entries = streams.db.entries_full(None, None).unwrap(); assert_eq!(all_entries.len(), 2); assert_eq!(all_entries[1].id, 2); assert_eq!(all_entries[1].start, facts.now); assert_eq!(all_entries[1].end, None); assert_eq!(all_entries[1].note, Some("fake note".into())); assert_eq!(all_entries[1].sheet, "default"); assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Resuming \"fake note\" from entry #1 Checked into sheet \"default\".\n")); assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps("")); } #[test] fn no_entries_to_resume() { let args = Default::default(); let mut streams = Streams::fake(b""); let facts = Facts::new(); ResumeCommand::handle(args, &mut streams, &facts).unwrap(); assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("\ No entry to resume in the sheet 'default'. Perhaps start a new one? Hint: use t in ")); } }