Allow tasks to be canceled

This commit is contained in:
Joscha 2021-12-31 23:57:40 +01:00
parent e0cb1c8f23
commit 0e4ef7fef3
13 changed files with 107 additions and 14 deletions

View file

@ -13,6 +13,7 @@ use crate::files::{self, Files};
use self::error::Result; use self::error::Result;
use self::layout::line::LineLayout; use self::layout::line::LineLayout;
mod cancel;
mod done; mod done;
mod error; mod error;
mod layout; mod layout;
@ -49,6 +50,12 @@ pub enum Command {
#[structopt(required = true)] #[structopt(required = true)]
entries: Vec<usize>, entries: Vec<usize>,
}, },
/// Marks one or more entries as canceled
Cancel {
/// Entries to mark as done
#[structopt(required = true)]
entries: Vec<usize>,
},
/// Reformat all loaded files /// Reformat all loaded files
Fmt, Fmt,
} }
@ -107,6 +114,14 @@ fn run_command(opt: &Opt, files: &mut Files, range: DateRange, now: NaiveDateTim
let layout = find_layout(files, &entries, range, now); let layout = find_layout(files, &entries, range, now);
print::print(&layout); print::print(&layout);
} }
Some(Command::Cancel { entries: ns }) => {
let entries = find_entries(files, range)?;
let layout = find_layout(files, &entries, range, now);
cancel::cancel(files, &entries, &layout, ns, now)?;
let entries = find_entries(files, range)?;
let layout = find_layout(files, &entries, range, now);
print::print(&layout);
}
Some(Command::Fmt) => files.mark_all_dirty(), Some(Command::Fmt) => files.mark_all_dirty(),
} }
Ok(()) Ok(())

37
src/cli/cancel.rs Normal file
View file

@ -0,0 +1,37 @@
use std::vec;
use chrono::NaiveDateTime;
use crate::eval::Entry;
use crate::files::commands::{Done, DoneKind};
use crate::files::Files;
use super::error::{Error, Result};
use super::layout::line::LineLayout;
pub fn cancel(
files: &mut Files,
entries: &[Entry],
layout: &LineLayout,
numbers: &[usize],
now: NaiveDateTime,
) -> Result<()> {
let mut not_tasks = vec![];
for &number in numbers {
let entry = &entries[layout.look_up_number(number)?];
let done = Done {
kind: DoneKind::Canceled,
date: entry.dates.map(|dates| dates.into()),
done_at: now.date(),
};
if !files.add_done(entry.source, done) {
not_tasks.push(number);
}
}
if not_tasks.is_empty() {
Ok(())
} else {
Err(Error::NotATask(not_tasks))
}
}

View file

@ -3,7 +3,7 @@ use std::vec;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use crate::eval::Entry; use crate::eval::Entry;
use crate::files::commands::Done; use crate::files::commands::{Done, DoneKind};
use crate::files::Files; use crate::files::Files;
use super::error::{Error, Result}; use super::error::{Error, Result};
@ -20,6 +20,7 @@ pub fn done(
for &number in numbers { for &number in numbers {
let entry = &entries[layout.look_up_number(number)?]; let entry = &entries[layout.look_up_number(number)?];
let done = Done { let done = Done {
kind: DoneKind::Done,
date: entry.dates.map(|dates| dates.into()), date: entry.dates.map(|dates| dates.into()),
done_at: now.date(), done_at: now.date(),
}; };

View file

@ -73,7 +73,9 @@ impl DayLayout {
fn layout_entry(&mut self, index: usize, entry: &Entry) { fn layout_entry(&mut self, index: usize, entry: &Entry) {
match entry.kind { match entry.kind {
EntryKind::Task => self.layout_task(index, entry), EntryKind::Task => self.layout_task(index, entry),
EntryKind::TaskDone(at) => self.layout_task_done(index, entry, at), EntryKind::TaskDone(at) | EntryKind::TaskCanceled(at) => {
self.layout_task_done(index, entry, at)
}
EntryKind::Note | EntryKind::Birthday(_) => self.layout_note(index, entry), EntryKind::Note | EntryKind::Birthday(_) => self.layout_note(index, entry),
} }
} }
@ -208,7 +210,7 @@ impl DayLayout {
// 3. // 3.
entries.sort_by_key(|(_, e, _)| match e.kind { entries.sort_by_key(|(_, e, _)| match e.kind {
EntryKind::Task => 0, EntryKind::Task => 0,
EntryKind::TaskDone(_) => 1, EntryKind::TaskDone(_) | EntryKind::TaskCanceled(_) => 1,
EntryKind::Birthday(_) => 2, EntryKind::Birthday(_) => 2,
EntryKind::Note => 3, EntryKind::Note => 3,
}); });

View file

@ -60,6 +60,7 @@ pub enum Times {
pub enum LineKind { pub enum LineKind {
Task, Task,
Done, Done,
Canceled,
Note, Note,
Birthday, Birthday,
} }
@ -245,6 +246,7 @@ impl LineLayout {
match entry.kind { match entry.kind {
EntryKind::Task => LineKind::Task, EntryKind::Task => LineKind::Task,
EntryKind::TaskDone(_) => LineKind::Done, EntryKind::TaskDone(_) => LineKind::Done,
EntryKind::TaskCanceled(_) => LineKind::Canceled,
EntryKind::Note => LineKind::Note, EntryKind::Note => LineKind::Note,
EntryKind::Birthday(_) => LineKind::Birthday, EntryKind::Birthday(_) => LineKind::Birthday,
} }

View file

@ -134,6 +134,7 @@ impl ShowLines {
match kind { match kind {
LineKind::Task => "T".magenta().bold(), LineKind::Task => "T".magenta().bold(),
LineKind::Done => "D".green().bold(), LineKind::Done => "D".green().bold(),
LineKind::Canceled => "C".red().bold(),
LineKind::Note => "N".blue().bold(), LineKind::Note => "N".blue().bold(),
LineKind::Birthday => "B".yellow().bold(), LineKind::Birthday => "B".yellow().bold(),
} }

View file

@ -13,6 +13,10 @@ fn show_entry(files: &Files, entry: &Entry) {
println!("DONE {}", command.title()); println!("DONE {}", command.title());
println!("DONE AT {}", when); println!("DONE AT {}", when);
} }
EntryKind::TaskCanceled(when) => {
println!("CANCELED {}", command.title());
println!("CANCELED AT {}", when);
}
EntryKind::Note => println!("NOTE {}", command.title()), EntryKind::Note => println!("NOTE {}", command.title()),
EntryKind::Birthday(Some(age)) => { EntryKind::Birthday(Some(age)) => {
println!("BIRTHDAY {}", command.title()); println!("BIRTHDAY {}", command.title());

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use chrono::{Duration, NaiveDate}; use chrono::{Duration, NaiveDate};
use crate::files::commands::{ use crate::files::commands::{
self, BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task, self, BirthdaySpec, Command, Done, DoneDate, DoneKind, Note, Spec, Statement, Task,
}; };
use crate::files::primitives::{Span, Spanned, Time}; use crate::files::primitives::{Span, Spanned, Time};
use crate::files::SourcedCommand; use crate::files::SourcedCommand;
@ -296,10 +296,12 @@ impl<'a> CommandState<'a> {
} }
fn eval_done(&mut self, done: &Done) -> Result<()> { fn eval_done(&mut self, done: &Done) -> Result<()> {
self.add_forced(self.entry_with_remind( let kind = match done.kind {
EntryKind::TaskDone(done.done_at), DoneKind::Done => EntryKind::TaskDone(done.done_at),
done.date.map(|date| date.into()), DoneKind::Canceled => EntryKind::TaskCanceled(done.done_at),
)?); };
let dates = done.date.map(|date| date.into());
self.add_forced(self.entry_with_remind(kind, dates)?);
Ok(()) Ok(())
} }
} }

View file

@ -9,6 +9,7 @@ use super::range::DateRange;
pub enum EntryKind { pub enum EntryKind {
Task, Task,
TaskDone(NaiveDate), TaskDone(NaiveDate),
TaskCanceled(NaiveDate),
Note, Note,
Birthday(Option<i32>), Birthday(Option<i32>),
} }
@ -120,7 +121,7 @@ impl Entries {
} }
// Tasks that were finished inside the range // Tasks that were finished inside the range
if let EntryKind::TaskDone(done) = entry.kind { if let EntryKind::TaskDone(done) | EntryKind::TaskCanceled(done) = entry.kind {
if self.range.contains(done) { if self.range.contains(done) {
return true; return true;
} }

View file

@ -309,8 +309,15 @@ impl DoneDate {
} }
} }
#[derive(Debug)]
pub enum DoneKind {
Done,
Canceled,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Done { pub struct Done {
pub kind: DoneKind,
pub date: Option<DoneDate>, pub date: Option<DoneDate>,
pub done_at: NaiveDate, pub done_at: NaiveDate,
} }

View file

@ -2,6 +2,8 @@ use std::fmt;
use chrono::Datelike; use chrono::Datelike;
use crate::files::commands::DoneKind;
use super::commands::{ use super::commands::{
BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, FormulaSpec, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, FormulaSpec,
Note, Repeat, Spec, Statement, Task, Var, WeekdaySpec, Note, Repeat, Spec, Statement, Task, Var, WeekdaySpec,
@ -258,7 +260,11 @@ impl fmt::Display for DoneDate {
impl fmt::Display for Done { impl fmt::Display for Done {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DONE [{}]", self.done_at)?; let kind = match self.kind {
DoneKind::Done => "DONE",
DoneKind::Canceled => "CANCELED",
};
write!(f, "{} [{}]", kind, self.done_at)?;
if let Some(date) = &self.date { if let Some(date) = &self.date {
write!(f, " {}", date)?; write!(f, " {}", date)?;
} }

View file

@ -118,7 +118,8 @@ donedate = {
| datum ~ "--" ~ datum | datum ~ "--" ~ datum
| datum | datum
} }
done = !{ "DONE" ~ "[" ~ datum ~ "]" ~ donedate? ~ eol } done_kind = { "DONE" | "CANCELED" }
done = !{ done_kind ~ "[" ~ datum ~ "]" ~ donedate? ~ eol }
dones = { done* } dones = { done* }
desc_line = { "#" ~ (" " ~ rest_any)? ~ eol } desc_line = { "#" ~ (" " ~ rest_any)? ~ eol }

View file

@ -8,8 +8,8 @@ use pest::prec_climber::{Assoc, Operator, PrecClimber};
use pest::{Parser, Span}; use pest::{Parser, Span};
use super::commands::{ use super::commands::{
BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, FormulaSpec, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, DoneKind, Expr, File,
Note, Repeat, Spec, Statement, Task, Var, WeekdaySpec, FormulaSpec, Note, Repeat, Spec, Statement, Task, Var, WeekdaySpec,
}; };
use super::primitives::{Spanned, Time, Weekday}; use super::primitives::{Spanned, Time, Weekday};
@ -704,10 +704,20 @@ fn parse_donedate(p: Pair<'_, Rule>) -> Result<DoneDate> {
}) })
} }
fn parse_done_kind(p: Pair<'_, Rule>) -> DoneKind {
assert_eq!(p.as_rule(), Rule::done_kind);
match p.as_str() {
"DONE" => DoneKind::Done,
"CANCELED" => DoneKind::Canceled,
_ => unreachable!(),
}
}
fn parse_done(p: Pair<'_, Rule>) -> Result<Done> { fn parse_done(p: Pair<'_, Rule>) -> Result<Done> {
assert_eq!(p.as_rule(), Rule::done); assert_eq!(p.as_rule(), Rule::done);
let mut p = p.into_inner(); let mut p = p.into_inner();
let kind = parse_done_kind(p.next().unwrap());
let done_at = parse_datum(p.next().unwrap())?.value; let done_at = parse_datum(p.next().unwrap())?.value;
let date = if let Some(p) = p.next() { let date = if let Some(p) = p.next() {
Some(parse_donedate(p)?) Some(parse_donedate(p)?)
@ -717,7 +727,11 @@ fn parse_done(p: Pair<'_, Rule>) -> Result<Done> {
assert_eq!(p.next(), None); assert_eq!(p.next(), None);
Ok(Done { date, done_at }) Ok(Done {
kind,
date,
done_at,
})
} }
fn parse_dones(p: Pair<'_, Rule>) -> Result<Vec<Done>> { fn parse_dones(p: Pair<'_, Rule>) -> Result<Vec<Done>> {