use std::rc::Rc;
use anyhow::anyhow;
use anyhow::Result;
use crate::ser::view::TagLit as SerTagLit;
use crate::ser::view::View as SerView;
use crate::ser::ToSerde;
use crate::tags::Tag;
use crate::tags::Templates;
use crate::tasks::Task;
use crate::tasks::TaskIter;
use crate::tasks::Tasks;
#[derive(Clone, Debug)]
enum TagLit {
Pos(Tag),
Neg(Tag),
}
impl TagLit {
fn tag(&self) -> &Tag {
match self {
TagLit::Pos(tag) | TagLit::Neg(tag) => tag,
}
}
fn is_pos(&self) -> bool {
match self {
TagLit::Pos(_) => true,
TagLit::Neg(_) => false,
}
}
}
impl ToSerde for TagLit {
type Output = SerTagLit;
fn to_serde(&self) -> Self::Output {
match self {
TagLit::Pos(tag) => SerTagLit::Pos(tag.to_serde()),
TagLit::Neg(tag) => SerTagLit::Neg(tag.to_serde()),
}
}
}
#[derive(Clone, Debug)]
pub struct Filter<'tasks> {
iter: TaskIter<'tasks>,
lits: &'tasks [Vec<TagLit>],
}
impl<'tasks> Filter<'tasks> {
fn new(iter: TaskIter<'tasks>, lits: &'tasks [Vec<TagLit>]) -> Self {
Self { iter, lits }
}
fn matches<'tag, I>(lits: &[TagLit], avail_tags: &I) -> bool
where
I: Iterator<Item = &'tag Tag> + Clone,
{
for lit in lits {
let tag = lit.tag();
let must_exist = lit.is_pos();
if avail_tags.clone().any(|x| x == tag) == must_exist {
return true
}
}
false
}
fn matched_by<'tag, I>(&self, avail_tags: &I) -> bool
where
I: Iterator<Item = &'tag Tag> + Clone,
{
for req_lits in self.lits {
if !Self::matches(req_lits, avail_tags) {
return false
}
}
true
}
}
impl<'tasks> Iterator for Filter<'tasks> {
type Item = &'tasks Rc<Task>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.iter.next() {
Some(task) => {
if task.tags(|iter| self.matched_by(&iter)) {
return Some(task)
}
},
None => return None,
}
}
}
}
impl DoubleEndedIterator for Filter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
loop {
match self.iter.next_back() {
Some(task) => {
if task.tags(|iter| self.matched_by(&iter)) {
return Some(task)
}
},
None => return None,
}
}
}
}
pub struct ViewBuilder {
tasks: Rc<Tasks>,
lits: Vec<Vec<TagLit>>,
}
impl ViewBuilder {
pub fn new(tasks: Rc<Tasks>) -> ViewBuilder {
Self {
tasks,
lits: Default::default(),
}
}
#[cfg(test)]
fn and_lit(mut self, lit: TagLit) -> ViewBuilder {
self.lits.push(vec![lit]);
self
}
#[cfg(test)]
pub fn and(self, tag: impl Into<Tag>) -> ViewBuilder {
self.and_lit(TagLit::Pos(tag.into()))
}
#[cfg(test)]
pub fn and_not(self, tag: impl Into<Tag>) -> ViewBuilder {
self.and_lit(TagLit::Neg(tag.into()))
}
#[cfg(test)]
fn or_lit(mut self, lit: TagLit) -> ViewBuilder {
let last = self.lits.pop();
match last {
Some(mut last) => {
last.push(lit);
self.lits.push(last);
},
None => self.lits.push(vec![lit]),
};
self
}
#[cfg(test)]
pub fn or(self, tag: impl Into<Tag>) -> ViewBuilder {
self.or_lit(TagLit::Pos(tag.into()))
}
#[cfg(test)]
pub fn or_not(self, tag: impl Into<Tag>) -> ViewBuilder {
self.or_lit(TagLit::Neg(tag.into()))
}
pub fn build(self, name: impl Into<String>) -> View {
View {
name: name.into(),
tasks: self.tasks,
lits: self.lits,
}
}
}
#[derive(Clone, Debug)]
pub struct View {
name: String,
tasks: Rc<Tasks>,
lits: Vec<Vec<TagLit>>,
}
impl View {
pub fn with_serde(view: SerView, templates: &Rc<Templates>, tasks: Rc<Tasks>) -> Result<Self> {
let mut and_lits = Vec::with_capacity(view.lits.len());
for lits in view.lits.into_iter() {
let mut or_lits = Vec::with_capacity(lits.len());
for lit in lits.into_iter() {
let tag = templates
.instantiate(lit.id())
.ok_or_else(|| anyhow!("encountered invalid tag ID {}", lit.id()))?;
let lit = match lit {
SerTagLit::Pos(_) => TagLit::Pos(tag),
SerTagLit::Neg(_) => TagLit::Neg(tag),
};
or_lits.push(lit);
}
and_lits.push(or_lits);
}
Ok(Self {
name: view.name,
tasks,
lits: and_lits,
})
}
#[inline]
pub fn iter<F, R>(&self, mut f: F) -> R
where
F: FnMut(Filter<'_>) -> R,
{
self.tasks.iter(|iter| f(Filter::new(iter, &self.lits)))
}
pub fn positive_tag_iter(&self) -> impl Iterator<Item = &Tag> {
self.lits.iter().flat_map(|disjunctions| {
disjunctions.iter().filter_map(|literal| match literal {
TagLit::Pos(tag) => Some(tag),
TagLit::Neg(..) => None,
})
})
}
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.iter(|mut iter| iter.next().is_none())
}
pub fn name(&self) -> &str {
&self.name
}
}
impl ToSerde for View {
type Output = SerView;
fn to_serde(&self) -> Self::Output {
let lits = self
.lits
.iter()
.map(|lits| lits.iter().map(ToSerde::to_serde).collect())
.collect();
SerView {
name: self.name.clone(),
lits,
deprecated: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ser::tags::Templates as SerTemplates;
use crate::ser::tasks::Tasks as SerTasks;
use crate::tags::Templates;
use crate::test::make_tasks;
use crate::test::make_tasks_with_tags;
use crate::test::COMPLETE_TAG;
fn make_view(count: usize) -> View {
let tasks = Tasks::with_serde_tasks(make_tasks(count));
let tasks = Rc::new(tasks.unwrap());
let view = ViewBuilder::new(tasks).build("test");
view
}
fn make_tagged_tasks(count: usize) -> (Rc<Templates>, Rc<Tasks>) {
let (_, templates, tasks) = make_tasks_with_tags(count);
let templates = Rc::new(Templates::with_serde(SerTemplates(templates)).unwrap());
let tasks = SerTasks::from(tasks);
let tasks = Tasks::with_serde(tasks, Rc::clone(&templates)).unwrap();
let tasks = Rc::new(tasks);
(templates, tasks)
}
#[test]
fn is_empty() {
assert!(make_view(0).is_empty());
assert!(!make_view(1).is_empty());
}
#[test]
fn pos_tag_iteration() {
fn pos_tags(view: &View) -> Vec<String> {
view
.positive_tag_iter()
.map(|tag| tag.name().to_string())
.collect::<Vec<_>>()
}
let (templates, tasks) = make_tagged_tasks(20);
let view = ViewBuilder::new(Rc::clone(&tasks)).build("test");
assert_eq!(pos_tags(&view), Vec::<String>::new());
let view = ViewBuilder::new(Rc::clone(&tasks))
.and(templates.instantiate_from_name(COMPLETE_TAG))
.build("test");
assert_eq!(pos_tags(&view), vec![COMPLETE_TAG]);
let view = ViewBuilder::new(Rc::clone(&tasks))
.and(templates.instantiate_from_name("tag1"))
.and_not(templates.instantiate_from_name("tag2"))
.build("test");
assert_eq!(pos_tags(&view), vec!["tag1"]);
let view = ViewBuilder::new(Rc::clone(&tasks))
.and(templates.instantiate_from_name("tag1"))
.or(templates.instantiate_from_name("tag2"))
.build("test");
assert_eq!(pos_tags(&view), vec!["tag1", "tag2"]);
let view = ViewBuilder::new(Rc::clone(&tasks))
.and(templates.instantiate_from_name("tag3"))
.or(templates.instantiate_from_name("tag1"))
.and_not(templates.instantiate_from_name("tag2"))
.build("test");
assert_eq!(pos_tags(&view), vec!["tag3", "tag1"]);
let view = ViewBuilder::new(Rc::clone(&tasks))
.or(templates.instantiate_from_name("tag3"))
.or(templates.instantiate_from_name("tag2"))
.or(templates.instantiate_from_name("tag5"))
.build("test");
assert_eq!(pos_tags(&view), vec!["tag3", "tag2", "tag5"]);
let view = ViewBuilder::new(tasks)
.and(templates.instantiate_from_name("tag3"))
.and(templates.instantiate_from_name("tag2"))
.and(templates.instantiate_from_name("tag5"))
.build("test");
assert_eq!(pos_tags(&view), vec!["tag3", "tag2", "tag5"]);
}
#[test]
fn filter_completions() {
let (templates, tasks) = make_tagged_tasks(16);
let complete_tag = templates.instantiate_from_name(COMPLETE_TAG);
let view = ViewBuilder::new(tasks)
.and(complete_tag.clone())
.build("test");
let () = view.iter(|mut iter| {
let () = iter
.clone()
.for_each(|task| assert!(task.has_tag(&complete_tag)));
assert_eq!(iter.next().unwrap().summary(), "2");
assert_eq!(iter.next().unwrap().summary(), "4");
assert_eq!(iter.next().unwrap().summary(), "6");
assert_eq!(iter.next().unwrap().summary(), "8");
assert_eq!(iter.next().unwrap().summary(), "10");
assert_eq!(iter.next().unwrap().summary(), "12");
assert_eq!(iter.next().unwrap().summary(), "14");
assert_eq!(iter.next().unwrap().summary(), "16");
assert!(iter.next().is_none());
});
}
#[test]
fn filter_no_completions() {
let (templates, tasks) = make_tagged_tasks(16);
let complete_tag = templates.instantiate_from_name(COMPLETE_TAG);
let view = ViewBuilder::new(tasks)
.and_not(complete_tag.clone())
.build("test");
view.iter(|mut iter| {
let () = iter
.clone()
.for_each(|task| assert!(!task.has_tag(&complete_tag)));
assert_eq!(iter.next().unwrap().summary(), "1");
assert_eq!(iter.next().unwrap().summary(), "3");
assert_eq!(iter.next().unwrap().summary(), "5");
assert_eq!(iter.next().unwrap().summary(), "7");
assert_eq!(iter.next().unwrap().summary(), "9");
assert_eq!(iter.next().unwrap().summary(), "11");
assert_eq!(iter.next().unwrap().summary(), "13");
assert_eq!(iter.next().unwrap().summary(), "15");
assert!(iter.next().is_none());
})
}
#[test]
fn filter_tag1_and_tag2() {
let (templates, tasks) = make_tagged_tasks(20);
let tag1 = templates.instantiate_from_name(templates.iter().nth(1).unwrap().name());
let tag2 = templates.instantiate_from_name(templates.iter().nth(2).unwrap().name());
let view = ViewBuilder::new(tasks).and(tag1).and(tag2).build("test");
let () = view.iter(|mut iter| {
assert_eq!(iter.next().unwrap().summary(), "11");
assert_eq!(iter.next().unwrap().summary(), "12");
assert_eq!(iter.next().unwrap().summary(), "15");
assert_eq!(iter.next().unwrap().summary(), "16");
assert_eq!(iter.next().unwrap().summary(), "19");
assert_eq!(iter.next().unwrap().summary(), "20");
assert!(iter.next().is_none());
});
}
#[test]
fn filter_tag3_or_tag1() {
let (templates, tasks) = make_tagged_tasks(20);
let tag1 = templates.instantiate_from_name(templates.iter().nth(1).unwrap().name());
let tag3 = templates.instantiate_from_name(templates.iter().nth(3).unwrap().name());
let view = ViewBuilder::new(tasks).or(tag3).or(tag1).build("test");
let () = view.iter(|mut iter| {
assert_eq!(iter.next().unwrap().summary(), "5");
assert_eq!(iter.next().unwrap().summary(), "6");
assert_eq!(iter.next().unwrap().summary(), "7");
assert_eq!(iter.next().unwrap().summary(), "8");
assert_eq!(iter.next().unwrap().summary(), "11");
assert_eq!(iter.next().unwrap().summary(), "12");
assert_eq!(iter.next().unwrap().summary(), "13");
assert_eq!(iter.next().unwrap().summary(), "14");
assert_eq!(iter.next().unwrap().summary(), "15");
assert_eq!(iter.next().unwrap().summary(), "16");
assert_eq!(iter.next().unwrap().summary(), "19");
assert_eq!(iter.next().unwrap().summary(), "20");
assert!(iter.next().is_none());
});
}
#[test]
fn filter_tag1_and_complete_or_tag4() {
let (templates, tasks) = make_tagged_tasks(20);
let complete_tag = templates.instantiate_from_name(COMPLETE_TAG);
let tag1 = templates.instantiate_from_name(templates.iter().nth(1).unwrap().name());
let tag4 = templates.instantiate_from_name(templates.iter().nth(4).unwrap().name());
let view = ViewBuilder::new(tasks)
.and(tag1)
.and(complete_tag)
.or(tag4)
.build("test");
let () = view.iter(|mut iter| {
assert_eq!(iter.next().unwrap().summary(), "6");
assert_eq!(iter.next().unwrap().summary(), "8");
assert_eq!(iter.next().unwrap().summary(), "12");
assert_eq!(iter.next().unwrap().summary(), "16");
assert_eq!(iter.next().unwrap().summary(), "19");
assert_eq!(iter.next().unwrap().summary(), "20");
assert!(iter.next().is_none());
});
}
#[test]
fn filter_tag2_and_not_complete() {
let (templates, tasks) = make_tagged_tasks(20);
let complete_tag = templates.instantiate_from_name(COMPLETE_TAG);
let tag2 = templates.instantiate_from_name(templates.iter().nth(2).unwrap().name());
let view = ViewBuilder::new(tasks)
.and_not(tag2)
.and_not(complete_tag)
.build("test");
let () = view.iter(|mut iter| {
assert_eq!(iter.next().unwrap().summary(), "1");
assert_eq!(iter.next().unwrap().summary(), "3");
assert_eq!(iter.next().unwrap().summary(), "5");
assert_eq!(iter.next().unwrap().summary(), "7");
assert_eq!(iter.next().unwrap().summary(), "13");
assert_eq!(iter.next().unwrap().summary(), "17");
assert!(iter.next().is_none());
});
}
#[test]
fn filter_tag2_or_not_complete_and_tag3() {
let (templates, tasks) = make_tagged_tasks(20);
let complete_tag = templates.instantiate_from_name(COMPLETE_TAG);
let tag2 = templates.instantiate_from_name(templates.iter().nth(2).unwrap().name());
let tag3 = templates.instantiate_from_name(templates.iter().nth(3).unwrap().name());
let view = ViewBuilder::new(tasks)
.or_not(tag2)
.or_not(complete_tag)
.and(tag3)
.build("test");
let () = view.iter(|mut iter| {
assert_eq!(iter.next().unwrap().summary(), "13");
assert_eq!(iter.next().unwrap().summary(), "14");
assert_eq!(iter.next().unwrap().summary(), "15");
assert_eq!(iter.next().unwrap().summary(), "19");
assert!(iter.next().is_none());
});
}
}