use std::borrow::Borrow;
use std::io::Write;
use std::result::Result as StdResult;
use conciliator::{
Buffer,
Claw,
Conciliator,
print::Formatter,
Inline,
Print,
Paint,
Wrap
};
use conciliator::edit::{
Edit,
Edited,
TomlEditor
};
use rusqlite::{
Row,
Result as SqlResult,
Error as SqlError
};
use greg::Point;
use greg::calendar::{
Calendar,
DateTime
};
use serde::{Serialize, Deserialize};
use crate::{
Nunc,
Result,
Error
};
use crate::cli::arg::{
Activity,
TimeArg
};
use crate::core::{
Tree,
Zone
};
use crate::core::zone::Table as ZoneTable;
use crate::util::format::LookupFormatter;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Entry {
pub id: u64,
pub activity_id: u64,
pub start: Point,
pub stop: Option<Point>
}
pub struct List<'n, I> {
iter: I,
nunc: &'n Nunc,
cal: Calendar<ZoneTable>
}
pub struct Editor<'a> {
claw: &'a Claw,
nunc: &'a Nunc,
cal: Calendar<ZoneTable>,
toml: TomlEditor<EditEntry>,
entry: Entry
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EditEntry {
pub activity: Activity,
pub start: TimeArg,
pub stop: Option<TimeArg>
}
#[derive(Debug)]
pub struct InlineEntry {
pub activity: String,
pub start: DateTime,
pub stop: Option<DateTime>
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct SetEntry {
pub(crate) start: Point,
pub(crate) activity_id: u64,
pub(crate) stop: Option<Point>
}
impl<'n> Formatter<&Entry> for LookupFormatter<'n> {
fn format(&mut self, buf: &mut Buffer, entry: &Entry) {
let activity = self.nunc.get_activity(entry.activity_id).unwrap();
let start = self.cal.lookup(entry.start);
let stop = entry.stop.map(|p| self.cal.lookup(p));
print_oneline(buf, &activity.name, start, stop);
}
}
impl<'n> Formatter<&SetEntry> for LookupFormatter<'n> {
fn format(&mut self, buf: &mut Buffer, entry: &SetEntry) {
let activity = self.nunc.get_activity(entry.activity_id).unwrap();
let start = self.cal.lookup(entry.start);
let stop = entry.stop.map(|p| self.cal.lookup(p));
print_oneline(buf, &activity.name, start, stop);
}
}
impl<'n, E: Borrow<Entry>, I: Iterator<Item = E>> List<'n, I> {
pub fn new(nunc: &'n Nunc, iter: I) -> SqlResult<Self> {
let cal = nunc.load_calendar()?;
Ok(Self{nunc, iter, cal})
}
}
impl<'n, E: Borrow<Entry>, I: Iterator<Item = E>> Print for List<'n, I> {
fn print<C: Conciliator + ?Sized>(self, con: &C) {
let mut date = None;
for entry in self.iter {
let entry: &Entry = entry.borrow();
let activity = self.nunc.get_activity(entry.activity_id).unwrap();
let start = self.cal.lookup(entry.start);
let stop = entry.stop.map(|p| self.cal.lookup(p));
if date != Some(start.0) {
con.get_line();
con.get_line()
.push(start.0)
.push(' ')
.push(self.cal.weekday(entry.start));
con.get_line();
date = Some(start.0);
}
let mut buf = con.get_line();
print_no_date(&mut buf, &activity.name, start, stop);
}
}
}
pub fn print_oneline(
buf: &mut Buffer,
act_name: &str,
start: DateTime,
stop: Option<DateTime>)
{
buf.push_iota(&start.0).push(' ');
print_start_stop(buf, start, stop);
buf.push(Wrap::Beta(act_name));
}
pub fn print_no_date(
buf: &mut Buffer,
act_name: &str,
start: DateTime,
stop: Option<DateTime>)
{
print_start_stop(buf, start, stop);
buf.push(Wrap::Beta(act_name));
}
pub fn print_start_stop(
buf: &mut Buffer,
start: DateTime,
stop: Option<DateTime>)
{
buf.push_plain("[")
.push_plain(&start.1)
.push_plain("] ");
match stop {
Some(stop) if start.0.add_day() == stop.0 => buf
.push_iota("→")
.push_plain(" ")
.push_iota("{")
.push_plain(&stop.1)
.push_iota("}")
.push_plain(": "),
Some(stop) if start.0 == stop.0 => buf
.push_plain("→ [")
.push_plain(&stop.1)
.push_plain("]: "),
Some(stop) => buf
.push_plain("→ ")
.push_iota(&stop.0)
.push_plain(" [")
.push_plain(&stop.1)
.push_plain("]: "),
None => buf.push_plain("→ [ ···· ]: ")
};
}
impl Entry {
pub fn edit(&self, claw: &Claw, nunc: &Nunc) -> Result<Option<Entry>> {
let cal = nunc.load_calendar()?;
let &Self { id: _, activity_id, start, stop } = self;
let activity = nunc.get_activity(activity_id)?.name;
let start = cal.lookup(start);
let stop = stop.map(|s| cal.lookup(s));
let inline = InlineEntry { activity, start, stop };
claw.info(&inline);
let editor = Editor {
claw,
nunc,
cal,
toml: TomlEditor::new(inline.into_edit()),
entry: self.clone()
};
match claw.edit(editor) {
Edited::Ok((edited, inline)) => {
claw.info(&inline);
match claw.confirm(false, "Apply these changes?") {
true => Ok(Some(edited)),
false => {
claw.info("Entry not updated!");
Ok(None)
}
}
},
Edited::Cancelled => {
claw.info("Edit aborted");
Ok(None)
},
Edited::Err(err) => Err(err)
}
}
}
impl<'r> TryFrom<&Row<'r>> for Entry {
type Error = SqlError;
fn try_from(value: &Row<'r>) -> StdResult<Self, Self::Error> {
value.try_into().map(|(id, activity_id, start, stop)|
Self {id, activity_id, start, stop}
)
}
}
impl<'a> Edit for Editor<'a> {
const EXTENSION: &'static str = TomlEditor::<()>::EXTENSION;
type T = (Entry, InlineEntry);
type Err = Error;
fn write<W: Write>(&self, w: &mut W) -> Result<()> {
self.toml.write(w)?;
Ok(())
}
fn read<C>(&self, content: String, con: &C) -> Option<Self::T>
where C: Conciliator + ?Sized
{
let edit_entry = self.toml.read(content, con)?;
let EditEntry {activity, start, stop} = edit_entry;
let try_block = || -> Result<(Entry, String)>{
let activity = activity.validate(self.nunc, self.claw)?;
let start = start.resolve_with(&self.cal);
let stop = stop.map(|s| s.resolve_with(&self.cal));
let edited = Entry {
id: self.entry.id,
activity_id: activity.id,
start,
stop
};
Ok((edited, activity.name))
};
let (edited, activity) = match try_block() {
Ok((ent, act)) => (ent, act),
Err(e) => {
con.error(e);
return None;
}
};
if edited == self.entry {
con.warn("Entry has not been changed!");
None
}
else {
let start = self.cal.lookup(edited.start);
let stop = edited.stop.map(|p| self.cal.lookup(p));
let inline_entry = InlineEntry {activity, start, stop};
Some((edited, inline_entry))
}
}
}
impl InlineEntry {
pub fn into_edit(self) -> EditEntry {
let Self { activity, start, stop } = self;
let activity = Activity(activity);
let start = TimeArg::DateTime(start);
let stop = stop.map(TimeArg::DateTime);
EditEntry { activity, start, stop }
}
}
impl Inline for InlineEntry {
fn inline(&self, buffer: &mut Buffer) {
let Self { ref activity, start, stop } = *self;
print_oneline(buffer, activity, start, stop);
}
}