use std::convert::Infallible;
use std::fmt;
use std::str::FromStr;
use conciliator::{
Claw,
input::Select,
List,
Conciliator
};
use greg::{
Point,
Frame,
Span,
real::ParseSpanError
};
use greg::calendar::{
Date,
Time,
DateTime,
Calendar,
ParseError
};
use greg::calendar::zone::Unsteady;
use crate::Nunc;
use crate::core::{
Log,
Tree,
Zone
};
use crate::data;
use crate::Error;
use crate::util::format::LookupFormatter;
#[derive(Clone, Debug)]
pub struct Activity(pub(crate) String);
#[derive(Clone, Debug)]
pub struct Category(pub(crate) String);
#[derive(Clone, Copy, Debug)]
pub enum TimeArg {
Date(Date),
DateTime(DateTime),
Time(Time),
Relative(Span)
}
#[derive(Clone, Debug)]
pub enum ParseTimeError {
Offset(ParseSpanError),
Absolute(ParseError)
}
pub fn parse_span(s: &str) -> Result<Span, String> {
s.parse::<Span>().map_err(|err| err.to_string())
}
pub fn select_entry(
con: &Claw,
nunc: &Nunc,
offset: u8,
when: Option<TimeArg>,
act: Option<Activity>)
-> Result<data::Entry, Error>
{
let act = act.map(|act| act.validate(nunc, con)).transpose()?;
let when = when
.map(|when| nunc.load_zone_table().map(|tb| tb.resolve(when)))
.transpose()?;
let mut ents = match (offset, when, act) {
(0, None, None) => nunc.get_last_entries(9)?,
(0, when, act) => {
let from = when.unwrap_or_else(Point::now) - Span::HOUR * 3;
let frame = Frame::from_point(from, Span::HOUR * 6);
let mut ents = nunc.recap(frame)?;
if let Some(act) = act {
ents.retain(|e| e.activity_id == act.id);
}
ents
},
(_n, Some(_), _) |
(_n, _, Some(_)) => ret_errmsg!("Incompatible options!"),
(n, None, None) => return nunc
.get_last_entries(n as usize)?
.pop()
.ok_or_else(|| Error::Message("No such entry".to_string())),
};
match ents.len() {
0 => ret_errmsg!("No matching entries!"),
1 => Ok(ents.pop().unwrap()),
_ => {
let list = List::indexed(ents.iter())
.with_formatter(LookupFormatter::new(nunc)?)
.with_count("entries");
let sel = Select::new(list, "Select entry");
Ok(ents.swap_remove(con.input(sel)))
}
}
}
impl Activity {
pub fn validate(
self,
nunc: &Nunc,
con: &Claw)
-> Result<data::Activity, Error>
{
let mut acts = nunc.find_activities(self.0.as_str())?;
match acts.len() {
0 => ret_errmsg!("Activity {:?} not found!", self.0),
1 => Ok(acts.pop().unwrap()),
_ => {
con.info("Activity ")
.push(&self.0)
.push(" is ambiguous");
let resolved = acts.into_iter()
.map(|act| {
let path = nunc.get_path_string(act.category_id)?;
Ok(act.resolve(path))
})
.collect::<Result<Vec<_>, Error>>()?;
let act = con.select(
resolved,
"ambiguous activities",
"Select activity"
).unwrap().revert();
Ok(act)
}
}
}
}
impl FromStr for Activity {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {Ok(Self(s.to_string()))}
}
impl fmt::Display for Activity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
serde_as_str!(Activity, "Activity");
impl Category {
pub fn validate(
self,
nunc: &Nunc,
con: &Claw)
-> Result<data::Category, Error>
{
let mut cats = nunc.find_categories(self.0.as_str())?;
match cats.len() {
0 => ret_errmsg!("Category {:?} not found!", self.0),
1 => Ok(cats.pop().unwrap()),
_ => {
con.info("Category ")
.push(&self.0)
.push(" is ambiguous");
let resolved = cats.into_iter()
.map(|cat| {
let path = nunc.resolve_path_string(&cat.path)?;
Ok(cat.resolve(path))
})
.collect::<Result<Vec<_>, Error>>()?;
let cat = con.select(
resolved,
"ambiguous categories",
"Select category"
).unwrap().revert();
Ok(cat)
}
}
}
}
impl FromStr for Category {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {Ok(Self(s.to_string()))}
}
impl TimeArg {
pub fn resolve_with<Z: Unsteady>(self, cal: &Calendar<Z>) -> Point {
match self {
Self::Relative(sp) => Point::now() - sp,
Self::DateTime(dt) => cal.resolve_lossy(dt),
Self::Date(d) => cal.resolve_midnight_lossy(d),
Self::Time(t) => {
let today = cal.lookup(Point::now()).0;
let yesterday = today.sub_day();
let tomorrow = today.add_day();
[yesterday, today, tomorrow].into_iter()
.map(|d| DateTime(d, t))
.map(|dt| cal.resolve_lossy(dt))
.min_by_key(|&p| Point::now() - p)
.unwrap()
}
}
}
}
impl fmt::Display for TimeArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TimeArg::Date(d) => d.fmt(f),
TimeArg::DateTime(dt) => dt.fmt(f),
TimeArg::Time(t) => t.fmt(f),
TimeArg::Relative(s) => s.fmt(f)
}
}
}
serde_as_str!(TimeArg, "absolute or relative time argument");
impl FromStr for TimeArg {
type Err = ParseTimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"" => Ok(Self::Relative(Span::ZERO)),
s if s.contains(['T', ' ']) => Ok(Self::DateTime(s.parse()?)),
s if s.contains('-') => Ok(Self::Date(s.parse()?)),
s if s.contains(':') => Ok(Self::Time(s.parse()?)),
s => Ok(Self::Relative(s.parse()?))
}
}
}
impl From<ParseError> for ParseTimeError {
fn from(e: ParseError) -> Self {Self::Absolute(e)}
}
impl From<ParseSpanError> for ParseTimeError {
fn from(e: ParseSpanError) -> Self {Self::Offset(e)}
}
impl fmt::Display for ParseTimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Offset(e) => fmt::Display::fmt(e, f),
Self::Absolute(e) => fmt::Display::fmt(e, f)
}
}
}
impl std::error::Error for ParseTimeError {}