the out command
This commit is contained in:
parent
acf2e30883
commit
b5c383d3c3
|
@ -16,6 +16,7 @@ pub mod sheet;
|
|||
pub mod week;
|
||||
pub mod month;
|
||||
pub mod list;
|
||||
pub mod out;
|
||||
|
||||
pub trait Command<'a> {
|
||||
type Args: TryFrom<&'a ArgMatches<'a>>;
|
||||
|
|
|
@ -5,12 +5,12 @@ use clap::ArgMatches;
|
|||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::error;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::editor;
|
||||
use crate::commands::Command;
|
||||
use crate::config::Config;
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::old::{start_or_warning, warn_if_needed};
|
||||
use crate::old::{time_or_warning, warn_if_needed};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -19,9 +19,9 @@ pub struct Args {
|
|||
}
|
||||
|
||||
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
|
||||
type Error = error::Error;
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(matches: &'a ArgMatches) -> Result<Self, Self::Error> {
|
||||
fn try_from(matches: &'a ArgMatches) -> Result<Self> {
|
||||
Ok(Args {
|
||||
at: matches.value_of("at").map(|s| parse_time(s)).transpose()?,
|
||||
note: matches.value_of("note").map(|s| s.into()),
|
||||
|
@ -34,7 +34,7 @@ pub struct InCommand {}
|
|||
impl<'a> Command<'a> for InCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> error::Result<()>
|
||||
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,
|
||||
|
@ -43,7 +43,7 @@ impl<'a> Command<'a> for InCommand {
|
|||
let start = args.at.unwrap_or(now);
|
||||
let sheet = db.current_sheet()?.unwrap_or("default".into());
|
||||
|
||||
if db.has_running_entry(&sheet)? {
|
||||
if db.running_entry(&sheet)?.is_some() {
|
||||
writeln!(out, "Timer is already running for sheet '{}'", sheet)?;
|
||||
|
||||
return Ok(());
|
||||
|
@ -55,11 +55,11 @@ impl<'a> Command<'a> for InCommand {
|
|||
if !config.require_note {
|
||||
None
|
||||
} else {
|
||||
Some(editor::get_string(config)?)
|
||||
Some(editor::get_string(config.note_editor.as_ref())?)
|
||||
}
|
||||
};
|
||||
|
||||
let (start, needs_warning) = start_or_warning(start, db)?;
|
||||
let (start, needs_warning) = time_or_warning(start, db)?;
|
||||
|
||||
db.entry_insert(start, None, note.map(|n| n.trim().to_owned()), &sheet)?;
|
||||
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
use std::io::Write;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::config::Config;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::old::{time_or_warning, warn_if_needed};
|
||||
|
||||
use super::Command;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(matches: &'a ArgMatches) -> Result<Args> {
|
||||
Ok(Args {
|
||||
at: matches.value_of("at").map(|s| parse_time(s)).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutCommand{}
|
||||
|
||||
impl<'a> Command<'a> for OutCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D: Database, O: Write, E: Write>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()> {
|
||||
let end = args.at.unwrap_or(now);
|
||||
let sheet = db.current_sheet()?.unwrap_or("default".into());
|
||||
|
||||
let (end, needs_warning) = time_or_warning(end, db)?;
|
||||
|
||||
if let Some(entry) = db.running_entry(&sheet)? {
|
||||
writeln!(out, "Checked out of sheet \"{}\".", sheet)?;
|
||||
|
||||
db.entry_update(entry.id, entry.start, Some(end), entry.note, &entry.sheet)?;
|
||||
} else {
|
||||
writeln!(out, "No running entry on sheet \"{}\".", sheet)?;
|
||||
}
|
||||
|
||||
warn_if_needed(err, needs_warning)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ansi_term::Color::Yellow;
|
||||
use pretty_assertions::assert_eq;
|
||||
use chrono::{TimeZone, Local};
|
||||
|
||||
use crate::test_utils::PrettyString;
|
||||
use crate::database::SqliteDatabase;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn finishes_entry() {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let args = Default::default();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
|
||||
db.init().unwrap();
|
||||
|
||||
db.entry_insert(now, None, None, "default").unwrap();
|
||||
|
||||
OutCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
let e = db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
|
||||
assert_eq!(e.end, Some(now));
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Checked out of sheet \"default\".\n"));
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&err)), PrettyString(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tells_if_no_running_entry() {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let args = Default::default();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
|
||||
db.init().unwrap();
|
||||
|
||||
db.entry_insert(now, None, None, "non-default").unwrap();
|
||||
|
||||
OutCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("No running entry on sheet \"default\".\n"));
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&err)), PrettyString(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warns_if_old_database() {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let args = Default::default();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
|
||||
db.init_old().unwrap();
|
||||
|
||||
db.entry_insert(now, None, None, "default").unwrap();
|
||||
|
||||
OutCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
let e = db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
|
||||
assert_eq!(e.end, Some(Utc.from_utc_datetime(&now.with_timezone(&Local).naive_local())));
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Checked out of sheet \"default\".\n"));
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&err)), PrettyString(&format!(
|
||||
"{} You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate\n",
|
||||
Yellow.bold().paint("[WARNING]"))));
|
||||
}
|
||||
}
|
|
@ -150,8 +150,14 @@ pub trait Database {
|
|||
])
|
||||
}
|
||||
|
||||
fn has_running_entry(&self, sheet: &str) -> Result<bool> {
|
||||
Ok(self.entry_query("select * from entries where end is null and sheet=?1", &[&sheet])?.len() != 0)
|
||||
fn entry_update(&mut self, id: u64, start: DateTime<Utc>, end: Option<DateTime<Utc>>, note: Option<String>, sheet: &str) -> Result<()> {
|
||||
self.execute("update entries set start=?2, end=?3, note=?4, sheet=?5 where id=?1", &[
|
||||
&id, &start, &end, ¬e, &sheet
|
||||
])
|
||||
}
|
||||
|
||||
fn running_entry(&self, sheet: &str) -> Result<Option<Entry>> {
|
||||
Ok(self.entry_query("select * from entries where end is null and sheet=?1", &[&sheet])?.into_iter().next())
|
||||
}
|
||||
|
||||
// Meta queries
|
||||
|
|
|
@ -11,7 +11,7 @@ use tiempo::config::Config;
|
|||
use tiempo::commands::{
|
||||
Command, r#in::InCommand, display::DisplayCommand, sheet::SheetCommand,
|
||||
today::TodayCommand, yesterday::YesterdayCommand, week::WeekCommand,
|
||||
month::MonthCommand, list::ListCommand,
|
||||
month::MonthCommand, list::ListCommand, out::OutCommand,
|
||||
};
|
||||
|
||||
fn error_trap(matches: ArgMatches) -> error::Result<()> {
|
||||
|
@ -24,6 +24,7 @@ fn error_trap(matches: ArgMatches) -> error::Result<()> {
|
|||
|
||||
match matches.subcommand() {
|
||||
("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("out", Some(matches)) => OutCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
|
||||
("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
|
|
|
@ -61,7 +61,7 @@ pub fn entries_or_warning<D: Database>(entries: Vec<Entry>, db: &D) -> Result<(V
|
|||
}
|
||||
}
|
||||
|
||||
pub fn start_or_warning<D: Database>(time: DateTime<Utc>, db: &D) -> Result<(DateTime<Utc>, bool)> {
|
||||
pub fn time_or_warning<D: Database>(time: DateTime<Utc>, db: &D) -> Result<(DateTime<Utc>, bool)> {
|
||||
if let DBVersion::Timetrap = db.version()? {
|
||||
Ok((utc_to_local(time), true))
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue