From 63154cc1fc95b64661ca677be08afa087ef0941c Mon Sep 17 00:00:00 2001 From: Abraham Toriz Date: Fri, 30 Jul 2021 17:55:19 -0500 Subject: [PATCH] implement kill command --- src/commands.rs | 1 + src/commands/kill.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++ src/database.rs | 20 +++++++++++ src/main.rs | 21 +++++++++--- 4 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 src/commands/kill.rs diff --git a/src/commands.rs b/src/commands.rs index cafc861..934768c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -19,6 +19,7 @@ pub mod list; pub mod out; pub mod resume; pub mod backend; +pub mod kill; pub trait Command<'a> { type Args: TryFrom<&'a ArgMatches<'a>>; diff --git a/src/commands/kill.rs b/src/commands/kill.rs new file mode 100644 index 0000000..2308d7d --- /dev/null +++ b/src/commands/kill.rs @@ -0,0 +1,79 @@ +use std::convert::TryFrom; +use std::io::{self, Write}; + +use clap::ArgMatches; +use chrono::{DateTime, Utc}; + +use crate::error::{Error, Result}; +use crate::database::Database; +use crate::config::Config; + +use super::Command; + +fn read_line() -> String { + let mut pre_n = String::new(); + io::stdin().read_line(&mut pre_n).unwrap(); + pre_n +} + +#[derive(Debug)] +pub enum Args { + Id(u64), + Sheet(String), +} + +impl<'a> TryFrom<&'a ArgMatches<'a>> for Args { + type Error = Error; + + fn try_from(args: &'a ArgMatches) -> Result { + Ok(args.value_of("id").map(|v| { + Args::Id(v.parse().unwrap()) + }).unwrap_or_else(|| { + Args::Sheet(args.value_of("sheet").unwrap().to_owned()) + })) + } +} + +pub struct KillCommand; + +impl<'a> Command<'a> for KillCommand { + type Args = Args; + + fn handle(args: Args, db: &mut D, out: &mut O, _err: &mut E, _config: &Config, _now: DateTime) -> Result<()> + where + D: Database, + O: Write, + E: Write, + { + match args { + Args::Id(id) => { + if let Some(entry) = db.entry_by_id(id)? { + writeln!(out, "are you sure you want to delete entry {}? ({}) [y/n]", entry.id, entry.note.unwrap_or("".into()))?; + + if read_line().to_lowercase().starts_with("y") { + db.delete_entry_by_id(id)?; + writeln!(out, "It's dead")?; + } else { + writeln!(out, "Don't worry, it's still there")?; + } + } else { + writeln!(out, "There's no entry with id {}. Someone found it before we did.", id)?; + } + }, + Args::Sheet(sheet) => { + let n = db.entries_by_sheet(&sheet, None, None)?.len(); + + writeln!(out, "are you sure you want to delete {} entries on sheet \"{}\"?", n, sheet)?; + + if read_line().to_lowercase().starts_with("y") { + db.delete_entries_in_sheet(&sheet)?; + writeln!(out, "They're gone")?; + } else { + writeln!(out, "Don't worry, they're still there")?; + } + } + } + + Ok(()) + } +} diff --git a/src/database.rs b/src/database.rs index a5a3d23..7040a47 100644 --- a/src/database.rs +++ b/src/database.rs @@ -169,6 +169,22 @@ pub trait Database { Ok(self.entry_query("select * from entries where end is not null and sheet=?1 order by end desc limit 1", &[&sheet])?.into_iter().next()) } + fn delete_entry_by_id(&mut self, id: u64) -> Result<()> { + Ok(self.execute("delete from entries where id=?1", &[&id])?) + } + + fn delete_entries_in_sheet(&mut self, sheet: &str) -> Result<()> { + self.execute("delete from entries where sheet=?1", &[&sheet])?; + + if let Some(last) = self.last_sheet()? { + if last == sheet { + self.unset_last_sheet()?; + } + } + + Ok(()) + } + // Meta queries fn current_sheet(&self) -> Result> { let results = self.meta_query("select * from meta where key='current_sheet'", &[])?; @@ -196,6 +212,10 @@ pub trait Database { Ok(()) } + fn unset_last_sheet(&mut self) -> Result<()> { + Ok(self.execute("delete from meta where key='last_sheet'", &[])?) + } + fn version(&self) -> Result { let results = self.meta_query("select * from meta where key='database_version'", &[])?; diff --git a/src/main.rs b/src/main.rs index 7a4ceb9..083a95e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use tiempo::commands::{ Command, r#in::InCommand, display::DisplayCommand, sheet::SheetCommand, today::TodayCommand, yesterday::YesterdayCommand, week::WeekCommand, month::MonthCommand, list::ListCommand, out::OutCommand, - resume::ResumeCommand, backend::BackendCommand, + resume::ResumeCommand, backend::BackendCommand, kill::KillCommand, }; fn error_trap(matches: ArgMatches) -> error::Result<()> { @@ -41,6 +41,7 @@ fn error_trap(matches: ArgMatches) -> error::Result<()> { ("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), + ("kill", Some(matches)) => KillCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), (cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())), } @@ -97,8 +98,7 @@ fn main() { Ok(()) } else { Err(format!("the --id arg must be a number. '{}' is not a valid number", v)) - }) - .help("Use entry with ID instead of the last entry"); + }); // Now declar this app's cli let matches = App::new("Tiempo") @@ -249,7 +249,7 @@ fn main() { .visible_alias("r") .about("Restart the timer for an entry. Defaults to the last active entry") .arg(at_arg.clone()) - .arg(id_arg.clone()) + .arg(id_arg.clone().help("Use entry with ID instead of the last entry")) ) .subcommand(SubCommand::with_name("out") @@ -272,6 +272,19 @@ fn main() { .help("List archive sheets also")) ) + .subcommand(SubCommand::with_name("kill") + .visible_alias("k") + .about("Delete an entry or an entire sheet") + .arg(id_arg.clone().help("Delete entry with this ID instead of sheet")) + .arg(Arg::with_name("sheet") + .takes_value(true).value_name("SHEET") + .conflicts_with("id") + .required_unless("id") + .help( + "Delete an entire sheet by its name" + )) + ) + .get_matches(); if let Err(e) = error_trap(matches) {