produce Tag instances in the parser (#260)

and..
* fix usage-docs plugin
* upgrade mdbook
This commit is contained in:
Dustin J. Mitchell 2021-06-04 09:26:12 -04:00 committed by GitHub
parent 5a454a5dfd
commit 5f28eb3a74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 62 additions and 80 deletions

4
Cargo.lock generated
View file

@ -1630,9 +1630,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]] [[package]]
name = "mdbook" name = "mdbook"
version = "0.4.8" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed4060ccf332a0479df37e84c8435ad20be737d5337c3a90fa1b3b0d480a3a0" checksum = "aeb86d199d0c1e8d41f3a9e9b0ba8639d6951a10043129159809ac9c18e3ce05"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"anyhow", "anyhow",

View file

@ -27,7 +27,7 @@ lazy_static = "1"
iso8601-duration = "0.1" iso8601-duration = "0.1"
# only needed for usage-docs # only needed for usage-docs
mdbook = { version = "0.4", optional = true } mdbook = { version = "0.4.9", optional = true }
serde_json = { version = "*", optional = true } serde_json = { version = "*", optional = true }
[dependencies.taskchampion] [dependencies.taskchampion]

View file

@ -44,7 +44,7 @@ mod test {
fn test_arg_matching() { fn test_arg_matching() {
assert_eq!( assert_eq!(
arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), 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()); assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err());
} }

View file

@ -25,7 +25,7 @@ mod test {
fn test_arg_matching() { fn test_arg_matching() {
assert_eq!( assert_eq!(
arg_matching(plus_tag)(argv!["+foo", "bar"]).unwrap(), 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()); assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err());
} }

View file

@ -3,31 +3,13 @@ use std::convert::TryFrom;
use taskchampion::Tag; use taskchampion::Tag;
/// Recognizes a tag prefixed with `+` and returns the tag value /// Recognizes a tag prefixed with `+` and returns the tag value
pub(crate) fn plus_tag(input: &str) -> IResult<&str, &str> { pub(crate) fn plus_tag(input: &str) -> IResult<&str, Tag> {
fn to_tag(input: (char, &str)) -> Result<&str, ()> { preceded(char('+'), map_res(rest, Tag::try_from))(input)
Ok(input.1)
}
map_res(
all_consuming(tuple((
char('+'),
recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())),
))),
to_tag,
)(input)
} }
/// Recognizes a tag prefixed with `-` and returns the tag value /// Recognizes a tag prefixed with `-` and returns the tag value
pub(crate) fn minus_tag(input: &str) -> IResult<&str, &str> { pub(crate) fn minus_tag(input: &str) -> IResult<&str, Tag> {
fn to_tag(input: (char, &str)) -> Result<&str, ()> { preceded(char('-'), map_res(rest, Tag::try_from))(input)
Ok(input.1)
}
map_res(
all_consuming(tuple((
char('-'),
recognize(verify(rest, |s: &str| Tag::try_from(s).is_ok())),
))),
to_tag,
)(input)
} }
#[cfg(test)] #[cfg(test)]
@ -36,21 +18,17 @@ mod test {
#[test] #[test]
fn test_plus_tag() { fn test_plus_tag() {
assert_eq!(plus_tag("+abc").unwrap().1, "abc"); assert_eq!(plus_tag("+abc").unwrap().1, tag!("abc"));
assert_eq!(plus_tag("+abc123").unwrap().1, "abc123"); 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(" +abc123").is_err());
assert!(plus_tag("+1abc").is_err()); assert!(plus_tag("+1abc").is_err());
} }
#[test] #[test]
fn test_minus_tag() { fn test_minus_tag() {
assert_eq!(minus_tag("-abc").unwrap().1, "abc"); assert_eq!(minus_tag("-abc").unwrap().1, tag!("abc"));
assert_eq!(minus_tag("-abc123").unwrap().1, "abc123"); 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(" -abc123").is_err());
assert!(minus_tag("-1abc").is_err()); assert!(minus_tag("-1abc").is_err());
} }
} }

View file

@ -8,7 +8,7 @@ use nom::{
multi::{fold_many0, fold_many1}, multi::{fold_many0, fold_many1},
IResult, IResult,
}; };
use taskchampion::Status; use taskchampion::{Status, Tag};
/// A filter represents a selection of a particular set of tasks. /// A filter represents a selection of a particular set of tasks.
/// ///
@ -26,10 +26,10 @@ pub(crate) struct Filter {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(crate) enum Condition { pub(crate) enum Condition {
/// Task has the given tag /// Task has the given tag
HasTag(String), HasTag(Tag),
/// Task does not have the given tag /// Task does not have the given tag
NoTag(String), NoTag(Tag),
/// Task has the given status /// Task has the given status
Status(Status), Status(Status),
@ -68,15 +68,15 @@ impl Condition {
} }
fn parse_plus_tag(input: ArgList) -> IResult<ArgList, Condition> { fn parse_plus_tag(input: ArgList) -> IResult<ArgList, Condition> {
fn to_condition(input: &str) -> Result<Condition, ()> { fn to_condition(input: Tag) -> Result<Condition, ()> {
Ok(Condition::HasTag(input.to_owned())) Ok(Condition::HasTag(input))
} }
map_res(arg_matching(plus_tag), to_condition)(input) map_res(arg_matching(plus_tag), to_condition)(input)
} }
fn parse_minus_tag(input: ArgList) -> IResult<ArgList, Condition> { fn parse_minus_tag(input: ArgList) -> IResult<ArgList, Condition> {
fn to_condition(input: &str) -> Result<Condition, ()> { fn to_condition(input: Tag) -> Result<Condition, ()> {
Ok(Condition::NoTag(input.to_owned())) Ok(Condition::NoTag(input))
} }
map_res(arg_matching(minus_tag), to_condition)(input) map_res(arg_matching(minus_tag), to_condition)(input)
} }
@ -320,8 +320,8 @@ mod test {
Filter { Filter {
conditions: vec![ conditions: vec![
Condition::IdList(vec![TaskId::WorkingSetId(1),]), Condition::IdList(vec![TaskId::WorkingSetId(1),]),
Condition::HasTag("yes".into()), Condition::HasTag(tag!("yes")),
Condition::NoTag("no".into()), Condition::NoTag(tag!("no")),
], ],
} }
); );
@ -353,10 +353,10 @@ mod test {
conditions: vec![ conditions: vec![
// from first filter // from first filter
Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]),
Condition::HasTag("yes".into()), Condition::HasTag(tag!("yes")),
// from second filter // from second filter
Condition::IdList(vec![TaskId::WorkingSetId(2), TaskId::WorkingSetId(3)]), 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![ conditions: vec![
// from first filter // from first filter
Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]), Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]),
Condition::HasTag("yes".into()), Condition::HasTag(tag!("yes")),
// from second filter // from second filter
Condition::HasTag("no".into()), Condition::HasTag(tag!("no")),
], ],
} }
); );
@ -390,8 +390,8 @@ mod test {
both, both,
Filter { Filter {
conditions: vec![ conditions: vec![
Condition::HasTag("yes".into()), Condition::HasTag(tag!("yes")),
Condition::HasTag("no".into()), Condition::HasTag(tag!("no")),
], ],
} }
); );

View file

@ -4,7 +4,7 @@ use crate::usage;
use chrono::prelude::*; use chrono::prelude::*;
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
use std::collections::HashSet; use std::collections::HashSet;
use taskchampion::Status; use taskchampion::{Status, Tag};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum DescriptionMod { pub enum DescriptionMod {
@ -44,17 +44,17 @@ pub struct Modification {
pub active: Option<bool>, pub active: Option<bool>,
/// Add tags /// Add tags
pub add_tags: HashSet<String>, pub add_tags: HashSet<Tag>,
/// Remove tags /// Remove tags
pub remove_tags: HashSet<String>, pub remove_tags: HashSet<Tag>,
} }
/// A single argument that is part of a modification, used internally to this module /// A single argument that is part of a modification, used internally to this module
enum ModArg<'a> { enum ModArg<'a> {
Description(&'a str), Description(&'a str),
PlusTag(&'a str), PlusTag(Tag),
MinusTag(&'a str), MinusTag(Tag),
Wait(Option<DateTime<Utc>>), Wait(Option<DateTime<Utc>>),
} }
@ -71,10 +71,10 @@ impl Modification {
} }
} }
ModArg::PlusTag(tag) => { ModArg::PlusTag(tag) => {
acc.add_tags.insert(tag.to_owned()); acc.add_tags.insert(tag);
} }
ModArg::MinusTag(tag) => { ModArg::MinusTag(tag) => {
acc.remove_tags.insert(tag.to_owned()); acc.remove_tags.insert(tag);
} }
ModArg::Wait(wait) => { ModArg::Wait(wait) => {
acc.wait = Some(wait); acc.wait = Some(wait);
@ -105,14 +105,14 @@ impl Modification {
} }
fn plus_tag(input: ArgList) -> IResult<ArgList, ModArg> { fn plus_tag(input: ArgList) -> IResult<ArgList, ModArg> {
fn to_modarg(input: &str) -> Result<ModArg, ()> { fn to_modarg(input: Tag) -> Result<ModArg<'static>, ()> {
Ok(ModArg::PlusTag(input)) Ok(ModArg::PlusTag(input))
} }
map_res(arg_matching(plus_tag), to_modarg)(input) map_res(arg_matching(plus_tag), to_modarg)(input)
} }
fn minus_tag(input: ArgList) -> IResult<ArgList, ModArg> { fn minus_tag(input: ArgList) -> IResult<ArgList, ModArg> {
fn to_modarg(input: &str) -> Result<ModArg, ()> { fn to_modarg(input: Tag) -> Result<ModArg<'static>, ()> {
Ok(ModArg::MinusTag(input)) Ok(ModArg::MinusTag(input))
} }
map_res(arg_matching(minus_tag), to_modarg)(input) map_res(arg_matching(minus_tag), to_modarg)(input)
@ -198,7 +198,7 @@ mod test {
assert_eq!( assert_eq!(
modification, modification,
Modification { Modification {
add_tags: set![s!("abc"), s!("def")], add_tags: set![tag!("abc"), tag!("def")],
..Default::default() ..Default::default()
} }
); );
@ -252,8 +252,8 @@ mod test {
modification, modification,
Modification { Modification {
description: DescriptionMod::Set(s!("new desc fun")), description: DescriptionMod::Set(s!("new desc fun")),
add_tags: set![s!("next")], add_tags: set![tag!("next")],
remove_tags: set![s!("daytime")], remove_tags: set![tag!("daytime")],
..Default::default() ..Default::default()
} }
); );

View file

@ -40,7 +40,10 @@ fn process(_ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
if new_content != chapter.content { if new_content != chapter.content {
eprintln!( eprintln!(
"Substituting usage in {:?}", "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; chapter.content = new_content;

View file

@ -1,22 +1,17 @@
use crate::argparse::{Condition, Filter, TaskId}; use crate::argparse::{Condition, Filter, TaskId};
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryInto; use taskchampion::{Replica, Status, Task, Uuid, WorkingSet};
use taskchampion::{Replica, Status, Tag, Task, Uuid, WorkingSet};
fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set: &WorkingSet) -> bool { fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set: &WorkingSet) -> bool {
for cond in &filter.conditions { for cond in &filter.conditions {
match cond { match cond {
Condition::HasTag(ref tag) => { Condition::HasTag(ref tag) => {
// see #111 for the unwrap if !task.has_tag(tag) {
let tag: Tag = tag.try_into().unwrap();
if !task.has_tag(&tag) {
return false; return false;
} }
} }
Condition::NoTag(ref tag) => { Condition::NoTag(ref tag) => {
// see #111 for the unwrap if task.has_tag(tag) {
let tag: Tag = tag.try_into().unwrap();
if task.has_tag(&tag) {
return false; return false;
} }
} }
@ -254,8 +249,8 @@ mod test {
#[test] #[test]
fn tag_filtering() -> anyhow::Result<()> { fn tag_filtering() -> anyhow::Result<()> {
let mut replica = test_replica(); let mut replica = test_replica();
let yes: Tag = "yes".try_into()?; let yes = tag!("yes");
let no: Tag = "no".try_into()?; let no = tag!("no");
let mut t1 = replica let mut t1 = replica
.new_task(Status::Pending, s!("A"))? .new_task(Status::Pending, s!("A"))?
@ -274,7 +269,7 @@ mod test {
// look for just "yes" (A and B) // look for just "yes" (A and B)
let filter = Filter { let filter = Filter {
conditions: vec![Condition::HasTag(s!("yes"))], conditions: vec![Condition::HasTag(tag!("yes"))],
}; };
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)?
.map(|t| t.get_description().to_owned()) .map(|t| t.get_description().to_owned())
@ -284,7 +279,7 @@ mod test {
// look for tags without "no" (A, D) // look for tags without "no" (A, D)
let filter = Filter { let filter = Filter {
conditions: vec![Condition::NoTag(s!("no"))], conditions: vec![Condition::NoTag(tag!("no"))],
}; };
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)?
.map(|t| t.get_description().to_owned()) .map(|t| t.get_description().to_owned())
@ -294,7 +289,10 @@ mod test {
// look for tags with "yes" and "no" (B) // look for tags with "yes" and "no" (B)
let filter = Filter { 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)? let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)?
.map(|t| t.get_description().to_owned()) .map(|t| t.get_description().to_owned())

View file

@ -1,5 +1,4 @@
use crate::argparse::{DescriptionMod, Modification}; use crate::argparse::{DescriptionMod, Modification};
use std::convert::TryInto;
use taskchampion::TaskMut; use taskchampion::TaskMut;
/// Apply the given modification /// Apply the given modification
@ -31,12 +30,10 @@ pub(super) fn apply_modification(
} }
for tag in modification.add_tags.iter() { for tag in modification.add_tags.iter() {
let tag = tag.try_into()?; // see #111
task.add_tag(&tag)?; task.add_tag(&tag)?;
} }
for tag in modification.remove_tags.iter() { for tag in modification.remove_tags.iter() {
let tag = tag.try_into()?; // see #111
task.remove_tag(&tag)?; task.remove_tag(&tag)?;
} }

View file

@ -30,3 +30,9 @@ macro_rules! set(
macro_rules! s( macro_rules! s(
{ $s:expr } => { $s.to_owned() }; { $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() } };
);

View file

@ -93,7 +93,7 @@ impl Usage {
/// With the appropriate documentation. /// With the appropriate documentation.
pub fn substitute_docs(&self, content: &str) -> Result<String> { pub fn substitute_docs(&self, content: &str) -> Result<String> {
// this is not efficient, but it doesn't need to be // 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(); let mut w = String::new();
const DOC_HEADER_PREFIX: &str = "<!-- INSERT GENERATED DOCUMENTATION - "; const DOC_HEADER_PREFIX: &str = "<!-- INSERT GENERATED DOCUMENTATION - ";