tiempo-rs/src/commands/resume.rs

146 lines
4.4 KiB
Rust

use std::convert::TryFrom;
use std::io::Write;
use clap::ArgMatches;
use chrono::{DateTime, Utc};
use crate::error::{Error, Result};
use crate::timeparse::parse_time;
use crate::config::Config;
use crate::database::Database;
use super::{Command, r#in};
#[derive(Default)]
pub struct Args {
id: Option<u64>,
at: Option<DateTime<Utc>>,
}
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
type Error = Error;
fn try_from(matches: &'a ArgMatches) -> Result<Self> {
Ok(Args {
at: matches.value_of("at").map(|s| parse_time(s)).transpose()?,
id: matches.value_of("id").map(|i| i.parse().unwrap()),
})
}
}
pub struct ResumeCommand;
impl<'a> Command<'a> for ResumeCommand {
type Args = Args;
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
where
D: Database,
O: Write,
E: Write,
{
let entry_id = args.id.or(db.last_checkout_id()?);
if let Some(entry_id) = entry_id {
if let Some(entry) = db.entry_by_id(entry_id)? {
writeln!(out, "Resuming \"{}\" from entry #{}", entry.note.as_ref().unwrap_or(&"".to_owned()), entry.id)?;
r#in::InCommand::handle(r#in::Args {
at: args.at,
note: entry.note,
}, db, out, err, config, now)
} else {
writeln!(out, "The entry with id '{}' could not be found to be resumed. Perhaps it was deleted?", entry_id)?;
Ok(())
}
} else {
writeln!(out, "No entry to resume. Either no --id was given or no last_checkout_id was found in the database.
Perhaps start an entry with t in")?;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use chrono::Duration;
use crate::test_utils::PrettyString;
use crate::database::SqliteDatabase;
use super::*;
#[test]
fn resume_an_entry() {
let args = Default::default();
let mut db = SqliteDatabase::from_memory().unwrap();
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
let one_hour_ago = now - Duration::hours(1);
let two_hours_ago = now - Duration::hours(2);
db.init().unwrap();
db.entry_insert(two_hours_ago, Some(one_hour_ago), Some("fake note".into()), "default").unwrap();
db.set_last_checkout_id(1).unwrap();
assert_eq!(db.entries_full(None, None).unwrap().len(), 1);
ResumeCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
let all_entries = 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, 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!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Resuming \"fake note\" from entry #1
Checked into sheet \"default\".\n"));
assert_eq!(PrettyString(&String::from_utf8_lossy(&err)), PrettyString(""));
}
#[test]
fn no_entries_to_resume() {
let args = Default::default();
let mut db = SqliteDatabase::from_memory().unwrap();
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
db.init().unwrap();
ResumeCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("\
No entry to resume. Either no --id was given or no last_checkout_id was found in the database.
Perhaps start an entry with t in
"));
}
#[test]
fn last_checkout_id_does_not_exist() {
let args = Default::default();
let mut db = SqliteDatabase::from_memory().unwrap();
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
db.init().unwrap();
db.set_last_checkout_id(23).unwrap();
ResumeCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("\
The entry with id '23' could not be found to be resumed. Perhaps it was deleted?
"));
}
}