Support parsing depends:.. in CLI

This commit is contained in:
Dustin J. Mitchell 2022-02-21 22:15:31 +00:00
parent bf73cc4cc7
commit db1e1c9c96
No known key found for this signature in database
4 changed files with 99 additions and 12 deletions

View file

@ -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<DateTime<Utc>>> {
)(input)
}
/// Recognizes `depends:<task>` to `(true, <task>)` and `depends:-<task>` to `(false, <task>)`.
pub(crate) fn depends_colon(input: &str) -> IResult<&str, (bool, Vec<TaskId>)> {
fn to_bool(maybe_minus: Option<char>) -> Result<bool, ()> {
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::*;

View file

@ -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),

View file

@ -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};

View file

@ -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<Status>,
pub(crate) status: Option<Status>,
/// Set (or, with `Some(None)`, clear) the wait timestamp
pub wait: Option<Option<DateTime<Utc>>>,
pub(crate) wait: Option<Option<DateTime<Utc>>>,
/// Set the "active" state, that is, start (true) or stop (false) the task.
pub active: Option<bool>,
pub(crate) active: Option<bool>,
/// Add tags
pub add_tags: HashSet<Tag>,
pub(crate) add_tags: HashSet<Tag>,
/// Remove tags
pub remove_tags: HashSet<Tag>,
pub(crate) remove_tags: HashSet<Tag>,
/// Add dependencies
pub(crate) add_dependencies: HashSet<TaskId>,
/// Remove dependencies
pub(crate) remove_dependencies: HashSet<TaskId>,
/// Add annotation
pub annotate: Option<String>,
pub(crate) annotate: Option<String>,
}
/// 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<DateTime<Utc>>),
AddDependencies(Vec<TaskId>),
RemoveDependencies(Vec<TaskId>),
}
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<ArgList, ModArg> {
fn to_modarg(input: (bool, Vec<TaskId>)) -> Result<ModArg<'static>, ()> {
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:<task-list>",
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:-<task-list>",
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();