Add 'log' cli command

This commit is contained in:
Joscha 2022-01-08 00:37:55 +01:00
parent 3e2fa54213
commit 0484eda859
8 changed files with 213 additions and 1 deletions

View file

@ -8,7 +8,7 @@ use directories::ProjectDirs;
use structopt::StructOpt;
use crate::eval::{self, DateRange, Entry, EntryMode};
use crate::files::arguments::{CliIdent, CliRange};
use crate::files::arguments::{CliDate, CliIdent, CliRange};
use crate::files::{self, FileSource, Files, ParseError};
use self::error::Error;
@ -18,6 +18,7 @@ mod cancel;
mod done;
mod error;
mod layout;
mod log;
mod print;
mod show;
@ -57,6 +58,11 @@ pub enum Command {
#[structopt(required = true)]
entries: Vec<usize>,
},
/// Edits or creates a log entry
Log {
#[structopt(default_value = "today")]
date: String,
},
/// Reformats all loaded files
Fmt,
}
@ -162,6 +168,12 @@ fn run_command(
let layout = find_layout(files, &entries, range, now);
print::print(&layout);
}
Some(Command::Log { date }) => {
match parse_eval_arg("date", date, |date: CliDate| date.eval((), now.date())) {
Some(date) => log::log(files, date)?,
None => process::exit(1),
};
}
Some(Command::Fmt) => files.mark_all_dirty(),
}
Ok(())

View file

@ -1,3 +1,5 @@
use std::io;
use chrono::NaiveDate;
use codespan_reporting::files::Files;
use codespan_reporting::term::Config;
@ -15,6 +17,8 @@ pub enum Error<S> {
NoSuchLog(NaiveDate),
#[error("Not a task")]
NotATask(Vec<usize>),
#[error("Error editing log for {date}: {error}")]
EditingLog { date: NaiveDate, error: io::Error },
}
impl<'a, F: Files<'a>> Eprint<'a, F> for Error<F::FileId> {
@ -33,6 +37,10 @@ impl<'a, F: Files<'a>> Eprint<'a, F> for Error<F::FileId> {
eprintln!("{} are not tasks.", ns.join(", "));
}
}
Error::EditingLog { date, error } => {
eprintln!("Error editing log for {}", date);
eprintln!(" {}", error);
}
}
}
}

26
src/cli/log.rs Normal file
View file

@ -0,0 +1,26 @@
use chrono::NaiveDate;
use crate::files::Files;
use super::error::Error;
pub fn log<S>(files: &mut Files, date: NaiveDate) -> Result<(), Error<S>> {
let desc = files
.log(date)
.map(|log| log.value.desc.join("\n"))
.unwrap_or_default();
let mut builder = edit::Builder::new();
builder.suffix(".md");
let edited = edit::edit_with_builder(desc, &builder)
.map_err(|error| Error::EditingLog { date, error })?;
let edited = edited
.lines()
.map(|line| line.to_string())
.collect::<Vec<_>>();
files.set_log(date, edited);
Ok(())
}

View file

@ -339,6 +339,21 @@ impl Files {
}
}
fn latest_log(&self) -> Option<(NaiveDate, Source)> {
self.logs
.iter()
.map(|(d, s)| (*d, *s))
.max_by_key(|(d, _)| *d)
}
fn latest_log_before(&self, date: NaiveDate) -> Option<(NaiveDate, Source)> {
self.logs
.iter()
.map(|(d, s)| (*d, *s))
.filter(|(d, _)| d <= &date)
.max_by_key(|(d, _)| *d)
}
pub fn now(&self) -> DateTime<&Tz> {
if let Some(tz) = &self.timezone {
Utc::now().with_timezone(&tz)
@ -355,6 +370,18 @@ impl Files {
}
}
fn modify(&mut self, source: Source, edit: impl FnOnce(&mut Command)) {
let file = &mut self.files[source.file];
edit(&mut file.file.commands[source.command]);
file.dirty = true;
}
fn insert(&mut self, file: FileSource, command: Command) {
let file = &mut self.files[file.0];
file.file.commands.push(command);
file.dirty = true;
}
/// Add a [`Done`] statement to the task identified by `source`.
///
/// Returns whether the addition was successful. It can fail if the entry
@ -370,6 +397,26 @@ impl Files {
true
}
pub fn set_log(&mut self, date: NaiveDate, desc: Vec<String>) {
if let Some(source) = self.logs.get(&date).cloned() {
self.modify(source, |command| match command {
Command::Log(log) => log.desc = desc,
_ => unreachable!(),
});
} else {
let file = self
.latest_log_before(date)
.or_else(|| self.latest_log())
.map(|(_, source)| source.file())
.unwrap_or(FileSource(0));
let date = Spanned::dummy(date);
let command = Command::Log(Log { date, desc });
self.insert(file, command);
}
}
/* Errors */
fn cs_id(&self, file: FileSource) -> usize {

View file

@ -46,6 +46,19 @@ fn parse_cli_date(p: Pair<'_, Rule>) -> Result<CliDate> {
Ok(CliDate { datum, delta })
}
impl FromStr for CliDate {
type Err = ParseError<()>;
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
let mut pairs =
TodayfileParser::parse(Rule::cli_date, s).map_err(|e| ParseError::new((), e))?;
let p = pairs.next().unwrap();
assert_eq!(pairs.next(), None);
parse_cli_date(p).map_err(|e| ParseError::new((), e))
}
}
#[derive(Debug)]
pub enum CliIdent {
Number(usize),

View file

@ -31,6 +31,10 @@ impl Span {
end: cmp::max(self.end, other.end),
}
}
fn dummy() -> Self {
Self { start: 0, end: 0 }
}
}
#[derive(Clone, Copy)]
@ -49,6 +53,10 @@ impl<T> Spanned<T> {
pub fn new(span: Span, value: T) -> Self {
Self { span, value }
}
pub fn dummy(value: T) -> Self {
Self::new(Span::dummy(), value)
}
}
// I don't know how one would write this. It works as a polymorphic standalone