Add 'log' cli command
This commit is contained in:
parent
3e2fa54213
commit
0484eda859
8 changed files with 213 additions and 1 deletions
97
Cargo.lock
generated
97
Cargo.lock
generated
|
|
@ -157,6 +157,22 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "edit"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3cdd6936f8bd9782e28932eef853bfcd8548992ce5748bb3e7e88bad613d0ee0"
|
||||||
|
dependencies = [
|
||||||
|
"tempfile",
|
||||||
|
"which",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fake-simd"
|
name = "fake-simd"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
|
@ -287,6 +303,12 @@ dependencies = [
|
||||||
"sha-1",
|
"sha-1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -329,6 +351,46 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
"rand_hc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_hc"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
|
|
@ -348,6 +410,15 @@ dependencies = [
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remove_dir_all"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
@ -401,6 +472,20 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"rand",
|
||||||
|
"redox_syscall",
|
||||||
|
"remove_dir_all",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
|
@ -459,6 +544,7 @@ dependencies = [
|
||||||
"colored",
|
"colored",
|
||||||
"computus",
|
"computus",
|
||||||
"directories",
|
"directories",
|
||||||
|
"edit",
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"structopt",
|
"structopt",
|
||||||
|
|
@ -524,6 +610,17 @@ version = "0.10.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "which"
|
||||||
|
version = "4.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ codespan-reporting = "0.11.1"
|
||||||
colored = "2.0.0"
|
colored = "2.0.0"
|
||||||
computus = "1.0.0"
|
computus = "1.0.0"
|
||||||
directories = "4.0.1"
|
directories = "4.0.1"
|
||||||
|
edit = "0.1.3"
|
||||||
pest = "2.1.3"
|
pest = "2.1.3"
|
||||||
pest_derive = "2.1.0"
|
pest_derive = "2.1.0"
|
||||||
structopt = "0.3.25"
|
structopt = "0.3.25"
|
||||||
|
|
|
||||||
14
src/cli.rs
14
src/cli.rs
|
|
@ -8,7 +8,7 @@ use directories::ProjectDirs;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use crate::eval::{self, DateRange, Entry, EntryMode};
|
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 crate::files::{self, FileSource, Files, ParseError};
|
||||||
|
|
||||||
use self::error::Error;
|
use self::error::Error;
|
||||||
|
|
@ -18,6 +18,7 @@ mod cancel;
|
||||||
mod done;
|
mod done;
|
||||||
mod error;
|
mod error;
|
||||||
mod layout;
|
mod layout;
|
||||||
|
mod log;
|
||||||
mod print;
|
mod print;
|
||||||
mod show;
|
mod show;
|
||||||
|
|
||||||
|
|
@ -57,6 +58,11 @@ pub enum Command {
|
||||||
#[structopt(required = true)]
|
#[structopt(required = true)]
|
||||||
entries: Vec<usize>,
|
entries: Vec<usize>,
|
||||||
},
|
},
|
||||||
|
/// Edits or creates a log entry
|
||||||
|
Log {
|
||||||
|
#[structopt(default_value = "today")]
|
||||||
|
date: String,
|
||||||
|
},
|
||||||
/// Reformats all loaded files
|
/// Reformats all loaded files
|
||||||
Fmt,
|
Fmt,
|
||||||
}
|
}
|
||||||
|
|
@ -162,6 +168,12 @@ fn run_command(
|
||||||
let layout = find_layout(files, &entries, range, now);
|
let layout = find_layout(files, &entries, range, now);
|
||||||
print::print(&layout);
|
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(),
|
Some(Command::Fmt) => files.mark_all_dirty(),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use codespan_reporting::files::Files;
|
use codespan_reporting::files::Files;
|
||||||
use codespan_reporting::term::Config;
|
use codespan_reporting::term::Config;
|
||||||
|
|
@ -15,6 +17,8 @@ pub enum Error<S> {
|
||||||
NoSuchLog(NaiveDate),
|
NoSuchLog(NaiveDate),
|
||||||
#[error("Not a task")]
|
#[error("Not a task")]
|
||||||
NotATask(Vec<usize>),
|
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> {
|
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(", "));
|
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
26
src/cli/log.rs
Normal 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(())
|
||||||
|
}
|
||||||
47
src/files.rs
47
src/files.rs
|
|
@ -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> {
|
pub fn now(&self) -> DateTime<&Tz> {
|
||||||
if let Some(tz) = &self.timezone {
|
if let Some(tz) = &self.timezone {
|
||||||
Utc::now().with_timezone(&tz)
|
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`.
|
/// Add a [`Done`] statement to the task identified by `source`.
|
||||||
///
|
///
|
||||||
/// Returns whether the addition was successful. It can fail if the entry
|
/// Returns whether the addition was successful. It can fail if the entry
|
||||||
|
|
@ -370,6 +397,26 @@ impl Files {
|
||||||
true
|
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 */
|
/* Errors */
|
||||||
|
|
||||||
fn cs_id(&self, file: FileSource) -> usize {
|
fn cs_id(&self, file: FileSource) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,19 @@ fn parse_cli_date(p: Pair<'_, Rule>) -> Result<CliDate> {
|
||||||
Ok(CliDate { datum, delta })
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum CliIdent {
|
pub enum CliIdent {
|
||||||
Number(usize),
|
Number(usize),
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ impl Span {
|
||||||
end: cmp::max(self.end, other.end),
|
end: cmp::max(self.end, other.end),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dummy() -> Self {
|
||||||
|
Self { start: 0, end: 0 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|
@ -49,6 +53,10 @@ impl<T> Spanned<T> {
|
||||||
pub fn new(span: Span, value: T) -> Self {
|
pub fn new(span: Span, value: T) -> Self {
|
||||||
Self { span, value }
|
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
|
// I don't know how one would write this. It works as a polymorphic standalone
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue