use crate::attributes::{AttrFilter, AttrValue};
use crate::commands::CmdResult;
use crate::error::Result;
use crate::index::{DisplayPad, PadSelector};
use crate::model::{Scope, TodoStatus};
use crate::store::DataStore;
mod attr_filter;
mod search;
mod selector_filter;
mod status_filter;
pub use status_filter::PadStatusFilter;
#[derive(Debug, Clone)]
pub struct PadFilter {
pub status: PadStatusFilter,
pub search_term: Option<String>,
pub todo_status: Option<TodoStatus>,
pub tags: Option<Vec<String>>,
}
impl Default for PadFilter {
fn default() -> Self {
Self {
status: PadStatusFilter::Active,
search_term: None,
todo_status: None,
tags: None,
}
}
}
pub fn run<S: DataStore>(
store: &S,
scope: Scope,
filter: PadFilter,
selectors: &[PadSelector],
) -> Result<CmdResult> {
let indexed = super::helpers::indexed_pads(store, scope)?;
let indexed = if selectors.is_empty() {
indexed
} else {
selector_filter::filter_by_selectors(indexed, selectors)?
};
let mut filtered: Vec<DisplayPad> = status_filter::filter_tree(indexed, filter.status);
let mut attr_filters: Vec<AttrFilter> = Vec::new();
if let Some(todo_status) = filter.todo_status {
let status_str = format!("{:?}", todo_status);
attr_filters.push(AttrFilter::eq("status", AttrValue::Enum(status_str)));
}
if let Some(ref tags) = filter.tags {
if !tags.is_empty() {
attr_filters.push(AttrFilter::contains_all("tags", tags.clone()));
}
}
filtered = attr_filter::apply_attr_filters(filtered, &attr_filters);
if let Some(term) = &filter.search_term {
filtered = search::apply_search(filtered, term);
}
Ok(CmdResult::default().with_listed_pads(filtered))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::{create, delete};
use crate::index::DisplayIndex;
use crate::store::bucketed::BucketedStore;
use crate::store::mem_backend::MemBackend;
#[test]
fn test_filters() {
let mut store = BucketedStore::new(
MemBackend::new(),
MemBackend::new(),
MemBackend::new(),
MemBackend::new(),
);
create::run(&mut store, Scope::Project, "Active".into(), "".into(), None).unwrap();
create::run(
&mut store,
Scope::Project,
"Deleted".into(),
"".into(),
None,
)
.unwrap();
delete::run(
&mut store,
Scope::Project,
&[PadSelector::Path(vec![DisplayIndex::Regular(1)])],
)
.unwrap();
let res = run(
&store,
Scope::Project,
PadFilter {
status: PadStatusFilter::Active,
search_term: None,
todo_status: None,
tags: None,
},
&[],
)
.unwrap();
assert_eq!(res.listed_pads.len(), 1);
assert_eq!(res.listed_pads[0].pad.metadata.title, "Active");
let res = run(
&store,
Scope::Project,
PadFilter {
status: PadStatusFilter::Deleted,
search_term: None,
todo_status: None,
tags: None,
},
&[],
)
.unwrap();
assert_eq!(res.listed_pads.len(), 1);
assert_eq!(res.listed_pads[0].pad.metadata.title, "Deleted");
let res = run(
&store,
Scope::Project,
PadFilter {
status: PadStatusFilter::All,
search_term: None,
todo_status: None,
tags: None,
},
&[],
)
.unwrap();
assert_eq!(res.listed_pads.len(), 2);
}
#[test]
fn test_search() {
let mut store = BucketedStore::new(
MemBackend::new(),
MemBackend::new(),
MemBackend::new(),
MemBackend::new(),
);
create::run(&mut store, Scope::Project, "Foo".into(), "".into(), None).unwrap();
create::run(
&mut store,
Scope::Project,
"Bar".into(),
"contains foo".into(),
None,
)
.unwrap();
let res = run(
&store,
Scope::Project,
PadFilter {
status: PadStatusFilter::Active,
search_term: Some("foo".into()),
todo_status: None,
tags: None,
},
&[],
)
.unwrap();
assert_eq!(res.listed_pads.len(), 2);
assert_eq!(res.listed_pads[0].pad.metadata.title, "Foo");
let matches_0 = res.listed_pads[0].matches.as_ref().unwrap();
assert!(matches_0.iter().any(|m| m.line_number == 0));
let matches_1 = res.listed_pads[1].matches.as_ref().unwrap();
assert!(matches_1.iter().any(|m| m.line_number == 3)); }
}