2021-07-26 16:55:24 -05:00
|
|
|
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;
|
2021-07-28 20:51:33 -05:00
|
|
|
use crate::models::Entry;
|
2021-07-26 16:55:24 -05:00
|
|
|
|
2021-07-28 20:51:33 -05:00
|
|
|
use super::{Command, r#in, sheet};
|
2021-07-26 16:55:24 -05:00
|
|
|
|
|
|
|
#[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()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-28 20:51:33 -05:00
|
|
|
fn resume<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, entry: Entry, now: DateTime<Utc>) -> Result<()>
|
|
|
|
where
|
|
|
|
D: Database,
|
|
|
|
O: Write,
|
|
|
|
E: Write,
|
|
|
|
{
|
|
|
|
writeln!(
|
|
|
|
out,
|
|
|
|
"Resuming \"{}\" from entry #{}",
|
2021-08-02 19:10:34 -05:00
|
|
|
entry.note.clone().unwrap_or_else(|| "".to_owned()), entry.id
|
2021-07-28 20:51:33 -05:00
|
|
|
)?;
|
|
|
|
|
|
|
|
r#in::InCommand::handle(r#in::Args {
|
|
|
|
at: args.at,
|
|
|
|
note: entry.note,
|
|
|
|
}, db, out, err, config, now)
|
|
|
|
}
|
|
|
|
|
2021-07-26 16:55:24 -05:00
|
|
|
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,
|
|
|
|
{
|
2021-08-02 19:10:34 -05:00
|
|
|
let current_sheet = db.current_sheet()?.unwrap_or_else(|| "default".to_owned());
|
2021-07-26 16:55:24 -05:00
|
|
|
|
2021-07-28 20:51:33 -05:00
|
|
|
// First try to process using the given id
|
|
|
|
if let Some(entry_id) = args.id {
|
2021-07-26 16:55:24 -05:00
|
|
|
if let Some(entry) = db.entry_by_id(entry_id)? {
|
2021-07-28 20:51:33 -05:00
|
|
|
if entry.sheet != current_sheet {
|
|
|
|
// first swith to the sheet
|
|
|
|
sheet::SheetCommand::handle(sheet::Args {
|
|
|
|
sheet: Some(entry.sheet.clone()),
|
|
|
|
}, db, out, err, config, now)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
return resume(args, db, out, err, config, entry, now);
|
2021-07-26 16:55:24 -05:00
|
|
|
} else {
|
|
|
|
writeln!(out, "The entry with id '{}' could not be found to be resumed. Perhaps it was deleted?", entry_id)?;
|
|
|
|
|
2021-07-28 20:51:33 -05:00
|
|
|
return Ok(());
|
2021-07-26 16:55:24 -05:00
|
|
|
}
|
2021-07-28 20:51:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// No id specified, try to find something suitable to switch to in the
|
|
|
|
// database
|
|
|
|
if let Some(entry) = db.last_checkout_of_sheet(¤t_sheet)? {
|
|
|
|
resume(args ,db, out, err, config, entry, now)
|
2021-07-26 16:55:24 -05:00
|
|
|
} else {
|
2021-07-28 20:51:33 -05:00
|
|
|
writeln!(out, "No entry to resume in the sheet '{}'. Perhaps start a new one?
|
|
|
|
Hint: use t in", current_sheet)?;
|
2021-07-26 16:55:24 -05:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use chrono::Duration;
|
2021-07-28 20:51:33 -05:00
|
|
|
use pretty_assertions::assert_eq;
|
2021-07-26 16:55:24 -05:00
|
|
|
|
2021-08-03 20:56:40 -05:00
|
|
|
use crate::test_utils::Ps;
|
2021-07-26 16:55:24 -05:00
|
|
|
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();
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
2021-08-03 20:56:40 -05:00
|
|
|
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Resuming \"fake note\" from entry #1
|
2021-07-26 16:55:24 -05:00
|
|
|
Checked into sheet \"default\".\n"));
|
2021-08-03 20:56:40 -05:00
|
|
|
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
2021-07-26 16:55:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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();
|
|
|
|
|
2021-08-03 20:56:40 -05:00
|
|
|
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("\
|
2021-07-28 20:51:33 -05:00
|
|
|
No entry to resume in the sheet 'default'. Perhaps start a new one?
|
|
|
|
Hint: use t in
|
2021-07-26 16:55:24 -05:00
|
|
|
"));
|
|
|
|
}
|
|
|
|
}
|