mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00
Treat any bare word in the command line as a report name
This commit is contained in:
parent
21684666a6
commit
0a458b5f5b
9 changed files with 177 additions and 101 deletions
|
@ -30,6 +30,11 @@ pub(super) fn any(input: &str) -> IResult<&str, &str> {
|
||||||
rest(input)
|
rest(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recognizes a report name
|
||||||
|
pub(super) fn report_name(input: &str) -> IResult<&str, &str> {
|
||||||
|
all_consuming(recognize(pair(alpha1, alphanumeric0)))(input)
|
||||||
|
}
|
||||||
|
|
||||||
/// Recognizes a literal string
|
/// Recognizes a literal string
|
||||||
pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, &str> {
|
pub(super) fn literal(literal: &'static str) -> impl Fn(&str) -> IResult<&str, &str> {
|
||||||
move |input: &str| all_consuming(nomtag(literal))(input)
|
move |input: &str| all_consuming(nomtag(literal))(input)
|
||||||
|
|
|
@ -15,14 +15,12 @@ mod args;
|
||||||
mod command;
|
mod command;
|
||||||
mod filter;
|
mod filter;
|
||||||
mod modification;
|
mod modification;
|
||||||
mod report;
|
|
||||||
mod subcommand;
|
mod subcommand;
|
||||||
|
|
||||||
pub(crate) use args::TaskId;
|
pub(crate) use args::TaskId;
|
||||||
pub(crate) use command::Command;
|
pub(crate) use command::Command;
|
||||||
pub(crate) use filter::{Condition, Filter, Universe};
|
pub(crate) use filter::{Condition, Filter, Universe};
|
||||||
pub(crate) use modification::{DescriptionMod, Modification};
|
pub(crate) use modification::{DescriptionMod, Modification};
|
||||||
pub(crate) use report::{Column, Property, Report, Sort, SortBy};
|
|
||||||
pub(crate) use subcommand::Subcommand;
|
pub(crate) use subcommand::Subcommand;
|
||||||
|
|
||||||
use crate::usage::Usage;
|
use crate::usage::Usage;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use super::args::*;
|
use super::args::*;
|
||||||
use super::{
|
use super::{ArgList, DescriptionMod, Filter, Modification};
|
||||||
ArgList, Column, DescriptionMod, Filter, Modification, Property, Report, Sort, SortBy,
|
|
||||||
};
|
|
||||||
use crate::usage;
|
use crate::usage;
|
||||||
use nom::{branch::alt, combinator::*, sequence::*, IResult};
|
use nom::{branch::alt, combinator::*, sequence::*, IResult};
|
||||||
use taskchampion::Status;
|
use taskchampion::Status;
|
||||||
|
@ -39,8 +37,12 @@ pub(crate) enum Subcommand {
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Lists (reports)
|
/// Lists (reports)
|
||||||
List {
|
Report {
|
||||||
report: Report,
|
/// The name of the report to show
|
||||||
|
report_name: String,
|
||||||
|
|
||||||
|
/// Additional filter terms beyond those in the report
|
||||||
|
filter: Filter,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Per-task information (typically one task)
|
/// Per-task information (typically one task)
|
||||||
|
@ -56,16 +58,17 @@ pub(crate) enum Subcommand {
|
||||||
|
|
||||||
impl Subcommand {
|
impl Subcommand {
|
||||||
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
||||||
alt((
|
all_consuming(alt((
|
||||||
Version::parse,
|
Version::parse,
|
||||||
Help::parse,
|
Help::parse,
|
||||||
Add::parse,
|
Add::parse,
|
||||||
Modify::parse,
|
Modify::parse,
|
||||||
List::parse,
|
|
||||||
Info::parse,
|
Info::parse,
|
||||||
Gc::parse,
|
Gc::parse,
|
||||||
Sync::parse,
|
Sync::parse,
|
||||||
))(input)
|
// This must come last since it accepts arbitrary report names
|
||||||
|
Report::parse,
|
||||||
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_usage(u: &mut usage::Usage) {
|
pub(super) fn get_usage(u: &mut usage::Usage) {
|
||||||
|
@ -73,10 +76,10 @@ impl Subcommand {
|
||||||
Help::get_usage(u);
|
Help::get_usage(u);
|
||||||
Add::get_usage(u);
|
Add::get_usage(u);
|
||||||
Modify::get_usage(u);
|
Modify::get_usage(u);
|
||||||
List::get_usage(u);
|
|
||||||
Info::get_usage(u);
|
Info::get_usage(u);
|
||||||
Gc::get_usage(u);
|
Gc::get_usage(u);
|
||||||
Sync::get_usage(u);
|
Sync::get_usage(u);
|
||||||
|
Report::get_usage(u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,59 +254,43 @@ impl Modify {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct List;
|
struct Report;
|
||||||
|
|
||||||
impl List {
|
|
||||||
// temporary
|
|
||||||
fn default_report() -> Report {
|
|
||||||
Report {
|
|
||||||
columns: vec![
|
|
||||||
Column {
|
|
||||||
label: "Id".to_owned(),
|
|
||||||
property: Property::Id,
|
|
||||||
},
|
|
||||||
Column {
|
|
||||||
label: "Description".to_owned(),
|
|
||||||
property: Property::Description,
|
|
||||||
},
|
|
||||||
Column {
|
|
||||||
label: "Active".to_owned(),
|
|
||||||
property: Property::Active,
|
|
||||||
},
|
|
||||||
Column {
|
|
||||||
label: "Tags".to_owned(),
|
|
||||||
property: Property::Tags,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
sort: vec![Sort {
|
|
||||||
ascending: false,
|
|
||||||
sort_by: SortBy::Uuid,
|
|
||||||
}],
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl Report {
|
||||||
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
||||||
fn to_subcommand(input: (Filter, &str)) -> Result<Subcommand, ()> {
|
fn to_subcommand(filter: Filter, report_name: &str) -> Result<Subcommand, ()> {
|
||||||
let report = Report {
|
Ok(Subcommand::Report {
|
||||||
filter: input.0,
|
filter,
|
||||||
..List::default_report()
|
report_name: report_name.to_owned(),
|
||||||
};
|
})
|
||||||
Ok(Subcommand::List { report })
|
|
||||||
}
|
}
|
||||||
map_res(
|
// allow the filter expression before or after the report name
|
||||||
pair(Filter::parse, arg_matching(literal("list"))),
|
alt((
|
||||||
to_subcommand,
|
map_res(pair(arg_matching(report_name), Filter::parse), |input| {
|
||||||
)(input)
|
to_subcommand(input.1, input.0)
|
||||||
|
}),
|
||||||
|
map_res(pair(Filter::parse, arg_matching(report_name)), |input| {
|
||||||
|
to_subcommand(input.0, input.1)
|
||||||
|
}),
|
||||||
|
// default to a "next" report
|
||||||
|
map_res(Filter::parse, |input| to_subcommand(input, "next")),
|
||||||
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
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: "list",
|
name: "report",
|
||||||
syntax: "[filter] list",
|
syntax: "[filter] [report-name] *or* [report-name] [filter]",
|
||||||
summary: "List tasks",
|
summary: "Show a report",
|
||||||
description: "
|
description: "
|
||||||
Show a list of the tasks matching the filter",
|
Show the named report, including only tasks matching the filter",
|
||||||
|
});
|
||||||
|
u.subcommands.push(usage::Subcommand {
|
||||||
|
name: "next",
|
||||||
|
syntax: "[filter]",
|
||||||
|
summary: "Show the 'next' report",
|
||||||
|
description: "
|
||||||
|
Show the report named 'next', including only tasks matching the filter",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -634,31 +621,72 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list() {
|
fn test_report() {
|
||||||
let subcommand = Subcommand::List {
|
let subcommand = Subcommand::Report {
|
||||||
report: Report {
|
filter: Default::default(),
|
||||||
..List::default_report()
|
report_name: "myreport".to_owned(),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Subcommand::parse(argv!["list"]).unwrap(),
|
Subcommand::parse(argv!["myreport"]).unwrap(),
|
||||||
(&EMPTY[..], subcommand)
|
(&EMPTY[..], subcommand)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_filter() {
|
fn test_report_filter_before() {
|
||||||
let subcommand = Subcommand::List {
|
let subcommand = Subcommand::Report {
|
||||||
report: Report {
|
filter: Filter {
|
||||||
filter: Filter {
|
universe: Universe::for_ids(vec![12, 13]),
|
||||||
universe: Universe::for_ids(vec![12, 13]),
|
..Default::default()
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..List::default_report()
|
|
||||||
},
|
},
|
||||||
|
report_name: "foo".to_owned(),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Subcommand::parse(argv!["12,13", "list"]).unwrap(),
|
Subcommand::parse(argv!["12,13", "foo"]).unwrap(),
|
||||||
|
(&EMPTY[..], subcommand)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_report_filter_after() {
|
||||||
|
let subcommand = Subcommand::Report {
|
||||||
|
filter: Filter {
|
||||||
|
universe: Universe::for_ids(vec![12, 13]),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
report_name: "foo".to_owned(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
Subcommand::parse(argv!["foo", "12,13"]).unwrap(),
|
||||||
|
(&EMPTY[..], subcommand)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_report_filter_next() {
|
||||||
|
let subcommand = Subcommand::Report {
|
||||||
|
filter: Filter {
|
||||||
|
universe: Universe::for_ids(vec![12, 13]),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
report_name: "next".to_owned(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
Subcommand::parse(argv!["12,13"]).unwrap(),
|
||||||
|
(&EMPTY[..], subcommand)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_report_next() {
|
||||||
|
let subcommand = Subcommand::Report {
|
||||||
|
filter: Filter {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
report_name: "next".to_owned(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
Subcommand::parse(argv![]).unwrap(),
|
||||||
(&EMPTY[..], subcommand)
|
(&EMPTY[..], subcommand)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -704,11 +732,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gc_extra_args() {
|
fn test_gc_extra_args() {
|
||||||
let subcommand = Subcommand::Gc;
|
assert!(Subcommand::parse(argv!["gc", "foo"]).is_err());
|
||||||
assert_eq!(
|
|
||||||
Subcommand::parse(argv!["gc", "foo"]).unwrap(),
|
|
||||||
(&vec!["foo"][..], subcommand)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -4,7 +4,7 @@ pub(crate) mod add;
|
||||||
pub(crate) mod gc;
|
pub(crate) mod gc;
|
||||||
pub(crate) mod help;
|
pub(crate) mod help;
|
||||||
pub(crate) mod info;
|
pub(crate) mod info;
|
||||||
pub(crate) mod list;
|
|
||||||
pub(crate) mod modify;
|
pub(crate) mod modify;
|
||||||
|
pub(crate) mod report;
|
||||||
pub(crate) mod sync;
|
pub(crate) mod sync;
|
||||||
pub(crate) mod version;
|
pub(crate) mod version;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::argparse::Report;
|
use crate::argparse::Filter;
|
||||||
use crate::invocation::display_report;
|
use crate::invocation::display_report;
|
||||||
use failure::Fallible;
|
use failure::Fallible;
|
||||||
use taskchampion::Replica;
|
use taskchampion::Replica;
|
||||||
|
@ -7,35 +7,33 @@ use termcolor::WriteColor;
|
||||||
pub(crate) fn execute<W: WriteColor>(
|
pub(crate) fn execute<W: WriteColor>(
|
||||||
w: &mut W,
|
w: &mut W,
|
||||||
replica: &mut Replica,
|
replica: &mut Replica,
|
||||||
report: Report,
|
report_name: String,
|
||||||
|
filter: Filter,
|
||||||
) -> Fallible<()> {
|
) -> Fallible<()> {
|
||||||
display_report(w, replica, &report)
|
display_report(w, replica, report_name, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::argparse::{Column, Filter, Property};
|
use crate::argparse::Filter;
|
||||||
use crate::invocation::test::*;
|
use crate::invocation::test::*;
|
||||||
use taskchampion::Status;
|
use taskchampion::Status;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list() {
|
fn test_report() {
|
||||||
let mut w = test_writer();
|
let mut w = test_writer();
|
||||||
let mut replica = test_replica();
|
let mut replica = test_replica();
|
||||||
replica.new_task(Status::Pending, s!("my task")).unwrap();
|
replica.new_task(Status::Pending, s!("my task")).unwrap();
|
||||||
|
|
||||||
let report = Report {
|
// The function being tested is only one line long, so this is sort of an integration test
|
||||||
filter: Filter {
|
// for display_report.
|
||||||
..Default::default()
|
|
||||||
},
|
let report_name = "next".to_owned();
|
||||||
columns: vec![Column {
|
let filter = Filter {
|
||||||
label: "Description".to_owned(),
|
|
||||||
property: Property::Description,
|
|
||||||
}],
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
execute(&mut w, &mut replica, report).unwrap();
|
execute(&mut w, &mut replica, report_name, filter).unwrap();
|
||||||
assert!(w.into_string().contains("my task"));
|
assert!(w.into_string().contains("my task"));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -60,9 +60,13 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> {
|
||||||
} => return cmd::modify::execute(&mut w, &mut replica, filter, modification),
|
} => return cmd::modify::execute(&mut w, &mut replica, filter, modification),
|
||||||
|
|
||||||
Command {
|
Command {
|
||||||
subcommand: Subcommand::List { report },
|
subcommand:
|
||||||
|
Subcommand::Report {
|
||||||
|
report_name,
|
||||||
|
filter,
|
||||||
|
},
|
||||||
..
|
..
|
||||||
} => return cmd::list::execute(&mut w, &mut replica, report),
|
} => return cmd::report::execute(&mut w, &mut replica, report_name, filter),
|
||||||
|
|
||||||
Command {
|
Command {
|
||||||
subcommand: Subcommand::Info { filter, debug },
|
subcommand: Subcommand::Info { filter, debug },
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::argparse::{Column, Property, Report, SortBy};
|
use crate::argparse::Filter;
|
||||||
use crate::invocation::filtered_tasks;
|
use crate::invocation::filtered_tasks;
|
||||||
|
use crate::report::{Column, Property, Report, Sort, SortBy};
|
||||||
use crate::table;
|
use crate::table;
|
||||||
use failure::Fallible;
|
use failure::{bail, Fallible};
|
||||||
use prettytable::{Row, Table};
|
use prettytable::{Row, Table};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use taskchampion::{Replica, Task, Uuid};
|
use taskchampion::{Replica, Task, Uuid};
|
||||||
|
@ -101,20 +102,64 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_report(report_name: String, filter: Filter) -> Fallible<Report> {
|
||||||
|
let columns = vec![
|
||||||
|
Column {
|
||||||
|
label: "Id".to_owned(),
|
||||||
|
property: Property::Id,
|
||||||
|
},
|
||||||
|
Column {
|
||||||
|
label: "Description".to_owned(),
|
||||||
|
property: Property::Description,
|
||||||
|
},
|
||||||
|
Column {
|
||||||
|
label: "Active".to_owned(),
|
||||||
|
property: Property::Active,
|
||||||
|
},
|
||||||
|
Column {
|
||||||
|
label: "Tags".to_owned(),
|
||||||
|
property: Property::Tags,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let sort = vec![Sort {
|
||||||
|
ascending: false,
|
||||||
|
sort_by: SortBy::Uuid,
|
||||||
|
}];
|
||||||
|
use crate::argparse::Universe;
|
||||||
|
Ok(match report_name.as_ref() {
|
||||||
|
"list" => Report {
|
||||||
|
columns,
|
||||||
|
sort,
|
||||||
|
filter,
|
||||||
|
},
|
||||||
|
"next" => Report {
|
||||||
|
columns,
|
||||||
|
sort,
|
||||||
|
// TODO: merge invocation filter and report filter
|
||||||
|
filter: Filter {
|
||||||
|
universe: Universe::PendingTasks,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => bail!("Unknown report {:?}", report_name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn display_report<W: WriteColor>(
|
pub(super) fn display_report<W: WriteColor>(
|
||||||
w: &mut W,
|
w: &mut W,
|
||||||
replica: &mut Replica,
|
replica: &mut Replica,
|
||||||
report: &Report,
|
report_name: String,
|
||||||
|
filter: Filter,
|
||||||
) -> Fallible<()> {
|
) -> Fallible<()> {
|
||||||
let mut t = Table::new();
|
let mut t = Table::new();
|
||||||
|
let report = get_report(report_name, filter)?;
|
||||||
let working_set = WorkingSet::new(replica)?;
|
let working_set = WorkingSet::new(replica)?;
|
||||||
|
|
||||||
// Get the tasks from the filter
|
// Get the tasks from the filter
|
||||||
let mut tasks: Vec<_> = filtered_tasks(replica, &report.filter)?.collect();
|
let mut tasks: Vec<_> = filtered_tasks(replica, &report.filter)?.collect();
|
||||||
|
|
||||||
// ..sort them as desired
|
// ..sort them as desired
|
||||||
sort_tasks(&mut tasks, report, &working_set);
|
sort_tasks(&mut tasks, &report, &working_set);
|
||||||
|
|
||||||
// ..set up the column titles
|
// ..set up the column titles
|
||||||
t.set_format(table::format());
|
t.set_format(table::format());
|
||||||
|
@ -138,8 +183,8 @@ pub(super) fn display_report<W: WriteColor>(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::argparse::{Column, Property, Report, Sort, SortBy};
|
|
||||||
use crate::invocation::test::*;
|
use crate::invocation::test::*;
|
||||||
|
use crate::report::Sort;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use taskchampion::Status;
|
use taskchampion::Status;
|
||||||
|
|
||||||
|
@ -371,4 +416,3 @@ mod test {
|
||||||
assert_eq!(task_column(&task, &column, &working_set), s!(""));
|
assert_eq!(task_column(&task, &column, &working_set), s!(""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: test task_column
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ mod macros;
|
||||||
|
|
||||||
mod argparse;
|
mod argparse;
|
||||||
mod invocation;
|
mod invocation;
|
||||||
|
mod report;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod table;
|
mod table;
|
||||||
mod usage;
|
mod usage;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use super::Filter;
|
//! This module contains the data structures used to define reports.
|
||||||
|
|
||||||
|
use crate::argparse::Filter;
|
||||||
|
|
||||||
/// A report specifies a filter as well as a sort order and information about which
|
/// A report specifies a filter as well as a sort order and information about which
|
||||||
/// task attributes to display
|
/// task attributes to display
|
Loading…
Add table
Add a link
Reference in a new issue