diff --git a/src/cli.rs b/src/cli.rs index 25b944f..2636f5b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -81,7 +81,19 @@ pub enum Command { // TODO Add templates for tasks and notes #[derive(Debug, StructOpt)] pub enum Template { - /// An undated task marked as done today + /// Adds a task + #[structopt(alias = "t")] + Task { + /// If specified, the task is dated to this date + date: Option, + }, + /// Adds a note + #[structopt(alias = "n")] + Note { + /// If specified, the note is dated to this date + date: Option, + }, + /// Adds an undated task marked as done today #[structopt(alias = "d")] Done, } @@ -126,6 +138,10 @@ where }) } +fn parse_eval_date(name: &str, text: &str, today: NaiveDate) -> Result { + parse_eval_arg(name, text, |date: CliDate| date.eval((), today)) +} + fn parse_show_idents(identifiers: &[String], today: NaiveDate) -> Result> { let mut idents = vec![]; for ident in identifiers { @@ -152,7 +168,17 @@ fn run_command(opt: &Opt, files: &mut Files, range: DateRange, now: NaiveDateTim show::show(files, &entries, &layout, &idents); } Some(Command::New { template }) => match template { - Template::Done => new::done(files, now)?, + Template::Task { date: Some(date) } => { + let date = parse_eval_date("date", date, now.date())?; + new::task(files, Some(date))? + } + Template::Task { date: None } => new::task(files, None)?, + Template::Note { date: Some(date) } => { + let date = parse_eval_date("date", date, now.date())?; + new::note(files, Some(date))? + } + Template::Note { date: None } => new::note(files, None)?, + Template::Done => new::done(files, now.date())?, }, Some(Command::Done { entries: ns }) => { let entries = find_entries(files, range)?; diff --git a/src/cli/new.rs b/src/cli/new.rs index 67a66e6..ecd040f 100644 --- a/src/cli/new.rs +++ b/src/cli/new.rs @@ -1,46 +1,114 @@ +use std::result; use std::str::FromStr; -use chrono::NaiveDateTime; +use chrono::NaiveDate; use codespan_reporting::files::SimpleFile; use crate::files::cli::CliCommand; -use crate::files::commands::{Done, DoneKind, Task}; -use crate::files::Files; +use crate::files::commands::{Command, DateSpec, Done, DoneKind, Note, Spec, Statement, Task}; +use crate::files::{Files, ParseError}; use super::error::{Error, Result}; use super::util; -pub fn done(files: &mut Files, now: NaiveDateTime) -> Result<()> { - let capture = files.capture().ok_or(Error::NoCaptureFile)?; - - let command = Task { - title: String::new(), - statements: vec![], - done: vec![Done { - kind: DoneKind::Done, - date: None, - done_at: now.date(), - }], - desc: vec![], - }; - - let mut text = format!("{command}"); - let command = loop { +fn edit(name: &str, mut text: String, validate: F) -> Result> +where + R: FromStr>, + F: Fn(&R) -> result::Result<(), &str>, +{ + Ok(loop { text = util::edit(&text)?; - match CliCommand::from_str(&text) { - Ok(command) => break command.0, - Err(e) => crate::error::eprint_error(&SimpleFile::new("new command", &text), &e), + match text.parse() { + Ok(command) => match validate(&command) { + Ok(()) => break Some(command), + Err(msg) => eprintln!("{msg}"), + }, + Err(e) => crate::error::eprint_error(&SimpleFile::new(name, &text), &e), } if !matches!( promptly::prompt_default("Continue editing?", true), Ok(true) ) { println!("Aborting"); - return Ok(()); + break None; } - }; + }) +} - files.insert(capture, command); +fn is_task_or_note(command: &CliCommand) -> result::Result<(), &str> { + match command.0 { + Command::Task(_) | Command::Note(_) => Ok(()), + _ => Err("Only TASK and NOTE are allowed"), + } +} + +fn new_command(files: &mut Files, command: Command) -> Result<()> { + let capture = files.capture().ok_or(Error::NoCaptureFile)?; + + let command = edit("new command", format!("{command}"), is_task_or_note)?; + if let Some(command) = command { + files.insert(capture, command.0) + } Ok(()) } + +pub fn task(files: &mut Files, date: Option) -> Result<()> { + let statements = match date { + Some(date) => vec![Statement::Date(Spec::Date(DateSpec { + start: date, + start_delta: None, + start_time: None, + end: None, + end_delta: None, + end_time: None, + repeat: None, + }))], + None => vec![], + }; + let command = Command::Task(Task { + title: String::new(), + statements, + done: vec![], + desc: vec![], + }); + + new_command(files, command) +} + +pub fn note(files: &mut Files, date: Option) -> Result<()> { + let statements = match date { + Some(date) => vec![Statement::Date(Spec::Date(DateSpec { + start: date, + start_delta: None, + start_time: None, + end: None, + end_delta: None, + end_time: None, + repeat: None, + }))], + None => vec![], + }; + let command = Command::Note(Note { + title: String::new(), + statements, + desc: vec![], + }); + + new_command(files, command) +} + +pub fn done(files: &mut Files, date: NaiveDate) -> Result<()> { + let command = Command::Task(Task { + title: String::new(), + statements: vec![], + done: vec![Done { + kind: DoneKind::Done, + date: None, + done_at: date, + }], + desc: vec![], + }); + + new_command(files, command) +}