nunc 0.2.0

WIP: time tracking application
Documentation
use std::ops::{
	Bound,
	RangeBounds
};

use conciliator::{
	Inline,
	Buffer,
	Print,
	Conciliator,
	Paint
};
use rusqlite::{
	Row,
	Error as SqlError
};
use rusqlite::types::FromSqlError;

use greg::{
	Point,
	Calendar
};
use greg_tz::{
	ZONES,
	Zone
};

use crate::util::TryFrames;

pub struct TimeZone {
	pub id: u64,
	pub zone: Zone
}

pub struct Entry {
	start: Point,
	stop: Option<Point>,
	zone: Zone
}

pub struct List<'z> {
	zones: &'z [(Point, Zone)]
}

/*
 *	TIME ZONE
 */

impl<'r> TryFrom<&Row<'r>> for TimeZone {
	type Error = SqlError;

	fn try_from(value: &Row<'r>) -> Result<Self, Self::Error> {
		let (id, name): (u64, String) = value.try_into()?;
		let zone = *ZONES.get(&name)
			.ok_or_else(|| format!("invalid zone {name}"))
			.map_err(Into::into)
			.map_err(FromSqlError::Other)?;
		Ok(Self {id, zone})
	}
}

/*
 *	ENTRY
 */

impl RangeBounds<Point> for Entry {
	fn start_bound(&self) -> Bound<&Point> {Bound::Included(&self.start)}
	fn end_bound(&self) -> Bound<&Point> {
		self.stop.as_ref().map_or(Bound::Unbounded, Bound::Excluded)
	}
}

/*
 *	LIST
 */

impl<'z> List<'z> {
	pub fn new(zones: &'z [(Point, Zone)]) -> Self {Self {zones}}
	pub fn current(&self) -> Option<Entry> {
		let now = Point::now();
		TryFrames::new(self.zones.iter().copied())
			.map(|(start, stop, zone)| Entry {start, stop, zone})
			.find(|ent| ent.contains(&now))
	}
}

impl<'z> Print for List<'z> {
	fn print<C: Conciliator + ?Sized>(self, con: &C) {
		let frames = TryFrames::new(self.zones.iter().copied());

		match frames.len() {
			0 => {con.warn("No time zones recorded!");},
			1 => {con.status("1 time zone recorded:");},
			n => {con.status(..).push(n).push(" time zones recorded:");}
		};

		for (start, stop, zone) in frames {
			con.line("      ").push(Entry{start, stop, zone});
		}

	}
}

impl Inline for Entry {
	fn inline(&self, buffer: &mut Buffer) {
		let cal = Calendar(self.zone);
		let start = cal.lookup(self.start);
		buffer.push_iota(self.zone.name());
		match self.stop {
			Some(stop) => buffer
				.push(" ")
				.push(start)
				.push("")
				.push(cal.lookup(stop)),
			None => buffer
				.push(" since ")
				.push(start)
		};
	}
}