use onetaskgraph_plugin_api::{
Document, Label, LabelFilter, NativeId, Project, ProjectFilter, StatusCategory, Task,
TextFields, TextQuery,
};
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct LocalTasks {
pub labels: Option<LabelFilter>,
pub statuses: Vec<StatusCategory>,
pub text: Option<TextQuery>,
pub project: Option<ProjectFilter>,
}
impl LocalTasks {
pub fn keeps(&self, task: &Task) -> bool {
if let Some(filter) = &self.labels
&& !labels_match(&task.labels, filter)
{
return false;
}
if !status_matches(task.status.category, &self.statuses) {
return false;
}
if let Some(query) = &self.text
&& !text_matches(&task.title, task.content.as_deref(), query)
{
return false;
}
match &self.project {
None | Some(ProjectFilter::Any) => true,
Some(ProjectFilter::Orphans) => task.project.is_none(),
Some(ProjectFilter::Is(id)) => task.project.as_ref() == Some(id),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct LocalProjects {
pub labels: Option<LabelFilter>,
pub statuses: Vec<StatusCategory>,
pub text: Option<TextQuery>,
}
impl LocalProjects {
pub fn keeps(&self, project: &Project) -> bool {
if let Some(filter) = &self.labels
&& !labels_match(&project.labels, filter)
{
return false;
}
if !status_matches(project.status.category, &self.statuses) {
return false;
}
match &self.text {
None => true,
Some(query) => text_matches(&project.title, project.content.as_deref(), query),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct LocalDocuments {
pub labels: Option<LabelFilter>,
pub text: Option<TextQuery>,
pub project: Option<ProjectFilter>,
}
impl LocalDocuments {
pub fn keeps(&self, document: &Document) -> bool {
if let Some(filter) = &self.labels
&& !labels_match(&document.labels, filter)
{
return false;
}
if let Some(query) = &self.text
&& !text_matches(&document.title, document.content.as_deref(), query)
{
return false;
}
match &self.project {
None | Some(ProjectFilter::Any) => true,
Some(ProjectFilter::Orphans) => document.project.is_none(),
Some(ProjectFilter::Is(id)) => document.project.as_ref() == Some(id),
}
}
}
pub(crate) fn labels_match(labels: &[Label], filter: &LabelFilter) -> bool {
let held: Vec<String> = labels
.iter()
.map(|label| label.name.to_lowercase())
.collect();
let holds = |name: &String| held.contains(&name.to_lowercase());
if !filter.any_of.is_empty() && !filter.any_of.iter().any(holds) {
return false;
}
if !filter.all_of.iter().all(holds) {
return false;
}
!filter.none_of.iter().any(holds)
}
pub(crate) fn status_matches(category: StatusCategory, statuses: &[StatusCategory]) -> bool {
statuses.is_empty() || statuses.contains(&category)
}
pub(crate) fn text_matches(title: &str, content: Option<&str>, query: &TextQuery) -> bool {
let terms = query.terms.to_lowercase();
let in_title = title.to_lowercase().contains(&terms);
let in_content = content.is_some_and(|body| body.to_lowercase().contains(&terms));
match query.fields {
TextFields::Title => in_title,
TextFields::Content => in_content,
TextFields::TitleOrContent => in_title || in_content,
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum ProjectSelector {
#[default]
Any,
Orphans,
Qualified(crate::GlobalId),
Native(NativeId),
}