mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Merge pull request #221 from taskchampion/issue177
Require a filter be specified for modifications
This commit is contained in:
commit
9d78654573
2 changed files with 109 additions and 34 deletions
|
@ -1,8 +1,13 @@
|
||||||
use super::args::{arg_matching, id_list, minus_tag, plus_tag, status_colon, TaskId};
|
use super::args::{arg_matching, id_list, literal, minus_tag, plus_tag, status_colon, TaskId};
|
||||||
use super::ArgList;
|
use super::ArgList;
|
||||||
use crate::usage;
|
use crate::usage;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
|
use nom::{
|
||||||
|
branch::alt,
|
||||||
|
combinator::*,
|
||||||
|
multi::{fold_many0, fold_many1},
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
use taskchampion::Status;
|
use taskchampion::Status;
|
||||||
|
|
||||||
/// A filter represents a selection of a particular set of tasks.
|
/// A filter represents a selection of a particular set of tasks.
|
||||||
|
@ -85,7 +90,9 @@ impl Condition {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Filter> {
|
/// Parse a filter that can include an empty set of args (meaning
|
||||||
|
/// all tasks)
|
||||||
|
pub(super) fn parse0(input: ArgList) -> IResult<ArgList, Filter> {
|
||||||
fold_many0(
|
fold_many0(
|
||||||
Condition::parse,
|
Condition::parse,
|
||||||
Filter {
|
Filter {
|
||||||
|
@ -95,6 +102,30 @@ impl Filter {
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a filter that must have at least one arg, which can be `all`
|
||||||
|
/// to mean all tasks
|
||||||
|
pub(super) fn parse1(input: ArgList) -> IResult<ArgList, Filter> {
|
||||||
|
alt((
|
||||||
|
Filter::parse_all,
|
||||||
|
fold_many1(
|
||||||
|
Condition::parse,
|
||||||
|
Filter {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
|acc, arg| acc.with_arg(arg),
|
||||||
|
),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_all(input: ArgList) -> IResult<ArgList, Filter> {
|
||||||
|
fn to_filter(_: &str) -> Result<Filter, ()> {
|
||||||
|
Ok(Filter {
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
map_res(arg_matching(literal("all")), to_filter)(input)
|
||||||
|
}
|
||||||
|
|
||||||
/// fold multiple filter args into a single Filter instance
|
/// fold multiple filter args into a single Filter instance
|
||||||
fn with_arg(mut self, cond: Condition) -> Filter {
|
fn with_arg(mut self, cond: Condition) -> Filter {
|
||||||
if let Condition::IdList(mut id_list) = cond {
|
if let Condition::IdList(mut id_list) = cond {
|
||||||
|
@ -157,6 +188,13 @@ impl Filter {
|
||||||
description: "
|
description: "
|
||||||
Select tasks with the given status.",
|
Select tasks with the given status.",
|
||||||
});
|
});
|
||||||
|
u.filters.push(usage::Filter {
|
||||||
|
syntax: "all",
|
||||||
|
summary: "All tasks",
|
||||||
|
description: "
|
||||||
|
When specified alone for task-modification commands, `all` matches all tasks.
|
||||||
|
For example, `task all done` will mark all tasks as done.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,8 +203,8 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty() {
|
fn test_empty_parse0() {
|
||||||
let (input, filter) = Filter::parse(argv![]).unwrap();
|
let (input, filter) = Filter::parse0(argv![]).unwrap();
|
||||||
assert_eq!(input.len(), 0);
|
assert_eq!(input.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
filter,
|
filter,
|
||||||
|
@ -176,9 +214,46 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_parse1() {
|
||||||
|
// parse1 does not allow empty input
|
||||||
|
assert!(Filter::parse1(argv![]).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_parse0() {
|
||||||
|
let (input, _) = Filter::parse0(argv!["all"]).unwrap();
|
||||||
|
assert_eq!(input.len(), 1); // did not parse "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_parse1() {
|
||||||
|
let (input, filter) = Filter::parse1(argv!["all"]).unwrap();
|
||||||
|
assert_eq!(input.len(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
filter,
|
||||||
|
Filter {
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_with_other_stuff() {
|
||||||
|
let (input, filter) = Filter::parse1(argv!["all", "+foo"]).unwrap();
|
||||||
|
// filter ends after `all`
|
||||||
|
assert_eq!(input.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
filter,
|
||||||
|
Filter {
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_id_list_single() {
|
fn test_id_list_single() {
|
||||||
let (input, filter) = Filter::parse(argv!["1"]).unwrap();
|
let (input, filter) = Filter::parse0(argv!["1"]).unwrap();
|
||||||
assert_eq!(input.len(), 0);
|
assert_eq!(input.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
filter,
|
filter,
|
||||||
|
@ -190,7 +265,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_id_list_commas() {
|
fn test_id_list_commas() {
|
||||||
let (input, filter) = Filter::parse(argv!["1,2,3"]).unwrap();
|
let (input, filter) = Filter::parse0(argv!["1,2,3"]).unwrap();
|
||||||
assert_eq!(input.len(), 0);
|
assert_eq!(input.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
filter,
|
filter,
|
||||||
|
@ -206,7 +281,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_id_list_multi_arg() {
|
fn test_id_list_multi_arg() {
|
||||||
let (input, filter) = Filter::parse(argv!["1,2", "3,4"]).unwrap();
|
let (input, filter) = Filter::parse0(argv!["1,2", "3,4"]).unwrap();
|
||||||
assert_eq!(input.len(), 0);
|
assert_eq!(input.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
filter,
|
filter,
|
||||||
|
@ -223,7 +298,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_id_list_uuids() {
|
fn test_id_list_uuids() {
|
||||||
let (input, filter) = Filter::parse(argv!["1,abcd1234"]).unwrap();
|
let (input, filter) = Filter::parse0(argv!["1,abcd1234"]).unwrap();
|
||||||
assert_eq!(input.len(), 0);
|
assert_eq!(input.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
filter,
|
filter,
|
||||||
|
@ -238,7 +313,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tags() {
|
fn test_tags() {
|
||||||
let (input, filter) = Filter::parse(argv!["1", "+yes", "-no"]).unwrap();
|
let (input, filter) = Filter::parse0(argv!["1", "+yes", "-no"]).unwrap();
|
||||||
assert_eq!(input.len(), 0);
|
assert_eq!(input.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
filter,
|
filter,
|
||||||
|
@ -254,7 +329,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_status() {
|
fn test_status() {
|
||||||
let (input, filter) = Filter::parse(argv!["status:completed", "status:pending"]).unwrap();
|
let (input, filter) = Filter::parse0(argv!["status:completed", "status:pending"]).unwrap();
|
||||||
assert_eq!(input.len(), 0);
|
assert_eq!(input.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
filter,
|
filter,
|
||||||
|
@ -269,8 +344,8 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn intersect_idlist_idlist() {
|
fn intersect_idlist_idlist() {
|
||||||
let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1;
|
let left = Filter::parse0(argv!["1,2", "+yes"]).unwrap().1;
|
||||||
let right = Filter::parse(argv!["2,3", "+no"]).unwrap().1;
|
let right = Filter::parse0(argv!["2,3", "+no"]).unwrap().1;
|
||||||
let both = left.intersect(right);
|
let both = left.intersect(right);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
both,
|
both,
|
||||||
|
@ -289,8 +364,8 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn intersect_idlist_alltasks() {
|
fn intersect_idlist_alltasks() {
|
||||||
let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1;
|
let left = Filter::parse0(argv!["1,2", "+yes"]).unwrap().1;
|
||||||
let right = Filter::parse(argv!["+no"]).unwrap().1;
|
let right = Filter::parse0(argv!["+no"]).unwrap().1;
|
||||||
let both = left.intersect(right);
|
let both = left.intersect(right);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
both,
|
both,
|
||||||
|
@ -308,8 +383,8 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn intersect_alltasks_alltasks() {
|
fn intersect_alltasks_alltasks() {
|
||||||
let left = Filter::parse(argv!["+yes"]).unwrap().1;
|
let left = Filter::parse0(argv!["+yes"]).unwrap().1;
|
||||||
let right = Filter::parse(argv!["+no"]).unwrap().1;
|
let right = Filter::parse0(argv!["+no"]).unwrap().1;
|
||||||
let both = left.intersect(right);
|
let both = left.intersect(right);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
both,
|
both,
|
||||||
|
|
|
@ -217,7 +217,7 @@ impl Modify {
|
||||||
}
|
}
|
||||||
map_res(
|
map_res(
|
||||||
tuple((
|
tuple((
|
||||||
Filter::parse,
|
Filter::parse1,
|
||||||
alt((
|
alt((
|
||||||
arg_matching(literal("modify")),
|
arg_matching(literal("modify")),
|
||||||
arg_matching(literal("prepend")),
|
arg_matching(literal("prepend")),
|
||||||
|
@ -235,47 +235,47 @@ impl Modify {
|
||||||
fn get_usage(u: &mut usage::Usage) {
|
fn get_usage(u: &mut usage::Usage) {
|
||||||
u.subcommands.push(usage::Subcommand {
|
u.subcommands.push(usage::Subcommand {
|
||||||
name: "modify",
|
name: "modify",
|
||||||
syntax: "[filter] modify [modification]",
|
syntax: "<filter> modify [modification]",
|
||||||
summary: "Modify tasks",
|
summary: "Modify tasks",
|
||||||
description: "
|
description: "
|
||||||
Modify all tasks matching the filter.",
|
Modify all tasks matching the required filter.",
|
||||||
});
|
});
|
||||||
u.subcommands.push(usage::Subcommand {
|
u.subcommands.push(usage::Subcommand {
|
||||||
name: "prepend",
|
name: "prepend",
|
||||||
syntax: "[filter] prepend [modification]",
|
syntax: "<filter> prepend [modification]",
|
||||||
summary: "Prepend task description",
|
summary: "Prepend task description",
|
||||||
description: "
|
description: "
|
||||||
Modify all tasks matching the filter by inserting the given description before each
|
Modify all tasks matching the required filter by inserting the given description before each
|
||||||
task's description.",
|
task's description.",
|
||||||
});
|
});
|
||||||
u.subcommands.push(usage::Subcommand {
|
u.subcommands.push(usage::Subcommand {
|
||||||
name: "append",
|
name: "append",
|
||||||
syntax: "[filter] append [modification]",
|
syntax: "<filter> append [modification]",
|
||||||
summary: "Append task description",
|
summary: "Append task description",
|
||||||
description: "
|
description: "
|
||||||
Modify all tasks matching the filter by adding the given description to the end
|
Modify all tasks matching the required filter by adding the given description to the end
|
||||||
of each task's description.",
|
of each task's description.",
|
||||||
});
|
});
|
||||||
u.subcommands.push(usage::Subcommand {
|
u.subcommands.push(usage::Subcommand {
|
||||||
name: "start",
|
name: "start",
|
||||||
syntax: "[filter] start [modification]",
|
syntax: "<filter> start [modification]",
|
||||||
summary: "Start tasks",
|
summary: "Start tasks",
|
||||||
description: "
|
description: "
|
||||||
Start all tasks matching the filter, additionally applying any given modifications."
|
Start all tasks matching the required filter, additionally applying any given modifications."
|
||||||
});
|
});
|
||||||
u.subcommands.push(usage::Subcommand {
|
u.subcommands.push(usage::Subcommand {
|
||||||
name: "stop",
|
name: "stop",
|
||||||
syntax: "[filter] stop [modification]",
|
syntax: "<filter> stop [modification]",
|
||||||
summary: "Stop tasks",
|
summary: "Stop tasks",
|
||||||
description: "
|
description: "
|
||||||
Stop all tasks matching the filter, additionally applying any given modifications.",
|
Stop all tasks matching the required filter, additionally applying any given modifications.",
|
||||||
});
|
});
|
||||||
u.subcommands.push(usage::Subcommand {
|
u.subcommands.push(usage::Subcommand {
|
||||||
name: "done",
|
name: "done",
|
||||||
syntax: "[filter] done [modification]",
|
syntax: "<filter> done [modification]",
|
||||||
summary: "Mark tasks as completed",
|
summary: "Mark tasks as completed",
|
||||||
description: "
|
description: "
|
||||||
Mark all tasks matching the filter as completed, additionally applying any given
|
Mark all tasks matching the required filter as completed, additionally applying any given
|
||||||
modifications.",
|
modifications.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -293,14 +293,14 @@ impl Report {
|
||||||
}
|
}
|
||||||
// allow the filter expression before or after the report name
|
// allow the filter expression before or after the report name
|
||||||
alt((
|
alt((
|
||||||
map_res(pair(arg_matching(report_name), Filter::parse), |input| {
|
map_res(pair(arg_matching(report_name), Filter::parse0), |input| {
|
||||||
to_subcommand(input.1, input.0)
|
to_subcommand(input.1, input.0)
|
||||||
}),
|
}),
|
||||||
map_res(pair(Filter::parse, arg_matching(report_name)), |input| {
|
map_res(pair(Filter::parse0, arg_matching(report_name)), |input| {
|
||||||
to_subcommand(input.0, input.1)
|
to_subcommand(input.0, input.1)
|
||||||
}),
|
}),
|
||||||
// default to a "next" report
|
// default to a "next" report
|
||||||
map_res(Filter::parse, |input| to_subcommand(input, "next")),
|
map_res(Filter::parse0, |input| to_subcommand(input, "next")),
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +335,7 @@ impl Info {
|
||||||
}
|
}
|
||||||
map_res(
|
map_res(
|
||||||
pair(
|
pair(
|
||||||
Filter::parse,
|
Filter::parse1,
|
||||||
alt((
|
alt((
|
||||||
arg_matching(literal("info")),
|
arg_matching(literal("info")),
|
||||||
arg_matching(literal("debug")),
|
arg_matching(literal("debug")),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue