Allow filtering by status
This commit is contained in:
@@ -10,7 +10,7 @@ use nom::{
|
|||||||
sequence::*,
|
sequence::*,
|
||||||
Err, IResult,
|
Err, IResult,
|
||||||
};
|
};
|
||||||
use taskchampion::Uuid;
|
use taskchampion::{Status, Uuid};
|
||||||
|
|
||||||
/// A task identifier, as given in a filter command-line expression
|
/// A task identifier, as given in a filter command-line expression
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[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)
|
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
|
/// Recognizes a comma-separated list of TaskIds
|
||||||
pub(super) fn id_list(input: &str) -> IResult<&str, Vec<TaskId>> {
|
pub(super) fn id_list(input: &str) -> IResult<&str, Vec<TaskId>> {
|
||||||
fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> {
|
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());
|
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]
|
#[test]
|
||||||
fn test_plus_tag() {
|
fn test_plus_tag() {
|
||||||
assert_eq!(plus_tag("+abc").unwrap().1, "abc");
|
assert_eq!(plus_tag("+abc").unwrap().1, "abc");
|
||||||
|
|||||||
@@ -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 super::ArgList;
|
||||||
use crate::usage;
|
use crate::usage;
|
||||||
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
|
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
|
||||||
@@ -25,7 +25,6 @@ pub(crate) enum Condition {
|
|||||||
/// Task does not have the given tag
|
/// Task does not have the given tag
|
||||||
NoTag(String),
|
NoTag(String),
|
||||||
|
|
||||||
// TODO: add command-line syntax for this
|
|
||||||
/// Task has the given status
|
/// Task has the given status
|
||||||
Status(Status),
|
Status(Status),
|
||||||
|
|
||||||
@@ -36,7 +35,12 @@ pub(crate) enum Condition {
|
|||||||
impl Filter {
|
impl Filter {
|
||||||
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Filter> {
|
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Filter> {
|
||||||
fold_many0(
|
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 {
|
Filter {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -78,25 +82,32 @@ impl Filter {
|
|||||||
|
|
||||||
// parsers
|
// parsers
|
||||||
|
|
||||||
fn id_list(input: ArgList) -> IResult<ArgList, Condition> {
|
fn parse_id_list(input: ArgList) -> IResult<ArgList, Condition> {
|
||||||
fn to_filterarg(input: Vec<TaskId>) -> Result<Condition, ()> {
|
fn to_condition(input: Vec<TaskId>) -> Result<Condition, ()> {
|
||||||
Ok(Condition::IdList(input))
|
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 parse_plus_tag(input: ArgList) -> IResult<ArgList, Condition> {
|
||||||
fn to_filterarg(input: &str) -> Result<Condition, ()> {
|
fn to_condition(input: &str) -> Result<Condition, ()> {
|
||||||
Ok(Condition::HasTag(input.to_owned()))
|
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 parse_minus_tag(input: ArgList) -> IResult<ArgList, Condition> {
|
||||||
fn to_filterarg(input: &str) -> Result<Condition, ()> {
|
fn to_condition(input: &str) -> Result<Condition, ()> {
|
||||||
Ok(Condition::NoTag(input.to_owned()))
|
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
|
// usage
|
||||||
@@ -123,6 +134,12 @@ impl Filter {
|
|||||||
description: "
|
description: "
|
||||||
Select tasks that do not have the given tag.",
|
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]
|
#[test]
|
||||||
fn intersect_idlist_idlist() {
|
fn intersect_idlist_idlist() {
|
||||||
let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1;
|
let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1;
|
||||||
|
|||||||
Reference in New Issue
Block a user