Limit Filter "universes" to invocation::filter

Universes are really an optimization of filtering tasks, so let's define
them there, and derive them from the set of conditions.  This means that
complex filters might get missed and end up doing a full task scan, but
that's probably OK.

Note that this does not fix the working-set API issues (#108 and #123).
This commit is contained in:
Dustin J. Mitchell
2020-12-30 00:23:18 +00:00
parent 0a458b5f5b
commit fc977a0fe6
5 changed files with 236 additions and 147 deletions

View File

@@ -2,6 +2,7 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, 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};
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.
/// ///
@@ -10,40 +11,11 @@ use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
/// pending tasks, or all tasks. /// pending tasks, or all tasks.
#[derive(Debug, PartialEq, Default, Clone)] #[derive(Debug, PartialEq, Default, Clone)]
pub(crate) struct Filter { pub(crate) struct Filter {
/// The universe of tasks from which this filter can select
pub(crate) universe: Universe,
/// A set of filter conditions, all of which must match a task in order for that task to be /// A set of filter conditions, all of which must match a task in order for that task to be
/// selected. /// selected.
pub(crate) conditions: Vec<Condition>, pub(crate) conditions: Vec<Condition>,
} }
/// The universe of tasks over which a filter should be applied.
#[derive(Debug, PartialEq, Clone)]
pub(crate) enum Universe {
/// Only the identified tasks. Note that this may contain duplicates.
IdList(Vec<TaskId>),
/// All tasks in the task database
AllTasks,
/// Only pending tasks (or as an approximation, the working set)
#[allow(dead_code)] // currently only used in tests
PendingTasks,
}
impl Universe {
/// Testing shorthand to construct a simple universe
#[cfg(test)]
pub(super) fn for_ids(mut ids: Vec<usize>) -> Self {
Universe::IdList(ids.drain(..).map(|id| TaskId::WorkingSetId(id)).collect())
}
}
impl Default for Universe {
fn default() -> Self {
Self::AllTasks
}
}
/// A condition which tasks must match to be accepted by the filter. /// A condition which tasks must match to be accepted by the filter.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(crate) enum Condition { pub(crate) enum Condition {
@@ -52,10 +24,18 @@ 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
Status(Status),
/// Task has one of the given IDs
IdList(Vec<TaskId>),
} }
/// Internal struct representing a parsed filter argument /// Internal struct representing a parsed filter argument
enum FilterArg { enum FilterArg {
// TODO: get rid of this whole enum
IdList(Vec<TaskId>), IdList(Vec<TaskId>),
Condition(Condition), Condition(Condition),
} }
@@ -67,30 +47,46 @@ impl Filter {
Filter { Filter {
..Default::default() ..Default::default()
}, },
Self::fold_args, |acc, arg| acc.with_arg(arg),
)(input) )(input)
} }
/// fold multiple filter args into a single Filter instance /// fold multiple filter args into a single Filter instance
fn fold_args(mut acc: Filter, mod_arg: FilterArg) -> Filter { fn with_arg(mut self, mod_arg: FilterArg) -> Filter {
match mod_arg { match mod_arg {
FilterArg::IdList(mut id_list) => { FilterArg::IdList(mut id_list) => {
// If any IDs are specified, then the filter's universe // If there is already an IdList condition, concatenate this one
// is those IDs. If there are already IDs, append to the // to it. Thus multiple IdList command-line args represent an OR
// list. // operation. This assumes that the filter is still being built
if let Universe::IdList(ref mut existing) = acc.universe { // from command-line arguments and thus has at most one IdList
// condition.
if let Some(Condition::IdList(existing)) = self
.conditions
.iter_mut()
.find(|c| matches!(c, Condition::IdList(_)))
{
existing.append(&mut id_list); existing.append(&mut id_list);
} else { } else {
acc.universe = Universe::IdList(id_list); self.conditions.push(Condition::IdList(id_list));
} }
} }
FilterArg::Condition(cond) => { FilterArg::Condition(cond) => {
acc.conditions.push(cond); self.conditions.push(cond);
} }
} }
acc self
} }
/// combine this filter with another filter in an AND operation
pub(crate) fn intersect(mut self, mut other: Filter) -> Filter {
// simply concatenate the conditions
self.conditions.append(&mut other.conditions);
self
}
// parsers
fn id_list(input: ArgList) -> IResult<ArgList, FilterArg> { fn id_list(input: ArgList) -> IResult<ArgList, FilterArg> {
fn to_filterarg(input: Vec<TaskId>) -> Result<FilterArg, ()> { fn to_filterarg(input: Vec<TaskId>) -> Result<FilterArg, ()> {
Ok(FilterArg::IdList(input)) Ok(FilterArg::IdList(input))
@@ -112,6 +108,8 @@ impl Filter {
map_res(arg_matching(minus_tag), to_filterarg)(input) map_res(arg_matching(minus_tag), to_filterarg)(input)
} }
// usage
pub(super) fn get_usage(u: &mut usage::Usage) { pub(super) fn get_usage(u: &mut usage::Usage) {
u.filters.push(usage::Filter { u.filters.push(usage::Filter {
syntax: "TASKID[,TASKID,..]", syntax: "TASKID[,TASKID,..]",
@@ -160,8 +158,7 @@ mod test {
assert_eq!( assert_eq!(
filter, filter,
Filter { Filter {
universe: Universe::IdList(vec![TaskId::WorkingSetId(1)]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(1)])],
..Default::default()
} }
); );
} }
@@ -173,12 +170,11 @@ mod test {
assert_eq!( assert_eq!(
filter, filter,
Filter { Filter {
universe: Universe::IdList(vec![ conditions: vec![Condition::IdList(vec![
TaskId::WorkingSetId(1), TaskId::WorkingSetId(1),
TaskId::WorkingSetId(2), TaskId::WorkingSetId(2),
TaskId::WorkingSetId(3), TaskId::WorkingSetId(3),
]), ])],
..Default::default()
} }
); );
} }
@@ -190,13 +186,12 @@ mod test {
assert_eq!( assert_eq!(
filter, filter,
Filter { Filter {
universe: Universe::IdList(vec![ conditions: vec![Condition::IdList(vec![
TaskId::WorkingSetId(1), TaskId::WorkingSetId(1),
TaskId::WorkingSetId(2), TaskId::WorkingSetId(2),
TaskId::WorkingSetId(3), TaskId::WorkingSetId(3),
TaskId::WorkingSetId(4), TaskId::WorkingSetId(4),
]), ])],
..Default::default()
} }
); );
} }
@@ -208,11 +203,10 @@ mod test {
assert_eq!( assert_eq!(
filter, filter,
Filter { Filter {
universe: Universe::IdList(vec![ conditions: vec![Condition::IdList(vec![
TaskId::WorkingSetId(1), TaskId::WorkingSetId(1),
TaskId::PartialUuid(s!("abcd1234")), TaskId::PartialUuid(s!("abcd1234")),
]), ])],
..Default::default()
} }
); );
} }
@@ -224,12 +218,66 @@ mod test {
assert_eq!( assert_eq!(
filter, filter,
Filter { Filter {
universe: Universe::IdList(vec![TaskId::WorkingSetId(1),]),
conditions: vec![ conditions: vec![
Condition::IdList(vec![TaskId::WorkingSetId(1),]),
Condition::HasTag("yes".into()), Condition::HasTag("yes".into()),
Condition::NoTag("no".into()), Condition::NoTag("no".into()),
], ],
..Default::default() }
);
}
#[test]
fn intersect_idlist_idlist() {
let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1;
let right = Filter::parse(argv!["2,3", "+no"]).unwrap().1;
let both = left.intersect(right);
assert_eq!(
both,
Filter {
conditions: vec![
// from first filter
Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]),
Condition::HasTag("yes".into()),
// from second filter
Condition::IdList(vec![TaskId::WorkingSetId(2), TaskId::WorkingSetId(3)]),
Condition::HasTag("no".into()),
],
}
);
}
#[test]
fn intersect_idlist_alltasks() {
let left = Filter::parse(argv!["1,2", "+yes"]).unwrap().1;
let right = Filter::parse(argv!["+no"]).unwrap().1;
let both = left.intersect(right);
assert_eq!(
both,
Filter {
conditions: vec![
// from first filter
Condition::IdList(vec![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2),]),
Condition::HasTag("yes".into()),
// from second filter
Condition::HasTag("no".into()),
],
}
);
}
#[test]
fn intersect_alltasks_alltasks() {
let left = Filter::parse(argv!["+yes"]).unwrap().1;
let right = Filter::parse(argv!["+no"]).unwrap().1;
let both = left.intersect(right);
assert_eq!(
both,
Filter {
conditions: vec![
Condition::HasTag("yes".into()),
Condition::HasTag("no".into()),
],
} }
); );
} }

View File

@@ -19,7 +19,7 @@ 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};
pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use modification::{DescriptionMod, Modification};
pub(crate) use subcommand::Subcommand; pub(crate) use subcommand::Subcommand;

View File

@@ -383,7 +383,7 @@ impl Sync {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::argparse::Universe; use crate::argparse::Condition;
const EMPTY: Vec<&str> = vec![]; const EMPTY: Vec<&str> = vec![];
@@ -459,8 +459,7 @@ mod test {
fn test_modify_description_multi() { fn test_modify_description_multi() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
description: DescriptionMod::Set(s!("foo bar")), description: DescriptionMod::Set(s!("foo bar")),
@@ -477,8 +476,7 @@ mod test {
fn test_append() { fn test_append() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
description: DescriptionMod::Append(s!("foo bar")), description: DescriptionMod::Append(s!("foo bar")),
@@ -495,8 +493,7 @@ mod test {
fn test_prepend() { fn test_prepend() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
description: DescriptionMod::Prepend(s!("foo bar")), description: DescriptionMod::Prepend(s!("foo bar")),
@@ -513,8 +510,7 @@ mod test {
fn test_done() { fn test_done() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
status: Some(Status::Completed), status: Some(Status::Completed),
@@ -531,8 +527,7 @@ mod test {
fn test_done_modify() { fn test_done_modify() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
description: DescriptionMod::Set(s!("now-finished")), description: DescriptionMod::Set(s!("now-finished")),
@@ -550,8 +545,7 @@ mod test {
fn test_start() { fn test_start() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
active: Some(true), active: Some(true),
@@ -568,8 +562,7 @@ mod test {
fn test_start_modify() { fn test_start_modify() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
active: Some(true), active: Some(true),
@@ -587,8 +580,7 @@ mod test {
fn test_stop() { fn test_stop() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
active: Some(false), active: Some(false),
@@ -605,8 +597,7 @@ mod test {
fn test_stop_modify() { fn test_stop_modify() {
let subcommand = Subcommand::Modify { let subcommand = Subcommand::Modify {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![123]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
..Default::default()
}, },
modification: Modification { modification: Modification {
description: DescriptionMod::Set(s!("mod")), description: DescriptionMod::Set(s!("mod")),
@@ -636,8 +627,10 @@ mod test {
fn test_report_filter_before() { fn test_report_filter_before() {
let subcommand = Subcommand::Report { let subcommand = Subcommand::Report {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![12, 13]), conditions: vec![Condition::IdList(vec![
..Default::default() TaskId::WorkingSetId(12),
TaskId::WorkingSetId(13),
])],
}, },
report_name: "foo".to_owned(), report_name: "foo".to_owned(),
}; };
@@ -651,8 +644,10 @@ mod test {
fn test_report_filter_after() { fn test_report_filter_after() {
let subcommand = Subcommand::Report { let subcommand = Subcommand::Report {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![12, 13]), conditions: vec![Condition::IdList(vec![
..Default::default() TaskId::WorkingSetId(12),
TaskId::WorkingSetId(13),
])],
}, },
report_name: "foo".to_owned(), report_name: "foo".to_owned(),
}; };
@@ -666,8 +661,10 @@ mod test {
fn test_report_filter_next() { fn test_report_filter_next() {
let subcommand = Subcommand::Report { let subcommand = Subcommand::Report {
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![12, 13]), conditions: vec![Condition::IdList(vec![
..Default::default() TaskId::WorkingSetId(12),
TaskId::WorkingSetId(13),
])],
}, },
report_name: "next".to_owned(), report_name: "next".to_owned(),
}; };
@@ -696,8 +693,10 @@ mod test {
let subcommand = Subcommand::Info { let subcommand = Subcommand::Info {
debug: false, debug: false,
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![12, 13]), conditions: vec![Condition::IdList(vec![
..Default::default() TaskId::WorkingSetId(12),
TaskId::WorkingSetId(13),
])],
}, },
}; };
assert_eq!( assert_eq!(
@@ -711,8 +710,7 @@ mod test {
let subcommand = Subcommand::Info { let subcommand = Subcommand::Info {
debug: true, debug: true,
filter: Filter { filter: Filter {
universe: Universe::for_ids(vec![12]), conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(12)])],
..Default::default()
}, },
}; };
assert_eq!( assert_eq!(

View File

@@ -1,10 +1,10 @@
use crate::argparse::{Condition, Filter, TaskId, Universe}; use crate::argparse::{Condition, Filter, TaskId};
use failure::Fallible; use failure::Fallible;
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryInto; use std::convert::TryInto;
use taskchampion::{Replica, Tag, Task}; use taskchampion::{Replica, Status, Tag, Task, Uuid};
fn match_task(filter: &Filter, task: &Task) -> bool { fn match_task(filter: &Filter, task: &Task, uuid: Uuid, working_set_id: Option<usize>) -> bool {
for cond in &filter.conditions { for cond in &filter.conditions {
match cond { match cond {
Condition::HasTag(ref tag) => { Condition::HasTag(ref tag) => {
@@ -21,11 +21,85 @@ fn match_task(filter: &Filter, task: &Task) -> bool {
return false; return false;
} }
} }
Condition::Status(status) => {
if task.get_status() != *status {
return false;
}
}
Condition::IdList(ids) => {
let uuid_str = uuid.to_string();
let mut found = false;
for id in ids {
if match id {
TaskId::WorkingSetId(i) => Some(*i) == working_set_id,
TaskId::PartialUuid(partial) => uuid_str.starts_with(partial),
TaskId::Uuid(i) => *i == uuid,
} {
found = true;
break;
}
}
if !found {
return false;
}
}
} }
} }
true true
} }
// the universe of tasks we must consider
enum Universe {
/// Scan all the tasks
AllTasks,
/// Scan the working set (for pending tasks)
WorkingSet,
/// Scan an explicit set of tasks, "Absolute" meaning either full UUID or a working set
/// index
AbsoluteIdList(Vec<TaskId>),
}
/// Determine the universe for the given filter; avoiding the need to scan all tasks in most cases.
fn universe_for_filter(filter: &Filter) -> Universe {
/// If there is a condition with Status::Pending, return true
fn has_pending_condition(filter: &Filter) -> bool {
filter
.conditions
.iter()
.any(|cond| matches!(cond, Condition::Status(Status::Pending)))
}
/// If there is a condition with an IdList containing no partial UUIDs,
/// return that.
fn absolute_id_list_condition(filter: &Filter) -> Option<Vec<TaskId>> {
filter
.conditions
.iter()
.find(|cond| {
if let Condition::IdList(ids) = cond {
!ids.iter().any(|id| matches!(id, TaskId::PartialUuid(_)))
} else {
false
}
})
.map(|cond| {
if let Condition::IdList(ids) = cond {
ids.to_vec()
} else {
unreachable!() // any condition found above must be an IdList(_)
}
})
}
if let Some(ids) = absolute_id_list_condition(filter) {
Universe::AbsoluteIdList(ids)
} else if has_pending_condition(filter) {
Universe::WorkingSet
} else {
Universe::AllTasks
}
}
/// Return the tasks matching the given filter. This will return each matching /// Return the tasks matching the given filter. This will return each matching
/// task once, even if the user specified the same task multiple times on the /// task once, even if the user specified the same task multiple times on the
/// command line. /// command line.
@@ -35,38 +109,14 @@ pub(super) fn filtered_tasks(
) -> Fallible<impl Iterator<Item = Task>> { ) -> Fallible<impl Iterator<Item = Task>> {
let mut res = vec![]; let mut res = vec![];
fn is_partial_uuid(taskid: &TaskId) -> bool { log::debug!("Applying filter {:?}", filter);
matches!(taskid, TaskId::PartialUuid(_))
}
// We will enumerate the universe of tasks for this filter, checking // We will enumerate the universe of tasks for this filter, checking
// each resulting task with match_task // each resulting task with match_task
match filter.universe { match universe_for_filter(filter) {
// A list of IDs, but some are partial so we need to iterate over // A list of IDs, but some are partial so we need to iterate over
// all tasks and pattern-match their Uuids // all tasks and pattern-match their Uuids
Universe::IdList(ref ids) if ids.iter().any(is_partial_uuid) => { Universe::AbsoluteIdList(ref ids) => {
log::debug!("Scanning entire task database due to partial UUIDs in the filter");
'task: for (uuid, task) in replica.all_tasks()?.drain() {
for id in ids {
let in_universe = match id {
TaskId::WorkingSetId(id) => {
// NOTE: (#108) this results in many reads of the working set; it
// may be better to cache this information here or in the Replica.
replica.get_working_set_index(&uuid)? == Some(*id)
}
TaskId::PartialUuid(prefix) => uuid.to_string().starts_with(prefix),
TaskId::Uuid(id) => id == &uuid,
};
if in_universe && match_task(filter, &task) {
res.push(task);
continue 'task;
}
}
}
}
// A list of full IDs, which we can fetch directly
Universe::IdList(ref ids) => {
log::debug!("Scanning only the tasks specified in the filter"); log::debug!("Scanning only the tasks specified in the filter");
// this is the only case where we might accidentally return the same task // this is the only case where we might accidentally return the same task
// several times, so we must track the seen tasks. // several times, so we must track the seen tasks.
@@ -74,7 +124,7 @@ pub(super) fn filtered_tasks(
for id in ids { for id in ids {
let task = match id { let task = match id {
TaskId::WorkingSetId(id) => replica.get_working_set_task(*id)?, TaskId::WorkingSetId(id) => replica.get_working_set_task(*id)?,
TaskId::PartialUuid(_) => unreachable!(), // handled above TaskId::PartialUuid(_) => unreachable!(), // not present in absolute id list
TaskId::Uuid(id) => replica.get_task(id)?, TaskId::Uuid(id) => replica.get_task(id)?,
}; };
@@ -86,7 +136,9 @@ pub(super) fn filtered_tasks(
} }
seen.insert(uuid); seen.insert(uuid);
if match_task(filter, &task) { let working_set_id = replica.get_working_set_index(&uuid)?;
if match_task(filter, &task, uuid, working_set_id) {
res.push(task); res.push(task);
} }
} }
@@ -96,19 +148,20 @@ pub(super) fn filtered_tasks(
// All tasks -- iterate over the full set // All tasks -- iterate over the full set
Universe::AllTasks => { Universe::AllTasks => {
log::debug!("Scanning all tasks in the task database"); log::debug!("Scanning all tasks in the task database");
for (_, task) in replica.all_tasks()?.drain() { for (uuid, task) in replica.all_tasks()?.drain() {
if match_task(filter, &task) { // Yikes, slow! https://github.com/djmitche/taskchampion/issues/108
let working_set_id = replica.get_working_set_index(&uuid)?;
if match_task(filter, &task, uuid, working_set_id) {
res.push(task); res.push(task);
} }
} }
} }
Universe::WorkingSet => {
// Pending tasks -- just scan the working set
Universe::PendingTasks => {
log::debug!("Scanning only the working set (pending tasks)"); log::debug!("Scanning only the working set (pending tasks)");
for task in replica.working_set()?.drain(..) { for (i, task) in replica.working_set()?.drain(..).enumerate() {
if let Some(task) = task { if let Some(task) = task {
if match_task(filter, &task) { let uuid = *task.get_uuid();
if match_task(filter, &task, uuid, Some(i)) {
res.push(task); res.push(task);
} }
} }
@@ -136,12 +189,11 @@ mod test {
let t1uuid = *t1.get_uuid(); let t1uuid = *t1.get_uuid();
let filter = Filter { let filter = Filter {
universe: Universe::IdList(vec![ conditions: vec![Condition::IdList(vec![
TaskId::Uuid(t1uuid), // A TaskId::Uuid(t1uuid), // A
TaskId::WorkingSetId(1), // A (again, dups filtered) TaskId::WorkingSetId(1), // A (again, dups filtered)
TaskId::Uuid(*t2.get_uuid()), // B TaskId::Uuid(*t2.get_uuid()), // B
]), ])],
..Default::default()
}; };
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)
.unwrap() .unwrap()
@@ -165,12 +217,11 @@ mod test {
let t2partial = t2uuid[..13].to_owned(); let t2partial = t2uuid[..13].to_owned();
let filter = Filter { let filter = Filter {
universe: Universe::IdList(vec![ conditions: vec![Condition::IdList(vec![
TaskId::Uuid(t1uuid), // A TaskId::Uuid(t1uuid), // A
TaskId::WorkingSetId(1), // A (again, dups filtered) TaskId::WorkingSetId(1), // A (again, dups filtered)
TaskId::PartialUuid(t2partial), // B TaskId::PartialUuid(t2partial), // B
]), ])],
..Default::default()
}; };
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)
.unwrap() .unwrap()
@@ -189,10 +240,7 @@ mod test {
replica.new_task(Status::Deleted, s!("C")).unwrap(); replica.new_task(Status::Deleted, s!("C")).unwrap();
replica.gc().unwrap(); replica.gc().unwrap();
let filter = Filter { let filter = Filter { conditions: vec![] };
universe: Universe::AllTasks,
..Default::default()
};
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)
.unwrap() .unwrap()
.map(|t| t.get_description().to_owned()) .map(|t| t.get_description().to_owned())
@@ -224,9 +272,7 @@ mod test {
// look for just "yes" (A and B) // look for just "yes" (A and B)
let filter = Filter { let filter = Filter {
universe: Universe::AllTasks,
conditions: vec![Condition::HasTag(s!("yes"))], conditions: vec![Condition::HasTag(s!("yes"))],
..Default::default()
}; };
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)?
.map(|t| t.get_description().to_owned()) .map(|t| t.get_description().to_owned())
@@ -236,9 +282,7 @@ mod test {
// look for tags without "no" (A, D) // look for tags without "no" (A, D)
let filter = Filter { let filter = Filter {
universe: Universe::AllTasks,
conditions: vec![Condition::NoTag(s!("no"))], conditions: vec![Condition::NoTag(s!("no"))],
..Default::default()
}; };
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)?
.map(|t| t.get_description().to_owned()) .map(|t| t.get_description().to_owned())
@@ -248,9 +292,7 @@ mod test {
// look for tags with "yes" and "no" (B) // look for tags with "yes" and "no" (B)
let filter = Filter { let filter = Filter {
universe: Universe::AllTasks,
conditions: vec![Condition::HasTag(s!("yes")), Condition::HasTag(s!("no"))], conditions: vec![Condition::HasTag(s!("yes")), Condition::HasTag(s!("no"))],
..Default::default()
}; };
let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)? let filtered: Vec<_> = filtered_tasks(&mut replica, &filter)?
.map(|t| t.get_description().to_owned()) .map(|t| t.get_description().to_owned())
@@ -270,8 +312,7 @@ mod test {
replica.gc().unwrap(); replica.gc().unwrap();
let filter = Filter { let filter = Filter {
universe: Universe::PendingTasks, conditions: vec![Condition::Status(Status::Pending)],
..Default::default()
}; };
let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter) let mut filtered: Vec<_> = filtered_tasks(&mut replica, &filter)
.unwrap() .unwrap()

View File

@@ -1,11 +1,11 @@
use crate::argparse::Filter; use crate::argparse::{Condition, Filter};
use crate::invocation::filtered_tasks; use crate::invocation::filtered_tasks;
use crate::report::{Column, Property, Report, Sort, SortBy}; use crate::report::{Column, Property, Report, Sort, SortBy};
use crate::table; use crate::table;
use failure::{bail, 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, Status, Task, Uuid};
use termcolor::WriteColor; use termcolor::WriteColor;
// pending #123, this is a non-fallible way of looking up a task's working set index // pending #123, this is a non-fallible way of looking up a task's working set index
@@ -125,24 +125,26 @@ fn get_report(report_name: String, filter: Filter) -> Fallible<Report> {
ascending: false, ascending: false,
sort_by: SortBy::Uuid, sort_by: SortBy::Uuid,
}]; }];
use crate::argparse::Universe; let mut report = match report_name.as_ref() {
Ok(match report_name.as_ref() {
"list" => Report { "list" => Report {
columns, columns,
sort, sort,
filter, filter: Default::default(),
}, },
"next" => Report { "next" => Report {
columns, columns,
sort, sort,
// TODO: merge invocation filter and report filter
filter: Filter { filter: Filter {
universe: Universe::PendingTasks, conditions: vec![Condition::Status(Status::Pending)],
..Default::default()
}, },
}, },
_ => bail!("Unknown report {:?}", report_name), _ => bail!("Unknown report {:?}", report_name),
}) };
// intersect the report's filter with the user-supplied filter
report.filter = report.filter.intersect(filter);
Ok(report)
} }
pub(super) fn display_report<W: WriteColor>( pub(super) fn display_report<W: WriteColor>(