nunc 0.2.0

WIP: time tracking application
Documentation
use std::result::Result as StdResult;

use clap::{
	ArgAction,
	Subcommand,
	ValueEnum
};

use conciliator::{
	Buffer,
	Claw,
	Conciliator,
	List
};

use greg::Point;
use greg::real::Scale;

use crate::{
	Nunc,
	Result
};
use crate::core::{
	Log,
	Tree,
	Measure,
	Zone
};
use crate::data::entry::{
	Entry,
	List as EntryList
};

#[derive(Subcommand)]
pub(crate) enum Cmd {
	/// Show entries for a given timespan
	Recap {
		#[clap(short = 'l', long = "last", action = ArgAction::Count)]
		offset: u8,
		#[clap(value_enum, default_value_t)]
		timespan: RecapTimespan
	},
	/// Show running activities and status of streaks
	Now,
}

#[derive(Clone, Default, ValueEnum)]
pub(crate) enum RecapTimespan {
	#[default]
	Day,
	Week,
	Month,
	All
}

fn cmd_now(con: &Claw, nunc: &Nunc) -> Result<()> {
	let states = nunc
		.show()?
		.drain(..)
		.map(|s| s.make_inline(nunc))
		.collect::<StdResult<Vec<_>, _>>()?;

	con.print(List::new("Streaks", states.iter()));

	let fmt_active = |buf: &mut Buffer, ent: &Entry| {
		let activity = nunc.get_activity(ent.activity_id).unwrap();
		let (pre, span) = match Point::now() > ent.start {
			true => ("for", Point::now() - ent.start),
			false => ("in", ent.start - Point::now())
		};
		buf.push(activity).push(format_args!(": {pre} {span:#}"));
	};
	List::new("currently running", nunc.get_active_entries()?.iter())
		.with_formatter(fmt_active)
		.print_to(con);

	Ok(())
}

impl Cmd {
	pub fn run(self, con: &Claw, nunc: &Nunc) -> Result<()> {
		match self {
			Self::Now => cmd_now(con, nunc),
			Self::Recap{offset, timespan} =>
				cmd_recap(con, nunc, offset, timespan),
		}
	}
}

pub(crate) fn cmd_recap(
	con: &Claw,
	nunc: &Nunc,
	offset: u8,
	timespan: RecapTimespan)
	-> Result<()>
{
	let cal = nunc.load_calendar()?;

	let scale = match timespan {
		RecapTimespan::Day => Scale::Days,
		RecapTimespan::Week => Scale::Weeks,
		RecapTimespan::Month => Scale::Months,
		RecapTimespan::All => todo!(),
	};
	let mut frames = cal.frames(scale, Point::now());
	let frame = match offset {
		0 => frames.next().unwrap(),
		n => frames
			.rev()
			.nth(n as usize - 1)
			.unwrap()
	};

	let entries = nunc.recap(frame)?;

	con.print(EntryList::new(nunc, entries.iter())?);

	Ok(())
}