From db1e1c9c96a593461baf53745c1f0af7271d83fd Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 21 Feb 2022 22:15:31 +0000 Subject: [PATCH] Support parsing depends:.. in CLI --- cli/src/argparse/args/colon.rs | 13 ++++- cli/src/argparse/args/idlist.rs | 2 +- cli/src/argparse/args/mod.rs | 2 +- cli/src/argparse/modification.rs | 94 +++++++++++++++++++++++++++++--- 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/cli/src/argparse/args/colon.rs b/cli/src/argparse/args/colon.rs index a0eec90f8..bca014081 100644 --- a/cli/src/argparse/args/colon.rs +++ b/cli/src/argparse/args/colon.rs @@ -1,4 +1,4 @@ -use super::{any, timestamp}; +use super::{any, id_list, timestamp, TaskId}; use crate::argparse::NOW; use nom::bytes::complete::tag as nomtag; use nom::{branch::*, character::complete::*, combinator::*, sequence::*, IResult}; @@ -48,6 +48,17 @@ pub(crate) fn wait_colon(input: &str) -> IResult<&str, Option>> { )(input) } +/// Recognizes `depends:` to `(true, )` and `depends:-` to `(false, )`. +pub(crate) fn depends_colon(input: &str) -> IResult<&str, (bool, Vec)> { + fn to_bool(maybe_minus: Option) -> Result { + Ok(maybe_minus.is_none()) // None -> true, Some -> false + } + preceded( + nomtag("depends:"), + pair(map_res(opt(char('-')), to_bool), id_list), + )(input) +} + #[cfg(test)] mod test { use super::*; diff --git a/cli/src/argparse/args/idlist.rs b/cli/src/argparse/args/idlist.rs index 095dd1cee..a7ea71e0e 100644 --- a/cli/src/argparse/args/idlist.rs +++ b/cli/src/argparse/args/idlist.rs @@ -2,7 +2,7 @@ use nom::{branch::*, character::complete::*, combinator::*, multi::*, sequence:: use taskchampion::Uuid; /// A task identifier, as given in a filter command-line expression -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) enum TaskId { /// A small integer identifying a working-set task WorkingSetId(usize), diff --git a/cli/src/argparse/args/mod.rs b/cli/src/argparse/args/mod.rs index f7124fa29..b9c20e9cd 100644 --- a/cli/src/argparse/args/mod.rs +++ b/cli/src/argparse/args/mod.rs @@ -8,7 +8,7 @@ mod tags; mod time; pub(crate) use arg_matching::arg_matching; -pub(crate) use colon::{status_colon, wait_colon}; +pub(crate) use colon::{depends_colon, status_colon, wait_colon}; pub(crate) use idlist::{id_list, TaskId}; pub(crate) use misc::{any, literal, report_name}; pub(crate) use tags::{minus_tag, plus_tag}; diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index ed597b2e0..21ddeef8e 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -1,4 +1,4 @@ -use super::args::{any, arg_matching, minus_tag, plus_tag, wait_colon}; +use super::args::{any, arg_matching, depends_colon, minus_tag, plus_tag, wait_colon, TaskId}; use super::ArgList; use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; @@ -30,27 +30,33 @@ impl Default for DescriptionMod { /// A modification represents a change to a task: adding or removing tags, setting the /// description, and so on. #[derive(Debug, PartialEq, Clone, Default)] -pub struct Modification { +pub(crate) struct Modification { /// Change the description - pub description: DescriptionMod, + pub(crate) description: DescriptionMod, /// Set the status - pub status: Option, + pub(crate) status: Option, /// Set (or, with `Some(None)`, clear) the wait timestamp - pub wait: Option>>, + pub(crate) wait: Option>>, /// Set the "active" state, that is, start (true) or stop (false) the task. - pub active: Option, + pub(crate) active: Option, /// Add tags - pub add_tags: HashSet, + pub(crate) add_tags: HashSet, /// Remove tags - pub remove_tags: HashSet, + pub(crate) remove_tags: HashSet, + + /// Add dependencies + pub(crate) add_dependencies: HashSet, + + /// Remove dependencies + pub(crate) remove_dependencies: HashSet, /// Add annotation - pub annotate: Option, + pub(crate) annotate: Option, } /// A single argument that is part of a modification, used internally to this module @@ -59,6 +65,8 @@ enum ModArg<'a> { PlusTag(Tag), MinusTag(Tag), Wait(Option>), + AddDependencies(Vec), + RemoveDependencies(Vec), } impl Modification { @@ -82,6 +90,16 @@ impl Modification { ModArg::Wait(wait) => { acc.wait = Some(wait); } + ModArg::AddDependencies(task_ids) => { + for tid in task_ids { + acc.add_dependencies.insert(tid); + } + } + ModArg::RemoveDependencies(task_ids) => { + for tid in task_ids { + acc.remove_dependencies.insert(tid); + } + } } acc } @@ -90,6 +108,7 @@ impl Modification { Self::plus_tag, Self::minus_tag, Self::wait, + Self::dependencies, // this must come last Self::description, )), @@ -128,6 +147,17 @@ impl Modification { map_res(arg_matching(wait_colon), to_modarg)(input) } + fn dependencies(input: ArgList) -> IResult { + fn to_modarg(input: (bool, Vec)) -> Result, ()> { + Ok(if input.0 { + ModArg::AddDependencies(input.1) + } else { + ModArg::RemoveDependencies(input.1) + }) + } + map_res(arg_matching(depends_colon), to_modarg)(input) + } + pub(super) fn get_usage(u: &mut usage::Usage) { u.modifications.push(usage::Modification { syntax: "DESCRIPTION", @@ -161,6 +191,19 @@ impl Modification { reports, e.g., `wait:3day` to wait for three days. With `wait:`, the time is un-set. See the documentation for the timestamp syntax.", }); + u.modifications.push(usage::Modification { + syntax: "depends:", + summary: "Add task dependencies", + description: " + Add a dependency of this task on the given tasks. The tasks can be specified + in the same syntax as for filters, e.g., `depends:13,94500c95`.", + }); + u.modifications.push(usage::Modification { + syntax: "depends:-", + summary: "Remove task dependencies", + description: " + Remove the dependency of this task on the given tasks.", + }); } } @@ -222,6 +265,39 @@ mod test { ); } + #[test] + fn test_add_deps() { + let (input, modification) = Modification::parse(argv!["depends:13,e72b73d1-9e88"]).unwrap(); + assert_eq!(input.len(), 0); + let mut deps = HashSet::new(); + deps.insert(TaskId::WorkingSetId(13)); + deps.insert(TaskId::PartialUuid("e72b73d1-9e88".into())); + assert_eq!( + modification, + Modification { + add_dependencies: deps, + ..Default::default() + } + ); + } + + #[test] + fn test_remove_deps() { + let (input, modification) = + Modification::parse(argv!["depends:-13,e72b73d1-9e88"]).unwrap(); + assert_eq!(input.len(), 0); + let mut deps = HashSet::new(); + deps.insert(TaskId::WorkingSetId(13)); + deps.insert(TaskId::PartialUuid("e72b73d1-9e88".into())); + assert_eq!( + modification, + Modification { + remove_dependencies: deps, + ..Default::default() + } + ); + } + #[test] fn test_unset_wait() { let (input, modification) = Modification::parse(argv!["wait:"]).unwrap();