diff --git a/cli/src/argparse/args.rs b/cli/src/argparse/args.rs index c59a4d730..d0fb9cc2d 100644 --- a/cli/src/argparse/args.rs +++ b/cli/src/argparse/args.rs @@ -58,7 +58,6 @@ pub(super) fn id_list(input: &str) -> IResult<&str, Vec<&str>> { } /// Recognizes a tag prefixed with `+` and returns the tag value -#[allow(dead_code)] // tags not implemented yet pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> { fn to_tag(input: (char, &str)) -> Result<&str, ()> { Ok(input.1) @@ -70,7 +69,6 @@ pub(super) fn plus_tag(input: &str) -> IResult<&str, &str> { } /// Recognizes a tag prefixed with `-` and returns the tag value -#[allow(dead_code)] // tags not implemented yet pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> { fn to_tag(input: (char, &str)) -> Result<&str, ()> { Ok(input.1) @@ -81,18 +79,6 @@ pub(super) fn minus_tag(input: &str) -> IResult<&str, &str> { )(input) } -/// Recognizes a tag prefixed with either `-` or `+`, returning true for + and false for - -#[allow(dead_code)] // tags not implemented yet -pub(super) fn tag(input: &str) -> IResult<&str, (bool, &str)> { - fn to_plus(input: &str) -> Result<(bool, &str), ()> { - Ok((true, input)) - } - fn to_minus(input: &str) -> Result<(bool, &str), ()> { - Ok((false, input)) - } - alt((map_res(plus_tag, to_plus), map_res(minus_tag, to_minus)))(input) -} - /// Consume a single argument from an argument list that matches the given string parser (one /// of the other functions in this module). The given parser must consume the entire input. pub(super) fn arg_matching<'a, O, F>(f: F) -> impl Fn(ArgList<'a>) -> IResult @@ -131,14 +117,10 @@ mod test { #[test] fn test_arg_matching() { assert_eq!( - arg_matching(tag)(argv!["+foo", "bar"]).unwrap(), - (argv!["bar"], (true, "foo")) + arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), + (argv!["bar"], "foo") ); - assert_eq!( - arg_matching(tag)(argv!["-foo", "bar"]).unwrap(), - (argv!["bar"], (false, "foo")) - ); - assert!(arg_matching(tag)(argv!["foo", "bar"]).is_err()); + assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); } #[test] @@ -161,16 +143,6 @@ mod test { assert!(minus_tag("-1abc").is_err()); } - #[test] - fn test_tag() { - assert_eq!(tag("-abc").unwrap().1, (false, "abc")); - assert_eq!(tag("+abc123").unwrap().1, (true, "abc123")); - assert!(tag("+abc123 --").is_err()); - assert!(tag("-abc123 ").is_err()); - assert!(tag(" -abc123").is_err()); - assert!(tag("-1abc").is_err()); - } - #[test] fn test_literal() { assert_eq!(literal("list")("list").unwrap().1, "list"); diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 6778ba30b..892e2ff5b 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -1,6 +1,7 @@ -use super::args::{any, arg_matching}; +use super::args::{any, arg_matching, minus_tag, plus_tag}; use super::ArgList; -use nom::{combinator::*, multi::fold_many0, IResult}; +use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; +use std::collections::HashSet; use taskchampion::Status; #[derive(Debug, PartialEq, Clone)] @@ -34,13 +35,21 @@ pub struct Modification { /// Set the status pub status: Option, - /// Set the "active" status, that is, start (true) or stop (false) the task. + /// Set the "active" state, that is, start (true) or stop (false) the task. pub active: Option, + + /// Add tags + pub add_tags: HashSet, + + /// Remove tags + pub remove_tags: HashSet, } /// A single argument that is part of a modification, used internally to this module enum ModArg<'a> { Description(&'a str), + PlusTag(&'a str), + MinusTag(&'a str), } impl Modification { @@ -55,11 +64,22 @@ impl Modification { acc.description = DescriptionMod::Set(description.to_string()); } } + ModArg::PlusTag(tag) => { + acc.add_tags.insert(tag.to_owned()); + } + ModArg::MinusTag(tag) => { + acc.remove_tags.insert(tag.to_owned()); + } } acc } fold_many0( - Self::description, + alt(( + Self::plus_tag, + Self::minus_tag, + // this must come last + Self::description, + )), Modification { ..Default::default() }, @@ -73,6 +93,20 @@ impl Modification { } map_res(arg_matching(any), to_modarg)(input) } + + fn plus_tag(input: ArgList) -> IResult { + fn to_modarg(input: &str) -> Result { + Ok(ModArg::PlusTag(input)) + } + map_res(arg_matching(plus_tag), to_modarg)(input) + } + + fn minus_tag(input: ArgList) -> IResult { + fn to_modarg(input: &str) -> Result { + Ok(ModArg::MinusTag(input)) + } + map_res(arg_matching(minus_tag), to_modarg)(input) + } } #[cfg(test)] @@ -104,6 +138,19 @@ mod test { ); } + #[test] + fn test_add_tags() { + let (input, modification) = Modification::parse(argv!["+abc", "+def"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + add_tags: set!["abc".to_owned(), "def".to_owned()], + ..Default::default() + } + ); + } + #[test] fn test_multi_arg_description() { let (input, modification) = Modification::parse(argv!["new", "desc", "fun"]).unwrap(); @@ -116,4 +163,20 @@ mod test { } ); } + + #[test] + fn test_multi_arg_description_and_tags() { + let (input, modification) = + Modification::parse(argv!["new", "+next", "desc", "-daytime", "fun"]).unwrap(); + assert_eq!(input.len(), 0); + assert_eq!( + modification, + Modification { + description: DescriptionMod::Set("new desc fun".to_owned()), + add_tags: set!["next".to_owned()], + remove_tags: set!["daytime".to_owned()], + ..Default::default() + } + ); + } } diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 29802dbab..26e190e2c 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,5 +1,6 @@ use crate::argparse::{DescriptionMod, Modification}; use failure::Fallible; +use std::convert::TryInto; use taskchampion::TaskMut; use termcolor::WriteColor; @@ -32,6 +33,18 @@ pub(super) fn apply_modification( task.stop()?; } + for tag in modification.add_tags.iter() { + // note that the parser should have already ensured that this tag was valid + let tag = tag.try_into()?; + task.add_tag(&tag)?; + } + + for tag in modification.remove_tags.iter() { + // note that the parser should have already ensured that this tag was valid + let tag = tag.try_into()?; + task.remove_tag(&tag)?; + } + write!(w, "modified task {}\n", task.get_uuid())?; Ok(()) diff --git a/cli/src/macros.rs b/cli/src/macros.rs index 284a2f426..83a347dd0 100644 --- a/cli/src/macros.rs +++ b/cli/src/macros.rs @@ -10,3 +10,17 @@ macro_rules! argv { &[$($x),*][..] ); } + +/// Create a hashset, similar to vec! +#[cfg(test)] +macro_rules! set( + { $($key:expr),+ } => { + { + let mut s = ::std::collections::HashSet::new(); + $( + s.insert($key); + )+ + s + } + }; +);