diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..bab865f --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; + +use chrono::NaiveDate; +use structopt::StructOpt; + +use crate::eval::{DateRange, EntryMode}; +use crate::files::Files; + +use self::layout::Layout; + +mod layout; + +#[derive(Debug, StructOpt)] +pub struct Opt { + #[structopt(parse(from_os_str))] + file: PathBuf, +} + +pub fn run() -> anyhow::Result<()> { + let opt = Opt::from_args(); + + let files = Files::load(&opt.file)?; + let now = files.now(); + let today = now.date().naive_local(); + + let range = DateRange::new( + NaiveDate::from_ymd(2021, 1, 1), + NaiveDate::from_ymd(2022, 12, 31), + ) + .unwrap(); + + let entries = files.eval(EntryMode::Relevant, range)?; + println!("{:#?}", entries); + + let mut layout = Layout::new(range, today); + layout.layout(&files, &entries); + println!("{:#?}", layout); + + files.save()?; + Ok(()) +} diff --git a/src/cli/layout.rs b/src/cli/layout.rs new file mode 100644 index 0000000..6ebb0bb --- /dev/null +++ b/src/cli/layout.rs @@ -0,0 +1,147 @@ +use std::collections::HashMap; + +use chrono::NaiveDate; + +use crate::eval::{DateRange, Dates, Entry, EntryKind}; +use crate::files::primitives::Time; +use crate::files::Files; + +#[derive(Debug)] +pub struct TimedLayout { + pub ending: Vec, + pub at: Vec, + pub starting: Vec, +} + +impl TimedLayout { + pub fn new() -> Self { + Self { + ending: vec![], + at: vec![], + starting: vec![], + } + } +} + +#[derive(Debug)] +pub struct DayLayout { + pub ending: Vec, + pub timed: HashMap, + pub at: Vec, + pub other: Vec, + pub starting: Vec, +} + +impl DayLayout { + pub fn new() -> Self { + Self { + ending: vec![], + timed: HashMap::new(), + at: vec![], + other: vec![], + starting: vec![], + } + } +} + +#[derive(Debug)] +pub struct Layout { + pub range: DateRange, + pub today: NaiveDate, + pub days: HashMap, +} + +impl Layout { + pub fn new(range: DateRange, today: NaiveDate) -> Self { + let mut days = HashMap::new(); + for day in range.days() { + days.insert(day, DayLayout::new()); + } + Self { range, today, days } + } + + pub fn layout(&mut self, files: &Files, entries: &[Entry]) { + let mut commands = entries + .iter() + .enumerate() + .map(|(i, e)| (i, e, files.command(e.source))) + .collect::>(); + + // Sort entries (maintaining the keys) so the output is more deterministic + commands.sort_by_key(|(_, _, c)| c.title()); + commands.sort_by_key(|(_, e, _)| e.dates.map(|d| (d.end(), d.end_time()))); + commands.sort_by_key(|(_, e, _)| e.dates.map(|d| (d.start(), d.start_time()))); + + for (index, entry, _) in commands { + self.insert(index, entry); + } + } + + fn insert(&mut self, index: usize, entry: &Entry) { + if let Some(dates) = entry.dates { + self.insert_dated(index, dates); + if let EntryKind::TaskDone(at) = entry.kind { + self.insert_other(at, index); + } + } else { + self.insert_other(self.today, index); + } + } + + fn insert_dated(&mut self, index: usize, dates: Dates) { + let (start, end) = dates.start_end(); + if start < self.range.from() && self.range.until() < end { + self.insert_other(self.today, index); + } else if let Some((date, time)) = dates.point_in_time() { + self.insert_at(date, time, index); + } else { + let (start_time, end_time) = match dates.start_end_time() { + Some((s, e)) => (Some(s), Some(e)), + None => (None, None), + }; + self.insert_start(start, start_time, index); + self.insert_end(end, end_time, index); + } + } + + fn insert_f(&mut self, date: NaiveDate, f: impl FnOnce(&mut DayLayout)) { + if let Some(l) = self.days.get_mut(&date) { + f(l); + } + } + + fn insert_timed_f(&mut self, date: NaiveDate, time: Time, f: impl FnOnce(&mut TimedLayout)) { + if let Some(l) = self.days.get_mut(&date) { + let tl = l.timed.entry(time).or_insert_with(TimedLayout::new); + f(tl); + } + } + + fn insert_start(&mut self, date: NaiveDate, time: Option