today/src/eval/entry.rs

159 lines
3.9 KiB
Rust

use std::collections::HashMap;
use chrono::NaiveDate;
use crate::files::commands::DoneDate;
use crate::files::Source;
use super::range::DateRange;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
Task,
TaskDone(NaiveDate),
Note,
Birthday,
}
#[derive(Debug)]
pub struct Entry {
pub kind: EntryKind,
pub title: String,
pub desc: Vec<String>,
pub source: Source,
pub root: Option<DoneDate>,
}
/// Mode that determines how entries are filtered when they are added to
/// an [`Entries`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryMode {
/// The entry's root date must be contained in the range.
Rooted,
/// The entry must overlap the range.
Touching,
/// The entry must be in some way relevant to the range. It may
/// - touch the range,
/// - be an unfinished task that lies before the range,
/// - be a finished task that was completed inside the range, or
/// - have no root date.
Relevant,
}
pub struct Entries {
mode: EntryMode,
range: DateRange,
entries: Vec<Entry>,
}
impl Entries {
pub fn new(mode: EntryMode, range: DateRange) -> Self {
Self {
mode,
range,
entries: vec![],
}
}
fn is_rooted(&self, entry: &Entry) -> bool {
match entry.root {
Some(date) => self.range.contains(date.root()),
None => false,
}
}
fn is_touching(&self, entry: &Entry) -> bool {
if let Some(date) = entry.root {
// Inside the range or overlapping it
date.first() <= self.range.until() && self.range.from() <= date.last()
} else {
false
}
}
fn is_relevant(&self, entry: &Entry) -> bool {
if entry.root.is_none() {
return true;
}
// Anything close to the range
if self.is_touching(entry) {
return true;
}
// Tasks that were finished inside the range
if let EntryKind::TaskDone(done) = entry.kind {
if self.range.contains(done) {
return true;
}
}
// Unfinished tasks before or inside the range
if let EntryKind::Task = entry.kind {
if let Some(date) = entry.root {
if date.first() <= self.range.until() {
return true;
}
}
}
return false;
}
pub fn add(&mut self, entry: Entry) {
let keep = match self.mode {
EntryMode::Rooted => self.is_rooted(&entry),
EntryMode::Touching => self.is_touching(&entry),
EntryMode::Relevant => self.is_relevant(&entry),
};
if keep {
self.entries.push(entry);
}
}
}
pub struct EntryMap {
pub range: DateRange,
map: HashMap<NaiveDate, Option<Entry>>,
undated: Vec<Entry>,
}
impl EntryMap {
pub fn new(range: DateRange) -> Self {
Self {
range,
map: HashMap::new(),
undated: vec![],
}
}
pub fn block(&mut self, date: NaiveDate) {
if self.range.contains(date) {
self.map.entry(date).or_insert(None);
}
}
pub fn insert(&mut self, entry: Entry) {
if let Some(date) = entry.root {
let date = date.root();
if self.range.contains(date) {
self.map.entry(date).or_insert(Some(entry));
} else if let EntryKind::TaskDone(done_date) = entry.kind {
if self.range.contains(done_date) {
self.map.entry(date).or_insert(Some(entry));
}
}
} else {
self.undated.push(entry);
}
}
pub fn drain(&mut self) -> Vec<Entry> {
self.map
.drain()
.filter_map(|(_, entry)| entry)
.chain(self.undated.drain(..))
.collect()
}
}