Allow filtering by status

This commit is contained in:
Dustin J. Mitchell 2020-12-30 00:51:29 +00:00
parent 83d8fc3b4e
commit b7c12eec1e
2 changed files with 91 additions and 13 deletions

View file

@ -10,7 +10,7 @@ use nom::{
sequence::*,
Err, IResult,
};
use taskchampion::Uuid;
use taskchampion::{Status, Uuid};
/// A task identifier, as given in a filter command-line expression
#[derive(Debug, PartialEq, Clone)]
@ -40,6 +40,32 @@ pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, &
move |input: &str| all_consuming(nomtag(literal))(input)
}
/// Recognizes a colon-prefixed pair
pub(super) fn colon_prefixed(prefix: &'static str) -> impl Fn(&str) -> IResult<&str, &str> {
fn to_suffix<'a>(input: (&'a str, char, &'a str)) -> Result<&'a str, ()> {
Ok(input.2)
}
move |input: &str| {
map_res(
all_consuming(tuple((nomtag(prefix), char(':'), any))),
to_suffix,
)(input)
}
}
/// Recognizes `status:{pending,completed,deleted}`
pub(super) fn status_colon(input: &str) -> IResult<&str, Status> {
fn to_status(input: &str) -> Result<Status, ()> {
match input {
"pending" => Ok(Status::Pending),
"completed" => Ok(Status::Completed),
"deleted" => Ok(Status::Deleted),
_ => Err(()),
}
}
map_res(colon_prefixed("status"), to_status)(input)
}
/// Recognizes a comma-separated list of TaskIds
pub(super) fn id_list(input: &str) -> IResult<&str, Vec<TaskId>> {
fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> {
@ -164,6 +190,26 @@ mod test {
assert!(arg_matching(plus_tag)(argv!["foo", "bar"]).is_err());
}
#[test]
fn test_colon_prefixed() {
assert_eq!(colon_prefixed("foo")("foo:abc").unwrap().1, "abc");
assert_eq!(colon_prefixed("foo")("foo:").unwrap().1, "");
assert!(colon_prefixed("foo")("foo").is_err());
}
#[test]
fn test_status_colon() {
assert_eq!(status_colon("status:pending").unwrap().1, Status::Pending);
assert_eq!(
status_colon("status:completed").unwrap().1,
Status::Completed
);
assert_eq!(status_colon("status:deleted").unwrap().1, Status::Deleted);
assert!(status_colon("status:foo").is_err());
assert!(status_colon("status:complete").is_err());
assert!(status_colon("status").is_err());
}
#[test]
fn test_plus_tag() {
assert_eq!(plus_tag("+abc").unwrap().1, "abc");

View file

@ -1,4 +1,4 @@
use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId};
use super::args::{arg_matching, id_list, minus_tag, plus_tag, status_colon, TaskId};
use super::ArgList;
use crate::usage;
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
@ -25,7 +25,6 @@ pub(crate) enum Condition {
/// Task does not have the given tag
NoTag(String),
// TODO: add command-line syntax for this
/// Task has the given status
Status(Status),
@ -36,7 +35,12 @@ pub(crate) enum Condition {
impl Filter {
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Filter> {
fold_many0(
alt((Self::id_list, Self::plus_tag, Self::minus_tag)),
alt((
Self::parse_id_list,
Self::parse_plus_tag,
Self::parse_minus_tag,
Self::parse_status,
)),
Filter {
..Default::default()
},
@ -78,25 +82,32 @@ impl Filter {
// parsers
fn id_list(input: ArgList) -> IResult<ArgList, Condition> {
fn to_filterarg(input: Vec<TaskId>) -> Result<Condition, ()> {
fn parse_id_list(input: ArgList) -> IResult<ArgList, Condition> {
fn to_condition(input: Vec<TaskId>) -> Result<Condition, ()> {
Ok(Condition::IdList(input))
}
map_res(arg_matching(id_list), to_filterarg)(input)
map_res(arg_matching(id_list), to_condition)(input)
}
fn plus_tag(input: ArgList) -> IResult<ArgList, Condition> {
fn to_filterarg(input: &str) -> Result<Condition, ()> {
fn parse_plus_tag(input: ArgList) -> IResult<ArgList, Condition> {
fn to_condition(input: &str) -> Result<Condition, ()> {
Ok(Condition::HasTag(input.to_owned()))
}
map_res(arg_matching(plus_tag), to_filterarg)(input)
map_res(arg_matching(plus_tag), to_condition)(input)
}
fn minus_tag(input: ArgList) -> IResult<ArgList, Condition> {
fn to_filterarg(input: &str) -> Result<Condition, ()> {
fn parse_minus_tag(input: ArgList) -> IResult<ArgList, Condition> {
fn to_condition(input: &str) -> Result<Condition, ()> {
Ok(Condition::NoTag(input.to_owned()))
}
map_res(arg_matching(minus_tag), to_filterarg)(input)
map_res(arg_matching(minus_tag), to_condition)(input)
}
fn parse_status(input: ArgList) -> IResult<ArgList, Condition> {
fn to_condition(input: Status) -> Result<Condition, ()> {
Ok(Condition::Status(input))
}
map_res(arg_matching(status_colon), to_condition)(input)
}
// usage
@ -123,6 +134,12 @@ impl Filter {
description: "
Select tasks that do not have the given tag.",
});
u.filters.push(usage::Filter {
syntax: "status:pending, status:completed, status:deleted",
summary: "Task status",
description: "
Select tasks with the given status.",
});
}
}
@ -218,6 +235,21 @@ mod test {
);
}
#[test]
fn test_status() {
let (input, filter) = Filter::parse(argv!["status:completed", "status:pending"]).unwrap();
assert_eq!(input.len(), 0);
assert_eq!(
filter,
Filter {
conditions: vec![
Condition::Status(Status::Completed),
Condition::Status(Status::Pending),
],
}
);
}
#[test]
fn intersect_idlist_idlist() {
let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1;