diff --git a/Cargo.lock b/Cargo.lock index d5b7e61c9..37d2536bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1630,9 +1630,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "mdbook" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed4060ccf332a0479df37e84c8435ad20be737d5337c3a90fa1b3b0d480a3a0" +checksum = "aeb86d199d0c1e8d41f3a9e9b0ba8639d6951a10043129159809ac9c18e3ce05" dependencies = [ "ammonia", "anyhow", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 05cfcb20c..fca908f70 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -27,7 +27,7 @@ lazy_static = "1" iso8601-duration = "0.1" # only needed for usage-docs -mdbook = { version = "0.4", optional = true } +mdbook = { version = "0.4.9", optional = true } serde_json = { version = "*", optional = true } [dependencies.taskchampion] diff --git a/cli/src/argparse/args/arg_matching.rs b/cli/src/argparse/args/arg_matching.rs index e1161738e..f52aa4254 100644 --- a/cli/src/argparse/args/arg_matching.rs +++ b/cli/src/argparse/args/arg_matching.rs @@ -44,7 +44,7 @@ mod test { fn test_arg_matching() { assert_eq!( arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), - (argv!["bar"], "foo") + (argv!["bar"], tag!("foo")) ); assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); } diff --git a/cli/src/argparse/args/misc.rs b/cli/src/argparse/args/misc.rs index 006a0b939..27d2a1315 100644 --- a/cli/src/argparse/args/misc.rs +++ b/cli/src/argparse/args/misc.rs @@ -25,7 +25,7 @@ mod test { fn test_arg_matching() { assert_eq!( arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), - (argv!["bar"], "foo") + (argv!["bar"], tag!("foo")) ); assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err()); } diff --git a/cli/src/argparse/args/tags.rs b/cli/src/argparse/args/tags.rs index 8c2cbd9c1..c15dae6bf 100644 --- a/cli/src/argparse/args/tags.rs +++ b/cli/src/argparse/args/tags.rs @@ -3,31 +3,13 @@ use std::convert::TryFrom; use taskchampion::Tag; /// Recognizes a tag prefixed with `+` and returns the tag value -pub(crate) fn plus_tag(input: &str) -> IResult<&str, &str> { - fn to_tag(input: (char, &str)) -> Result<&str, ()> { - Ok(input.1) - } - map_res( - all_consuming(tuple(( - char('+'), - recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), - ))), - to_tag, - )(input) +pub(crate) fn plus_tag(input: &str) -> IResult<&str, Tag> { + preceded(char('+'), map_res(rest, Tag::try_from))(input) } /// Recognizes a tag prefixed with `-` and returns the tag value -pub(crate) fn minus_tag(input: &str) -> IResult<&str, &str> { - fn to_tag(input: (char, &str)) -> Result<&str, ()> { - Ok(input.1) - } - map_res( - all_consuming(tuple(( - char('-'), - recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())), - ))), - to_tag, - )(input) +pub(crate) fn minus_tag(input: &str) -> IResult<&str, Tag> { + preceded(char('-'), map_res(rest, Tag::try_from))(input) } #[cfg(test)] @@ -36,21 +18,17 @@ mod test { #[test] fn test_plus_tag() { - assert_eq!(plus_tag("+abc").unwrap().1, "abc"); - assert_eq!(plus_tag("+abc123").unwrap().1, "abc123"); + assert_eq!(plus_tag("+abc").unwrap().1, tag!("abc")); + assert_eq!(plus_tag("+abc123").unwrap().1, tag!("abc123")); assert!(plus_tag("-abc123").is_err()); - assert!(plus_tag("+abc123 ").is_err()); - assert!(plus_tag(" +abc123").is_err()); assert!(plus_tag("+1abc").is_err()); } #[test] fn test_minus_tag() { - assert_eq!(minus_tag("-abc").unwrap().1, "abc"); - assert_eq!(minus_tag("-abc123").unwrap().1, "abc123"); + assert_eq!(minus_tag("-abc").unwrap().1, tag!("abc")); + assert_eq!(minus_tag("-abc123").unwrap().1, tag!("abc123")); assert!(minus_tag("+abc123").is_err()); - assert!(minus_tag("-abc123 ").is_err()); - assert!(minus_tag(" -abc123").is_err()); assert!(minus_tag("-1abc").is_err()); } } diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index be63aa1af..fa2746bb1 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -8,7 +8,7 @@ use nom::{ multi::{fold_many0, fold_many1}, IResult, }; -use taskchampion::Status; +use taskchampion::{Status, Tag}; /// A filter represents a selection of a particular set of tasks. /// @@ -26,10 +26,10 @@ pub(crate) struct Filter { #[derive(Debug, PartialEq, Clone)] pub(crate) enum Condition { /// Task has the given tag - HasTag(String), + HasTag(Tag), /// Task does not have the given tag - NoTag(String), + NoTag(Tag), /// Task has the given status Status(Status), @@ -68,15 +68,15 @@ impl Condition { } fn parse_plus_tag(input: ArgList) -> IResult { - fn to_condition(input: &str) -> Result { - Ok(Condition::HasTag(input.to_owned())) + fn to_condition(input: Tag) -> Result { + Ok(Condition::HasTag(input)) } map_res(arg_matching(plus_tag), to_condition)(input) } fn parse_minus_tag(input: ArgList) -> IResult { - fn to_condition(input: &str) -> Result { - Ok(Condition::NoTag(input.to_owned())) + fn to_condition(input: Tag) -> Result { + Ok(Condition::NoTag(input)) } map_res(arg_matching(minus_tag), to_condition)(input) } @@ -320,8 +320,8 @@ mod test { Filter { conditions: vec![ Condition::IdList(vec![TaskId::WorkingSetId(1),]), - Condition::HasTag("yes".into()), - Condition::NoTag("no".into()), + Condition::HasTag(tag!("yes")), + Condition::NoTag(tag!("no")), ], } ); @@ -353,10 +353,10 @@ mod test { conditions: vec![ // from first filter Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), - Condition::HasTag("yes".into()), + Condition::HasTag(tag!("yes")), // from second filter Condition::IdList(vec![TaskId::WorkingSetId(2), TaskId::WorkingSetId(3)]), - Condition::HasTag("no".into()), + Condition::HasTag(tag!("no")), ], } ); @@ -373,9 +373,9 @@ mod test { conditions: vec![ // from first filter Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), - Condition::HasTag("yes".into()), + Condition::HasTag(tag!("yes")), // from second filter - Condition::HasTag("no".into()), + Condition::HasTag(tag!("no")), ], } ); @@ -390,8 +390,8 @@ mod test { both, Filter { conditions: vec![ - Condition::HasTag("yes".into()), - Condition::HasTag("no".into()), + Condition::HasTag(tag!("yes")), + Condition::HasTag(tag!("no")), ], } ); diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 083f7fc8e..bd37db928 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -4,7 +4,7 @@ use crate::usage; use chrono::prelude::*; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use std::collections::HashSet; -use taskchampion::Status; +use taskchampion::{Status, Tag}; #[derive(Debug, PartialEq, Clone)] pub enum DescriptionMod { @@ -44,17 +44,17 @@ pub struct Modification { pub active: Option, /// Add tags - pub add_tags: HashSet, + pub add_tags: HashSet, /// Remove tags - pub remove_tags: HashSet, + 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), + PlusTag(Tag), + MinusTag(Tag), Wait(Option>), } @@ -71,10 +71,10 @@ impl Modification { } } ModArg::PlusTag(tag) => { - acc.add_tags.insert(tag.to_owned()); + acc.add_tags.insert(tag); } ModArg::MinusTag(tag) => { - acc.remove_tags.insert(tag.to_owned()); + acc.remove_tags.insert(tag); } ModArg::Wait(wait) => { acc.wait = Some(wait); @@ -105,14 +105,14 @@ impl Modification { } fn plus_tag(input: ArgList) -> IResult { - fn to_modarg(input: &str) -> Result { + fn to_modarg(input: Tag) -> 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 { + fn to_modarg(input: Tag) -> Result, ()> { Ok(ModArg::MinusTag(input)) } map_res(arg_matching(minus_tag), to_modarg)(input) @@ -198,7 +198,7 @@ mod test { assert_eq!( modification, Modification { - add_tags: set![s!("abc"), s!("def")], + add_tags: set![tag!("abc"), tag!("def")], ..Default::default() } ); @@ -252,8 +252,8 @@ mod test { modification, Modification { description: DescriptionMod::Set(s!("new desc fun")), - add_tags: set![s!("next")], - remove_tags: set![s!("daytime")], + add_tags: set![tag!("next")], + remove_tags: set![tag!("daytime")], ..Default::default() } ); diff --git a/cli/src/bin/usage-docs.rs b/cli/src/bin/usage-docs.rs index f78179c71..baa4079e1 100644 --- a/cli/src/bin/usage-docs.rs +++ b/cli/src/bin/usage-docs.rs @@ -40,7 +40,10 @@ fn process(_ctx: &PreprocessorContext, mut book: Book) -> Result { if new_content != chapter.content { eprintln!( "Substituting usage in {:?}", - chapter.source_path.as_ref().unwrap() + chapter + .source_path + .as_ref() + .unwrap_or(chapter.path.as_ref().unwrap()) ); } chapter.content = new_content; diff --git a/cli/src/invocation/filter.rs b/cli/src/invocation/filter.rs index 0cc6e31fe..0d4add799 100644 --- a/cli/src/invocation/filter.rs +++ b/cli/src/invocation/filter.rs @@ -1,22 +1,17 @@ use crate::argparse::{Condition, Filter, TaskId}; use std::collections::HashSet; -use std::convert::TryInto; -use taskchampion::{Replica, Status, Tag, Task, Uuid, WorkingSet}; +use taskchampion::{Replica, Status, Task, Uuid, WorkingSet}; fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set: &WorkingSet) -> bool { for cond in &filter.conditions { match cond { Condition::HasTag(ref tag) => { - // see #111 for the unwrap - let tag: Tag = tag.try_into().unwrap(); - if !task.has_tag(&tag) { + if !task.has_tag(tag) { return false; } } Condition::NoTag(ref tag) => { - // see #111 for the unwrap - let tag: Tag = tag.try_into().unwrap(); - if task.has_tag(&tag) { + if task.has_tag(tag) { return false; } } @@ -254,8 +249,8 @@ mod test { #[test] fn tag_filtering() -> anyhow::Result<()> { let mut replica = test_replica(); - let yes: Tag = "yes".try_into()?; - let no: Tag = "no".try_into()?; + let yes = tag!("yes"); + let no = tag!("no"); let mut t1 = replica .new_task(Status::Pending, s!("A"))? @@ -274,7 +269,7 @@ mod test { // look for just "yes" (A and B) let filter = Filter { - conditions: vec![Condition::HasTag(s!("yes"))], + conditions: vec![Condition::HasTag(tag!("yes"))], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) @@ -284,7 +279,7 @@ mod test { // look for tags without "no" (A, D) let filter = Filter { - conditions: vec![Condition::NoTag(s!("no"))], + conditions: vec![Condition::NoTag(tag!("no"))], }; let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) @@ -294,7 +289,10 @@ mod test { // look for tags with "yes" and "no" (B) let filter = Filter { - conditions: vec![Condition::HasTag(s!("yes")), Condition::HasTag(s!("no"))], + conditions: vec![ + Condition::HasTag(tag!("yes")), + Condition::HasTag(tag!("no")), + ], }; let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? .map(|t| t.get_description().to_owned()) diff --git a/cli/src/invocation/modify.rs b/cli/src/invocation/modify.rs index 7ef6d758c..dd943fdd1 100644 --- a/cli/src/invocation/modify.rs +++ b/cli/src/invocation/modify.rs @@ -1,5 +1,4 @@ use crate::argparse::{DescriptionMod, Modification}; -use std::convert::TryInto; use taskchampion::TaskMut; /// Apply the given modification @@ -31,12 +30,10 @@ pub(super) fn apply_modification( } for tag in modification.add_tags.iter() { - let tag = tag.try_into()?; // see #111 task.add_tag(&tag)?; } for tag in modification.remove_tags.iter() { - let tag = tag.try_into()?; // see #111 task.remove_tag(&tag)?; } diff --git a/cli/src/macros.rs b/cli/src/macros.rs index 02e11e5cf..1a3024c13 100644 --- a/cli/src/macros.rs +++ b/cli/src/macros.rs @@ -30,3 +30,9 @@ macro_rules! set( macro_rules! s( { $s:expr } => { $s.to_owned() }; ); + +/// Create a Tag from an &str; just a testing shorthand +#[cfg(test)] +macro_rules! tag( + { $s:expr } => { { use std::convert::TryFrom; taskchampion::Tag::try_from($s).unwrap() } }; +); diff --git a/cli/src/usage.rs b/cli/src/usage.rs index 59a2ba982..b3f688909 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -93,7 +93,7 @@ impl Usage { /// With the appropriate documentation. pub fn substitute_docs(&self, content: &str) -> Result { // this is not efficient, but it doesn't need to be - let mut lines = content.lines(); + let lines = content.lines(); let mut w = String::new(); const DOC_HEADER_PREFIX: &str = "