Compare commits

..

No commits in common. "master" and "v0.2.0" have entirely different histories.

26 changed files with 918 additions and 1372 deletions

View file

@ -4,16 +4,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased ## Unreleased
### Added
- Marks to show which span a reminder belongs to
### Changed
- Birthdays for current day are now highlighted
- Default value for `--range` argument
### Fixed
- `--date` accepting incomplete expressions
## 0.2.0 - 2022-03-18 ## 0.2.0 - 2022-03-18
### Added ### Added

740
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,32 +4,16 @@ version = "0.2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
chrono = "0.4.23" chrono = "0.4.19"
clap = { version = "4.1.4", features = ["derive"] }
codespan-reporting = "0.11.1" 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.4" edit = "0.1.3"
pest = "2.5.5" pest = "2.1.3"
pest_derive = "2.5.5" pest_derive = "2.1.0"
promptly = "0.3.1" promptly = "0.3.0"
termcolor = "1.2.0" structopt = "0.3.26"
thiserror = "1.0.38" termcolor = "1.1.3"
thiserror = "1.0.30"
tzfile = { git = "https://github.com/Garmelon/tzfile.git", branch = "tzdir" } tzfile = { git = "https://github.com/Garmelon/tzfile.git", branch = "tzdir" }
[lints]
rust.unsafe_code = { level = "forbid", priority = 1 }
rust.future_incompatible = "warn"
rust.rust_2018_idioms = "warn"
rust.noop_method_call = "warn"
rust.single_use_lifetimes = "warn"
rust.trivial_numeric_casts = "warn"
rust.unused = "warn"
rust.unused_crate_dependencies = "warn"
rust.unused_extern_crates = "warn"
rust.unused_import_braces = "warn"
rust.unused_lifetimes = "warn"
rust.unused_qualifications = "warn"
clippy.all = "warn"
clippy.use_self = "warn"

View file

@ -3,9 +3,9 @@ use std::str::FromStr;
use std::{process, result}; use std::{process, result};
use chrono::{NaiveDate, NaiveDateTime}; use chrono::{NaiveDate, NaiveDateTime};
use clap::Parser;
use codespan_reporting::files::SimpleFile; use codespan_reporting::files::SimpleFile;
use directories::ProjectDirs; use directories::ProjectDirs;
use structopt::StructOpt;
use crate::eval::{self, DateRange, Entry, EntryMode}; use crate::eval::{self, DateRange, Entry, EntryMode};
use crate::files::cli::{CliDate, CliIdent, CliRange}; use crate::files::cli::{CliDate, CliIdent, CliRange};
@ -24,76 +24,77 @@ mod print;
mod show; mod show;
mod util; mod util;
#[derive(Debug, clap::Parser)] #[derive(Debug, StructOpt)]
pub struct Opt { pub struct Opt {
/// File to load /// File to load
#[clap(short, long)] #[structopt(short, long, parse(from_os_str))]
file: Option<PathBuf>, file: Option<PathBuf>,
/// Overwrite the current date /// Overwrite the current date
#[clap(short, long, default_value = "t")] #[structopt(short, long, default_value = "t")]
date: String, date: String,
/// Range of days to focus on /// Range of days to focus on
#[clap(short, long, default_value = "t-2d--t+2w")] #[structopt(short, long, default_value = "t-2d--t+13d")]
range: String, range: String,
#[clap(subcommand)] #[structopt(subcommand)]
command: Option<Command>, command: Option<Command>,
} }
#[derive(Debug, clap::Subcommand)] #[derive(Debug, StructOpt)]
pub enum Command { pub enum Command {
/// Shows individual entries in detail /// Shows individual entries in detail
#[clap(alias = "s")] #[structopt(alias = "s")]
Show { Show {
/// Entries and days to show /// Entries and days to show
#[clap(required = true)] #[structopt(required = true)]
identifiers: Vec<String>, identifiers: Vec<String>,
}, },
/// Create a new entry based on a template /// Create a new entry based on a template
#[clap(alias = "n")] #[structopt(alias = "n")]
New { New {
#[clap(subcommand)] #[structopt(subcommand)]
template: Template, template: Template,
}, },
/// Marks one or more entries as done /// Marks one or more entries as done
#[clap(alias = "d")] #[structopt(alias = "d")]
Done { Done {
/// Entries to mark as done /// Entries to mark as done
#[clap(required = true)] #[structopt(required = true)]
entries: Vec<usize>, entries: Vec<usize>,
}, },
/// Marks one or more entries as canceled /// Marks one or more entries as canceled
#[clap(alias = "c")] #[structopt(alias = "c")]
Cancel { Cancel {
/// Entries to mark as done /// Entries to mark as done
#[clap(required = true)] #[structopt(required = true)]
entries: Vec<usize>, entries: Vec<usize>,
}, },
/// Edits or creates a log entry /// Edits or creates a log entry
#[clap(alias = "l")] #[structopt(alias = "l")]
Log { Log {
#[clap(default_value = "t")] #[structopt(default_value = "t")]
date: String, date: String,
}, },
/// Reformats all loaded files /// Reformats all loaded files
Fmt, Fmt,
} }
#[derive(Debug, clap::Subcommand)] // TODO Add templates for tasks and notes
#[derive(Debug, StructOpt)]
pub enum Template { pub enum Template {
/// Adds a task /// Adds a task
#[clap(alias = "t")] #[structopt(alias = "t")]
Task { Task {
/// If specified, the task is dated to this date /// If specified, the task is dated to this date
date: Option<String>, date: Option<String>,
}, },
/// Adds a note /// Adds a note
#[clap(alias = "n")] #[structopt(alias = "n")]
Note { Note {
/// If specified, the note is dated to this date /// If specified, the note is dated to this date
date: Option<String>, date: Option<String>,
}, },
/// Adds an undated task marked as done today /// Adds an undated task marked as done today
#[clap(alias = "d")] #[structopt(alias = "d")]
Done, Done,
} }
@ -221,7 +222,7 @@ fn run_with_files(opt: Opt, files: &mut Files) -> Result<()> {
} }
pub fn run() { pub fn run() {
let opt = Opt::parse(); let opt = Opt::from_args();
let mut files = Files::new(); let mut files = Files::new();
if let Err(e) = load_files(&opt, &mut files) { if let Err(e) = load_files(&opt, &mut files) {

View file

@ -40,15 +40,14 @@ impl<'a, F> Eprint<'a, F> for Error
where where
F: Files<'a, FileId = FileSource>, F: Files<'a, FileId = FileSource>,
{ {
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) { fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
match self { match self {
Self::Eval(e) => e.eprint(files, config), Error::Eval(e) => e.eprint(files, config),
Self::ArgumentParse { file, error } => error.eprint(file, config), Error::ArgumentParse { file, error } => error.eprint(file, config),
Self::ArgumentEval { file, error } => error.eprint(file, config), Error::ArgumentEval { file, error } => error.eprint(file, config),
Self::NoSuchEntry(n) => eprintln!("No entry with number {n}"), Error::NoSuchEntry(n) => eprintln!("No entry with number {n}"),
Self::NoSuchLog(date) => eprintln!("No log for {date}"), Error::NoSuchLog(date) => eprintln!("No log for {date}"),
Self::NotATask(ns) => { Error::NotATask(ns) => {
if ns.is_empty() { if ns.is_empty() {
eprintln!("Not a task."); eprintln!("Not a task.");
} else if ns.len() == 1 { } else if ns.len() == 1 {
@ -58,8 +57,8 @@ where
eprintln!("{} are not tasks.", ns.join(", ")); eprintln!("{} are not tasks.", ns.join(", "));
} }
} }
Self::NoCaptureFile => eprintln!("No capture file found"), Error::NoCaptureFile => eprintln!("No capture file found"),
Self::EditingIo(error) => { Error::EditingIo(error) => {
eprintln!("Error while editing:"); eprintln!("Error while editing:");
eprintln!(" {error}"); eprintln!(" {error}");
} }

View file

@ -189,7 +189,7 @@ impl DayLayout {
} }
} }
fn sort_entries(entries: &mut [(usize, &Entry)]) { fn sort_entries(entries: &mut Vec<(usize, &Entry)>) {
// Entries should be sorted by these factors, in descending order of // Entries should be sorted by these factors, in descending order of
// significance: // significance:
// 1. Their start date, if any // 1. Their start date, if any
@ -219,7 +219,7 @@ impl DayLayout {
entries.sort_by_key(|(_, e)| e.dates.map(|d| d.sorted().root_with_time())); entries.sort_by_key(|(_, e)| e.dates.map(|d| d.sorted().root_with_time()));
} }
fn sort_day(day: &mut [DayEntry]) { fn sort_day(day: &mut Vec<DayEntry>) {
// In a day, entries should be sorted into these categories: // In a day, entries should be sorted into these categories:
// 1. Untimed entries that end at the current day // 1. Untimed entries that end at the current day
// 2. Timed entries, based on // 2. Timed entries, based on

View file

@ -36,17 +36,15 @@ impl SpanStyle {
pub enum SpanSegment { pub enum SpanSegment {
Start(SpanStyle), Start(SpanStyle),
Middle(SpanStyle), Middle(SpanStyle),
Mark(SpanStyle),
End(SpanStyle), End(SpanStyle),
} }
impl SpanSegment { impl SpanSegment {
fn style(&self) -> SpanStyle { fn style(&self) -> SpanStyle {
match self { match self {
Self::Start(s) => *s, SpanSegment::Start(s) => *s,
Self::Middle(s) => *s, SpanSegment::Middle(s) => *s,
Self::Mark(s) => *s, SpanSegment::End(s) => *s,
Self::End(s) => *s,
} }
} }
} }
@ -58,7 +56,7 @@ pub enum Times {
FromTo(Time, Time), FromTo(Time, Time),
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy)]
pub enum LineKind { pub enum LineKind {
Task, Task,
Done, Done,
@ -81,7 +79,6 @@ pub enum LineEntry {
Entry { Entry {
number: Option<usize>, number: Option<usize>,
spans: Vec<Option<SpanSegment>>, spans: Vec<Option<SpanSegment>>,
today: bool,
time: Times, time: Times,
kind: LineKind, kind: LineKind,
text: String, text: String,
@ -124,18 +121,17 @@ impl LineLayout {
self.step_spans(); self.step_spans();
for day in layout.range.days() { for day in layout.range.days() {
let today = day == layout.today;
let spans = self.spans_for_line(); let spans = self.spans_for_line();
self.line(LineEntry::Day { self.line(LineEntry::Day {
spans, spans,
date: day, date: day,
today, today: day == layout.today,
has_log: files.log(day).is_some(), has_log: files.log(day).is_some(),
}); });
let layout_entries = layout.days.get(&day).expect("got nonexisting day"); let layout_entries = layout.days.get(&day).expect("got nonexisting day");
for layout_entry in layout_entries { for layout_entry in layout_entries {
self.render_layout_entry(entries, layout_entry, today); self.render_layout_entry(entries, layout_entry);
} }
} }
} }
@ -161,11 +157,11 @@ impl LineLayout {
.ok_or(Error::NoSuchEntry(number)) .ok_or(Error::NoSuchEntry(number))
} }
fn render_layout_entry(&mut self, entries: &[Entry], l_entry: &DayEntry, today: bool) { fn render_layout_entry(&mut self, entries: &[Entry], l_entry: &DayEntry) {
match l_entry { match l_entry {
DayEntry::End(i) => { DayEntry::End(i) => {
self.stop_span(*i); self.stop_span(*i);
self.line_entry(entries, *i, today, Times::Untimed, None); self.line_entry(entries, *i, Times::Untimed, None);
} }
DayEntry::Now(t) => self.line(LineEntry::Now { DayEntry::Now(t) => self.line(LineEntry::Now {
spans: self.spans_for_line(), spans: self.spans_for_line(),
@ -173,15 +169,17 @@ impl LineLayout {
}), }),
DayEntry::TimedEnd(i, t) => { DayEntry::TimedEnd(i, t) => {
self.stop_span(*i); self.stop_span(*i);
self.line_entry(entries, *i, today, Times::At(*t), None); self.line_entry(entries, *i, Times::At(*t), None);
} }
DayEntry::TimedAt(i, t, t2) => { DayEntry::TimedAt(i, t, t2) => {
let time = t2.map(|t2| Times::FromTo(*t, t2)).unwrap_or(Times::At(*t)); let time = t2
self.line_entry(entries, *i, today, time, None); .map(|t2| Times::FromTo(*t, t2))
.unwrap_or_else(|| Times::At(*t));
self.line_entry(entries, *i, time, None);
} }
DayEntry::TimedStart(i, t) => { DayEntry::TimedStart(i, t) => {
self.start_span(*i); self.start_span(*i);
self.line_entry(entries, *i, today, Times::At(*t), None); self.line_entry(entries, *i, Times::At(*t), None);
} }
DayEntry::ReminderSince(i, d) => { DayEntry::ReminderSince(i, d) => {
let extra = if *d == 1 { let extra = if *d == 1 {
@ -189,23 +187,22 @@ impl LineLayout {
} else { } else {
format!("{d} days ago") format!("{d} days ago")
}; };
self.line_entry(entries, *i, today, Times::Untimed, Some(extra)); self.line_entry(entries, *i, Times::Untimed, Some(extra));
} }
DayEntry::At(i) => { DayEntry::At(i) => {
self.line_entry(entries, *i, today, Times::Untimed, None); self.line_entry(entries, *i, Times::Untimed, None);
} }
DayEntry::ReminderWhile(i, d) => { DayEntry::ReminderWhile(i, d) => {
let plural = if *d == 1 { "" } else { "s" }; let plural = if *d == 1 { "" } else { "s" };
let extra = format!("{d} day{plural} left"); let extra = format!("{d} day{plural} left");
self.mark_span(*i); self.line_entry(entries, *i, Times::Untimed, Some(extra));
self.line_entry(entries, *i, today, Times::Untimed, Some(extra));
} }
DayEntry::Undated(i) => { DayEntry::Undated(i) => {
self.line_entry(entries, *i, today, Times::Untimed, None); self.line_entry(entries, *i, Times::Untimed, None);
} }
DayEntry::Start(i) => { DayEntry::Start(i) => {
self.start_span(*i); self.start_span(*i);
self.line_entry(entries, *i, today, Times::Untimed, None); self.line_entry(entries, *i, Times::Untimed, None);
} }
DayEntry::ReminderUntil(i, d) => { DayEntry::ReminderUntil(i, d) => {
let extra = if *d == 1 { let extra = if *d == 1 {
@ -213,7 +210,7 @@ impl LineLayout {
} else { } else {
format!("in {d} days") format!("in {d} days")
}; };
self.line_entry(entries, *i, today, Times::Untimed, Some(extra)); self.line_entry(entries, *i, Times::Untimed, Some(extra));
} }
} }
} }
@ -249,15 +246,6 @@ impl LineLayout {
self.spans.push(Some((index, SpanSegment::Start(style)))); self.spans.push(Some((index, SpanSegment::Start(style))));
} }
fn mark_span(&mut self, index: usize) {
for span in self.spans.iter_mut() {
match span {
Some((i, s)) if *i == index => *s = SpanSegment::Mark(s.style()),
_ => {}
}
}
}
fn stop_span(&mut self, index: usize) { fn stop_span(&mut self, index: usize) {
for span in self.spans.iter_mut() { for span in self.spans.iter_mut() {
match span { match span {
@ -270,9 +258,7 @@ impl LineLayout {
fn step_spans(&mut self) { fn step_spans(&mut self) {
for span in self.spans.iter_mut() { for span in self.spans.iter_mut() {
match span { match span {
Some((_, s @ (SpanSegment::Start(_) | SpanSegment::Mark(_)))) => { Some((_, s @ SpanSegment::Start(_))) => *s = SpanSegment::Middle(s.style()),
*s = SpanSegment::Middle(s.style())
}
Some((_, SpanSegment::End(_))) => *span = None, Some((_, SpanSegment::End(_))) => *span = None,
_ => {} _ => {}
} }
@ -291,14 +277,7 @@ impl LineLayout {
self.step_spans(); self.step_spans();
} }
fn line_entry( fn line_entry(&mut self, entries: &[Entry], index: usize, time: Times, extra: Option<String>) {
&mut self,
entries: &[Entry],
index: usize,
today: bool,
time: Times,
extra: Option<String>,
) {
let entry = &entries[index]; let entry = &entries[index];
let number = match self.numbers.get(&index) { let number = match self.numbers.get(&index) {
@ -313,7 +292,6 @@ impl LineLayout {
self.line(LineEntry::Entry { self.line(LineEntry::Entry {
number: Some(number), number: Some(number),
spans: self.spans_for_line(), spans: self.spans_for_line(),
today,
time, time,
kind: Self::entry_kind(entry), kind: Self::entry_kind(entry),
text: Self::entry_title(entry), text: Self::entry_title(entry),

View file

@ -35,14 +35,12 @@ impl ShowLines {
LineEntry::Entry { LineEntry::Entry {
number, number,
spans, spans,
today,
time, time,
kind, kind,
text, text,
has_desc, has_desc,
extra, extra,
} => self } => self.display_line_entry(*number, spans, *time, *kind, text, *has_desc, extra),
.display_line_entry(*number, spans, *today, *time, *kind, text, *has_desc, extra),
} }
} }
@ -101,7 +99,6 @@ impl ShowLines {
&mut self, &mut self,
number: Option<usize>, number: Option<usize>,
spans: &[Option<SpanSegment>], spans: &[Option<SpanSegment>],
today: bool,
time: Times, time: Times,
kind: LineKind, kind: LineKind,
text: &str, text: &str,
@ -113,12 +110,6 @@ impl ShowLines {
None => "".to_string(), None => "".to_string(),
}; };
let text = if kind == LineKind::Birthday && today {
util::display_current_birthday_text(text)
} else {
text.into()
};
self.push(&format!( self.push(&format!(
"{:>nw$} {} {}{} {}{}{}\n", "{:>nw$} {} {}{} {}{}{}\n",
num.bright_black(), num.bright_black(),
@ -141,7 +132,6 @@ impl ShowLines {
SpanSegment::Middle(SpanStyle::Solid) => "".bright_black(), SpanSegment::Middle(SpanStyle::Solid) => "".bright_black(),
SpanSegment::Middle(SpanStyle::Dashed) => "".bright_black(), SpanSegment::Middle(SpanStyle::Dashed) => "".bright_black(),
SpanSegment::Middle(SpanStyle::Dotted) => "".bright_black(), SpanSegment::Middle(SpanStyle::Dotted) => "".bright_black(),
SpanSegment::Mark(_) => "".bright_black(),
SpanSegment::End(_) => "".bright_black(), SpanSegment::End(_) => "".bright_black(),
}; };
result.push_str(&format!("{colored_str}")); result.push_str(&format!("{colored_str}"));

View file

@ -13,10 +13,6 @@ pub fn display_kind(kind: LineKind) -> ColoredString {
} }
} }
pub fn display_current_birthday_text(text: &str) -> ColoredString {
text.yellow()
}
pub fn edit(input: &str) -> Result<String> { pub fn edit(input: &str) -> Result<String> {
edit::edit(input).map_err(Error::EditingIo) edit::edit(input).map_err(Error::EditingIo)
} }

View file

@ -4,7 +4,6 @@ use codespan_reporting::term::{self, Config};
use termcolor::StandardStream; use termcolor::StandardStream;
pub trait Eprint<'a, F: Files<'a>> { pub trait Eprint<'a, F: Files<'a>> {
#[allow(single_use_lifetimes)]
fn eprint_diagnostic<'f: 'a>( fn eprint_diagnostic<'f: 'a>(
files: &'f F, files: &'f F,
config: &Config, config: &Config,
@ -16,11 +15,9 @@ pub trait Eprint<'a, F: Files<'a>> {
} }
} }
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config); fn eprint<'f: 'a>(&self, files: &'f F, config: &Config);
} }
#[allow(single_use_lifetimes)]
pub fn eprint_error<'a, 'f: 'a, F, E>(files: &'f F, e: &E) pub fn eprint_error<'a, 'f: 'a, F, E>(files: &'f F, e: &E)
where where
F: Files<'a>, F: Files<'a>,

View file

@ -8,7 +8,7 @@ use super::super::date::Dates;
use super::super::error::Error; use super::super::error::Error;
use super::super::EntryKind; use super::super::EntryKind;
impl CommandState<'_> { impl<'a> CommandState<'a> {
pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<(), Error<FileSource>> { pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<(), Error<FileSource>> {
let range = match self.limit_from_until(self.range_with_remind()) { let range = match self.limit_from_until(self.range_with_remind()) {
Some(range) => range, Some(range) => range,
@ -35,8 +35,8 @@ impl CommandState<'_> {
assert_eq!(spec.date.month(), 2); assert_eq!(spec.date.month(), 2);
assert_eq!(spec.date.day(), 29); assert_eq!(spec.date.day(), 29);
let first = NaiveDate::from_ymd_opt(year, 2, 28).unwrap(); let first = NaiveDate::from_ymd(year, 2, 28);
let second = NaiveDate::from_ymd_opt(year, 3, 1).unwrap(); let second = NaiveDate::from_ymd(year, 3, 1);
self.add(self.entry_with_remind(kind, Some(Dates::new(first, second)))?); self.add(self.entry_with_remind(kind, Some(Dates::new(first, second)))?);
} }
} }

View file

@ -85,7 +85,7 @@ impl DateSpec {
let range_from = s let range_from = s
.command .command
.last_done_root() .last_done_root()
.map(|date| date.succ_opt().unwrap()) .map(|date| date.succ())
.unwrap_or(self.start); .unwrap_or(self.start);
let range = s let range = s
.range_with_remind() .range_with_remind()
@ -137,7 +137,7 @@ impl DateSpec {
} }
} }
impl CommandState<'_> { impl<'a> CommandState<'a> {
pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<(), Error<FileSource>> { pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<(), Error<FileSource>> {
let index = self.source.file(); let index = self.source.file();
if let Some(repeat) = &spec.repeat { if let Some(repeat) = &spec.repeat {

File diff suppressed because it is too large Load diff

View file

@ -43,84 +43,84 @@ impl DeltaStep {
/// A lower bound on days /// A lower bound on days
fn lower_bound(&self) -> i32 { fn lower_bound(&self) -> i32 {
match self { match self {
Self::Year(n) => { DeltaStep::Year(n) => {
if *n < 0 { if *n < 0 {
*n * 366 *n * 366
} else { } else {
*n * 365 *n * 365
} }
} }
Self::Month(n) | Self::MonthReverse(n) => { DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => {
if *n < 0 { if *n < 0 {
*n * 31 *n * 31
} else { } else {
*n * 28 *n * 28
} }
} }
Self::Day(n) => *n, DeltaStep::Day(n) => *n,
Self::Week(n) => *n * 7, DeltaStep::Week(n) => *n * 7,
Self::Hour(n) => { DeltaStep::Hour(n) => {
if *n < 0 { if *n < 0 {
*n / 24 + (*n % 24).signum() *n / 24 + (*n % 24).signum()
} else { } else {
*n / 24 *n / 24
} }
} }
Self::Minute(n) => { DeltaStep::Minute(n) => {
if *n < 0 { if *n < 0 {
*n / (24 * 60) + (*n % (24 * 60)).signum() *n / (24 * 60) + (*n % (24 * 60)).signum()
} else { } else {
*n / (24 * 60) *n / (24 * 60)
} }
} }
Self::Weekday(n, _) => match n.cmp(&0) { DeltaStep::Weekday(n, _) => match n.cmp(&0) {
Ordering::Less => *n * 7 - 1, Ordering::Less => *n * 7 - 1,
Ordering::Equal => 0, Ordering::Equal => 0,
Ordering::Greater => *n * 7 - 7, Ordering::Greater => *n * 7 - 7,
}, },
Self::Time(_) => 0, DeltaStep::Time(_) => 0,
} }
} }
/// An upper bound on days /// An upper bound on days
fn upper_bound(&self) -> i32 { fn upper_bound(&self) -> i32 {
match self { match self {
Self::Year(n) => { DeltaStep::Year(n) => {
if *n > 0 { if *n > 0 {
*n * 366 *n * 366
} else { } else {
*n * 365 *n * 365
} }
} }
Self::Month(n) | Self::MonthReverse(n) => { DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => {
if *n > 0 { if *n > 0 {
*n * 31 *n * 31
} else { } else {
*n * 28 *n * 28
} }
} }
Self::Day(n) => *n, DeltaStep::Day(n) => *n,
Self::Week(n) => *n * 7, DeltaStep::Week(n) => *n * 7,
Self::Hour(n) => { DeltaStep::Hour(n) => {
if *n > 0 { if *n > 0 {
*n / 24 + (*n % 24).signum() *n / 24 + (*n % 24).signum()
} else { } else {
*n / 24 *n / 24
} }
} }
Self::Minute(n) => { DeltaStep::Minute(n) => {
if *n > 0 { if *n > 0 {
*n / (24 * 60) + (*n % (24 * 60)).signum() *n / (24 * 60) + (*n % (24 * 60)).signum()
} else { } else {
*n / (24 * 60) *n / (24 * 60)
} }
} }
Self::Weekday(n, _) => match n.cmp(&0) { DeltaStep::Weekday(n, _) => match n.cmp(&0) {
Ordering::Less => *n * 7 - 7, Ordering::Less => *n * 7 - 7,
Ordering::Equal => 0, Ordering::Equal => 0,
Ordering::Greater => *n * 7 - 1, Ordering::Greater => *n * 7 - 1,
}, },
Self::Time(_) => 1, DeltaStep::Time(_) => 1,
} }
} }
} }
@ -298,7 +298,7 @@ impl<S: Copy> DeltaEval<S> {
}; };
if time < curr_time { if time < curr_time {
self.curr = self.curr.succ_opt().unwrap(); self.curr = self.curr.succ();
} }
self.curr_time = Some(time); self.curr_time = Some(time);
Ok(()) Ok(())
@ -359,13 +359,13 @@ mod tests {
} }
fn apply_d(step: Step, from: (i32, u32, u32)) -> Result<NaiveDate, Error<()>> { fn apply_d(step: Step, from: (i32, u32, u32)) -> Result<NaiveDate, Error<()>> {
delta(step).apply_date((), NaiveDate::from_ymd_opt(from.0, from.1, from.2).unwrap()) delta(step).apply_date((), NaiveDate::from_ymd(from.0, from.1, from.2))
} }
fn test_d(step: Step, from: (i32, u32, u32), expected: (i32, u32, u32)) { fn test_d(step: Step, from: (i32, u32, u32), expected: (i32, u32, u32)) {
assert_eq!( assert_eq!(
apply_d(step, from).unwrap(), apply_d(step, from).unwrap(),
NaiveDate::from_ymd_opt(expected.0, expected.1, expected.2).unwrap() NaiveDate::from_ymd(expected.0, expected.1, expected.2)
); );
} }
@ -375,7 +375,7 @@ mod tests {
) -> Result<(NaiveDate, Time), Error<()>> { ) -> Result<(NaiveDate, Time), Error<()>> {
delta(step).apply_date_time( delta(step).apply_date_time(
(), (),
NaiveDate::from_ymd_opt(from.0, from.1, from.2).unwrap(), NaiveDate::from_ymd(from.0, from.1, from.2),
Time::new(from.3, from.4), Time::new(from.3, from.4),
) )
} }
@ -385,7 +385,7 @@ mod tests {
assert_eq!( assert_eq!(
apply_dt(step, from).unwrap(), apply_dt(step, from).unwrap(),
( (
NaiveDate::from_ymd_opt(expected.0, expected.1, expected.2).unwrap(), NaiveDate::from_ymd(expected.0, expected.1, expected.2),
Time::new(expected.3, expected.4) Time::new(expected.3, expected.4)
) )
); );

View file

@ -61,7 +61,6 @@ impl Entry {
/// Mode that determines how entries are filtered when they are added to /// Mode that determines how entries are filtered when they are added to
/// an [`Entries`]. /// an [`Entries`].
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryMode { pub enum EntryMode {
/// The entry's root date must be contained in the range. /// The entry's root date must be contained in the range.

View file

@ -88,7 +88,6 @@ impl<S> Error<S> {
} }
impl<'a, F: Files<'a>> Eprint<'a, F> for Error<F::FileId> { impl<'a, F: Files<'a>> Eprint<'a, F> for Error<F::FileId> {
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) { fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
let diagnostic = match self { let diagnostic = match self {
Error::DeltaInvalidStep { Error::DeltaInvalidStep {

View file

@ -37,7 +37,6 @@ impl DateRange {
/// Return a new range with its [`Self::until`] set to a new value. /// Return a new range with its [`Self::until`] set to a new value.
/// ///
/// Returns [`None`] if the new value is earlier than [`Self::from`]. /// Returns [`None`] if the new value is earlier than [`Self::from`].
#[allow(dead_code)]
pub fn with_until(&self, until: NaiveDate) -> Option<Self> { pub fn with_until(&self, until: NaiveDate) -> Option<Self> {
if self.from <= until { if self.from <= until {
Some(Self::new(self.from, until)) Some(Self::new(self.from, until))
@ -76,7 +75,7 @@ impl DateRange {
pub fn days(&self) -> impl Iterator<Item = NaiveDate> { pub fn days(&self) -> impl Iterator<Item = NaiveDate> {
(self.from.num_days_from_ce()..=self.until.num_days_from_ce()) (self.from.num_days_from_ce()..=self.until.num_days_from_ce())
.map(|days| NaiveDate::from_num_days_from_ce_opt(days).unwrap()) .map(NaiveDate::from_num_days_from_ce)
} }
pub fn years(&self) -> RangeInclusive<i32> { pub fn years(&self) -> RangeInclusive<i32> {

View file

@ -9,14 +9,13 @@ pub fn is_iso_leap_year(year: i32) -> bool {
} }
pub fn year_length(year: i32) -> u32 { pub fn year_length(year: i32) -> u32 {
NaiveDate::from_ymd_opt(year, 12, 31).unwrap().ordinal() NaiveDate::from_ymd(year, 12, 31).ordinal()
} }
pub fn month_length(year: i32, month: u32) -> u32 { pub fn month_length(year: i32, month: u32) -> u32 {
NaiveDate::from_ymd_opt(year, month + 1, 1) NaiveDate::from_ymd_opt(year, month + 1, 1)
.unwrap_or_else(|| NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap()) .unwrap_or_else(|| NaiveDate::from_ymd(year + 1, 1, 1))
.pred_opt() .pred()
.unwrap()
.day() .day()
} }

View file

@ -13,8 +13,7 @@ fn from_str_via_parse<P, R>(s: &str, rule: Rule, parse: P) -> result::Result<R,
where where
P: FnOnce(Pair<'_, Rule>) -> Result<R>, P: FnOnce(Pair<'_, Rule>) -> Result<R>,
{ {
let mut pairs = let mut pairs = TodayfileParser::parse(rule, s).map_err(|e| ParseError::new((), e))?;
TodayfileParser::parse(rule, s).map_err(|e| ParseError::new((), Box::new(e)))?;
let p = pairs.next().unwrap(); let p = pairs.next().unwrap();
assert_eq!(pairs.next(), None); assert_eq!(pairs.next(), None);
@ -58,17 +57,11 @@ fn parse_cli_date(p: Pair<'_, Rule>) -> Result<CliDate> {
Ok(CliDate { datum, delta }) Ok(CliDate { datum, delta })
} }
fn parse_cli_date_arg(p: Pair<'_, Rule>) -> Result<CliDate> {
assert_eq!(p.as_rule(), Rule::cli_date_arg);
let p = p.into_inner().next().unwrap();
parse_cli_date(p)
}
impl FromStr for CliDate { impl FromStr for CliDate {
type Err = ParseError<()>; type Err = ParseError<()>;
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> { fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
from_str_via_parse(s, Rule::cli_date_arg, parse_cli_date_arg) from_str_via_parse(s, Rule::cli_date, parse_cli_date)
} }
} }
@ -88,17 +81,11 @@ fn parse_cli_ident(p: Pair<'_, Rule>) -> Result<CliIdent> {
}) })
} }
fn parse_cli_ident_arg(p: Pair<'_, Rule>) -> Result<CliIdent> {
assert_eq!(p.as_rule(), Rule::cli_ident_arg);
let p = p.into_inner().next().unwrap();
parse_cli_ident(p)
}
impl FromStr for CliIdent { impl FromStr for CliIdent {
type Err = ParseError<()>; type Err = ParseError<()>;
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> { fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
from_str_via_parse(s, Rule::cli_ident_arg, parse_cli_ident_arg) from_str_via_parse(s, Rule::cli_ident, parse_cli_ident)
} }
} }
@ -148,12 +135,11 @@ fn parse_cli_range(p: Pair<'_, Rule>) -> Result<CliRange> {
let (start, start_delta) = parse_cli_range_start(p.next().unwrap())?; let (start, start_delta) = parse_cli_range_start(p.next().unwrap())?;
let (end, end_delta) = match p.next() { let (end, end_delta) = match p.next() {
Some(p) => parse_cli_range_end(p)?, // For some reason, the EOI gets captured but the SOI doesn't.
None => (None, None), Some(p) if p.as_rule() != Rule::EOI => parse_cli_range_end(p)?,
_ => (None, None),
}; };
assert_eq!(p.next(), None);
Ok(CliRange { Ok(CliRange {
start, start,
start_delta, start_delta,
@ -162,17 +148,11 @@ fn parse_cli_range(p: Pair<'_, Rule>) -> Result<CliRange> {
}) })
} }
fn parse_cli_range_arg(p: Pair<'_, Rule>) -> Result<CliRange> {
assert_eq!(p.as_rule(), Rule::cli_range_arg);
let p = p.into_inner().next().unwrap();
parse_cli_range(p)
}
impl FromStr for CliRange { impl FromStr for CliRange {
type Err = ParseError<()>; type Err = ParseError<()>;
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> { fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
from_str_via_parse(s, Rule::cli_range_arg, parse_cli_range_arg) from_str_via_parse(s, Rule::cli_range, parse_cli_range)
} }
} }

View file

@ -27,27 +27,27 @@ pub enum DeltaStep {
impl DeltaStep { impl DeltaStep {
pub fn amount(&self) -> i32 { pub fn amount(&self) -> i32 {
match self { match self {
Self::Year(i) => *i, DeltaStep::Year(i) => *i,
Self::Month(i) => *i, DeltaStep::Month(i) => *i,
Self::MonthReverse(i) => *i, DeltaStep::MonthReverse(i) => *i,
Self::Day(i) => *i, DeltaStep::Day(i) => *i,
Self::Week(i) => *i, DeltaStep::Week(i) => *i,
Self::Hour(i) => *i, DeltaStep::Hour(i) => *i,
Self::Minute(i) => *i, DeltaStep::Minute(i) => *i,
Self::Weekday(i, _) => *i, DeltaStep::Weekday(i, _) => *i,
} }
} }
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match self { match self {
Self::Year(_) => "y", DeltaStep::Year(_) => "y",
Self::Month(_) => "m", DeltaStep::Month(_) => "m",
Self::MonthReverse(_) => "M", DeltaStep::MonthReverse(_) => "M",
Self::Day(_) => "d", DeltaStep::Day(_) => "d",
Self::Week(_) => "w", DeltaStep::Week(_) => "w",
Self::Hour(_) => "h", DeltaStep::Hour(_) => "h",
Self::Minute(_) => "min", DeltaStep::Minute(_) => "min",
Self::Weekday(_, wd) => wd.name(), DeltaStep::Weekday(_, wd) => wd.name(),
} }
} }
} }
@ -168,39 +168,39 @@ impl Var {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match self { match self {
// Constants // Constants
Self::True => "true", Var::True => "true",
Self::False => "false", Var::False => "false",
Self::Monday => "mon", Var::Monday => "mon",
Self::Tuesday => "tue", Var::Tuesday => "tue",
Self::Wednesday => "wed", Var::Wednesday => "wed",
Self::Thursday => "thu", Var::Thursday => "thu",
Self::Friday => "fri", Var::Friday => "fri",
Self::Saturday => "sat", Var::Saturday => "sat",
Self::Sunday => "sun", Var::Sunday => "sun",
// Variables // Variables
Self::JulianDay => "j", Var::JulianDay => "j",
Self::Year => "y", Var::Year => "y",
Self::YearLength => "yl", Var::YearLength => "yl",
Self::YearDay => "yd", Var::YearDay => "yd",
Self::YearDayReverse => "yD", Var::YearDayReverse => "yD",
Self::YearWeek => "yw", Var::YearWeek => "yw",
Self::YearWeekReverse => "yW", Var::YearWeekReverse => "yW",
Self::Month => "m", Var::Month => "m",
Self::MonthLength => "ml", Var::MonthLength => "ml",
Self::MonthWeek => "mw", Var::MonthWeek => "mw",
Self::MonthWeekReverse => "mW", Var::MonthWeekReverse => "mW",
Self::Day => "d", Var::Day => "d",
Self::DayReverse => "D", Var::DayReverse => "D",
Self::IsoYear => "iy", Var::IsoYear => "iy",
Self::IsoYearLength => "iyl", Var::IsoYearLength => "iyl",
Self::IsoWeek => "iw", Var::IsoWeek => "iw",
Self::Weekday => "wd", Var::Weekday => "wd",
Self::Easter => "e", Var::Easter => "e",
// Variables with "boolean" values // Variables with "boolean" values
Self::IsWeekday => "isWeekday", Var::IsWeekday => "isWeekday",
Self::IsWeekend => "isWeekend", Var::IsWeekend => "isWeekend",
Self::IsLeapYear => "isLeapYear", Var::IsLeapYear => "isLeapYear",
Self::IsIsoLeapYear => "isIsoLeapYear", Var::IsIsoLeapYear => "isIsoLeapYear",
} }
} }
} }
@ -301,11 +301,11 @@ pub enum DoneDate {
impl DoneDate { impl DoneDate {
pub fn root(self) -> NaiveDate { pub fn root(self) -> NaiveDate {
match self { match self {
Self::Date { root } => root, DoneDate::Date { root } => root,
Self::DateTime { root, .. } => root, DoneDate::DateTime { root, .. } => root,
Self::DateToDate { root, .. } => root, DoneDate::DateToDate { root, .. } => root,
Self::DateTimeToTime { root, .. } => root, DoneDate::DateTimeToTime { root, .. } => root,
Self::DateTimeToDateTime { root, .. } => root, DoneDate::DateTimeToDateTime { root, .. } => root,
} }
} }
@ -346,9 +346,7 @@ pub enum DoneKind {
#[derive(Debug)] #[derive(Debug)]
pub struct Done { pub struct Done {
pub kind: DoneKind, pub kind: DoneKind,
/// The date of the task the DONE refers to.
pub date: Option<DoneDate>, pub date: Option<DoneDate>,
/// When the task was actually completed.
pub done_at: NaiveDate, pub done_at: NaiveDate,
} }

View file

@ -15,11 +15,11 @@ use super::{parse, FileSource, Files};
#[error("{error}")] #[error("{error}")]
pub struct ParseError<S> { pub struct ParseError<S> {
file: S, file: S,
error: Box<parse::Error>, error: parse::Error,
} }
impl<S> ParseError<S> { impl<S> ParseError<S> {
pub fn new(file: S, error: Box<parse::Error>) -> Self { pub fn new(file: S, error: parse::Error) -> Self {
Self { file, error } Self { file, error }
} }
@ -69,7 +69,6 @@ impl<'a, F> Eprint<'a, F> for ParseError<F::FileId>
where where
F: codespan_reporting::files::Files<'a>, F: codespan_reporting::files::Files<'a>,
{ {
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) { fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
let range = match self.error.location { let range = match self.error.location {
InputLocation::Pos(at) => at..at, InputLocation::Pos(at) => at..at,
@ -104,7 +103,7 @@ pub enum Error {
#[error("{error}")] #[error("{error}")]
Parse { Parse {
file: FileSource, file: FileSource,
error: Box<parse::Error>, error: parse::Error,
}, },
#[error("Conflicting time zones {tz1} and {tz2}")] #[error("Conflicting time zones {tz1} and {tz2}")]
TzConflict { TzConflict {
@ -133,22 +132,21 @@ pub enum Error {
} }
impl<'a> Eprint<'a, Files> for Error { impl<'a> Eprint<'a, Files> for Error {
#[allow(single_use_lifetimes)]
fn eprint<'f: 'a>(&self, files: &'f Files, config: &Config) { fn eprint<'f: 'a>(&self, files: &'f Files, config: &Config) {
match self { match self {
Self::ResolvePath { path, error } => { Error::ResolvePath { path, error } => {
eprintln!("Could not resolve path {path:?}:"); eprintln!("Could not resolve path {path:?}:");
eprintln!(" {error}"); eprintln!(" {error}");
} }
Self::ReadFile { file, error } => { Error::ReadFile { file, error } => {
eprintln!("Could not read file {file:?}:"); eprintln!("Could not read file {file:?}:");
eprintln!(" {error}"); eprintln!(" {error}");
} }
Self::WriteFile { file, error } => { Error::WriteFile { file, error } => {
eprintln!("Could not write file {file:?}:"); eprintln!("Could not write file {file:?}:");
eprintln!(" {error}"); eprintln!(" {error}");
} }
Self::ResolveTz { Error::ResolveTz {
file, file,
span, span,
tz, tz,
@ -160,14 +158,14 @@ impl<'a> Eprint<'a, Files> for Error {
.with_notes(vec![format!("{error}")]); .with_notes(vec![format!("{error}")]);
Self::eprint_diagnostic(files, config, &diagnostic); Self::eprint_diagnostic(files, config, &diagnostic);
} }
Self::LocalTz { error } => { Error::LocalTz { error } => {
eprintln!("Could not determine local timezone:"); eprintln!("Could not determine local timezone:");
eprintln!(" {error}"); eprintln!(" {error}");
} }
Self::Parse { file, error } => { Error::Parse { file, error } => {
ParseError::new(*file, error.clone()).eprint(files, config) ParseError::new(*file, error.clone()).eprint(files, config)
} }
Self::TzConflict { Error::TzConflict {
file1, file1,
span1, span1,
tz1, tz1,
@ -186,7 +184,7 @@ impl<'a> Eprint<'a, Files> for Error {
]); ]);
Self::eprint_diagnostic(files, config, &diagnostic); Self::eprint_diagnostic(files, config, &diagnostic);
} }
Self::MultipleCapture { Error::MultipleCapture {
file1, file1,
span1, span1,
file2, file2,
@ -203,7 +201,7 @@ impl<'a> Eprint<'a, Files> for Error {
]); ]);
Self::eprint_diagnostic(files, config, &diagnostic); Self::eprint_diagnostic(files, config, &diagnostic);
} }
Self::LogConflict { Error::LogConflict {
file1, file1,
span1, span1,
file2, file2,

View file

@ -75,10 +75,10 @@ impl fmt::Display for DateSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Start // Start
write!(f, "{}", self.start)?; write!(f, "{}", self.start)?;
if let Some(delta) = &self.start_delta { for delta in &self.start_delta {
write!(f, " {delta}")?; write!(f, " {delta}")?;
} }
if let Some(time) = &self.start_time { for time in &self.start_time {
write!(f, " {time}")?; write!(f, " {time}")?;
} }
@ -109,7 +109,7 @@ impl fmt::Display for WeekdaySpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Start // Start
write!(f, "{}", self.start)?; write!(f, "{}", self.start)?;
if let Some(time) = &self.start_time { for time in &self.start_time {
write!(f, " {time}")?; write!(f, " {time}")?;
} }
@ -140,25 +140,25 @@ impl fmt::Display for Var {
impl fmt::Display for Expr { impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Lit(i) => write!(f, "{i}"), Expr::Lit(i) => write!(f, "{i}"),
Self::Var(v) => write!(f, "{v}"), Expr::Var(v) => write!(f, "{v}"),
Self::Paren(e) => write!(f, "({e})"), Expr::Paren(e) => write!(f, "({e})"),
Self::Neg(e) => write!(f, "-{e}"), Expr::Neg(e) => write!(f, "-{e}"),
Self::Add(a, b) => write!(f, "{a} + {b}"), Expr::Add(a, b) => write!(f, "{a} + {b}"),
Self::Sub(a, b) => write!(f, "{a} - {b}"), Expr::Sub(a, b) => write!(f, "{a} - {b}"),
Self::Mul(a, b) => write!(f, "{a} * {b}"), Expr::Mul(a, b) => write!(f, "{a} * {b}"),
Self::Div(a, b) => write!(f, "{a} / {b}"), Expr::Div(a, b) => write!(f, "{a} / {b}"),
Self::Mod(a, b) => write!(f, "{a} % {b}"), Expr::Mod(a, b) => write!(f, "{a} % {b}"),
Self::Eq(a, b) => write!(f, "{a} = {b}"), Expr::Eq(a, b) => write!(f, "{a} = {b}"),
Self::Neq(a, b) => write!(f, "{a} != {b}"), Expr::Neq(a, b) => write!(f, "{a} != {b}"),
Self::Lt(a, b) => write!(f, "{a} < {b}"), Expr::Lt(a, b) => write!(f, "{a} < {b}"),
Self::Lte(a, b) => write!(f, "{a} <= {b}"), Expr::Lte(a, b) => write!(f, "{a} <= {b}"),
Self::Gt(a, b) => write!(f, "{a} > {b}"), Expr::Gt(a, b) => write!(f, "{a} > {b}"),
Self::Gte(a, b) => write!(f, "{a} >= {b}"), Expr::Gte(a, b) => write!(f, "{a} >= {b}"),
Self::Not(e) => write!(f, "!{e}"), Expr::Not(e) => write!(f, "!{e}"),
Self::And(a, b) => write!(f, "{a} & {b}"), Expr::And(a, b) => write!(f, "{a} & {b}"),
Self::Or(a, b) => write!(f, "{a} | {b}"), Expr::Or(a, b) => write!(f, "{a} | {b}"),
Self::Xor(a, b) => write!(f, "{a} ^ {b}"), Expr::Xor(a, b) => write!(f, "{a} ^ {b}"),
} }
} }
} }
@ -171,10 +171,10 @@ impl fmt::Display for FormulaSpec {
} else { } else {
write!(f, "*")?; write!(f, "*")?;
} }
if let Some(delta) = &self.start_delta { for delta in &self.start_delta {
write!(f, " {delta}")?; write!(f, " {delta}")?;
} }
if let Some(time) = &self.start_time { for time in &self.start_time {
write!(f, " {time}")?; write!(f, " {time}")?;
} }
@ -196,9 +196,9 @@ impl fmt::Display for FormulaSpec {
impl fmt::Display for Spec { impl fmt::Display for Spec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Date(spec) => write!(f, "{spec}"), Spec::Date(spec) => write!(f, "{spec}"),
Self::Weekday(spec) => write!(f, "{spec}"), Spec::Weekday(spec) => write!(f, "{spec}"),
Self::Formula(spec) => write!(f, "{spec}"), Spec::Formula(spec) => write!(f, "{spec}"),
} }
} }
} }
@ -216,14 +216,14 @@ impl fmt::Display for BirthdaySpec {
impl fmt::Display for Statement { impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Date(spec) => writeln!(f, "DATE {spec}"), Statement::Date(spec) => writeln!(f, "DATE {spec}"),
Self::BDate(spec) => writeln!(f, "BDATE {spec}"), Statement::BDate(spec) => writeln!(f, "BDATE {spec}"),
Self::From(Some(date)) => writeln!(f, "FROM {date}"), Statement::From(Some(date)) => writeln!(f, "FROM {date}"),
Self::From(None) => writeln!(f, "FROM *"), Statement::From(None) => writeln!(f, "FROM *"),
Self::Until(Some(date)) => writeln!(f, "UNTIL {date}"), Statement::Until(Some(date)) => writeln!(f, "UNTIL {date}"),
Self::Until(None) => writeln!(f, "UNTIL *"), Statement::Until(None) => writeln!(f, "UNTIL *"),
Self::Except(date) => writeln!(f, "EXCEPT {date}"), Statement::Except(date) => writeln!(f, "EXCEPT {date}"),
Self::Move { Statement::Move {
from, to, to_time, .. from, to, to_time, ..
} => match (to, to_time) { } => match (to, to_time) {
(None, None) => unreachable!(), (None, None) => unreachable!(),
@ -231,8 +231,8 @@ impl fmt::Display for Statement {
(None, Some(to_time)) => writeln!(f, "MOVE {from} TO {to_time}"), (None, Some(to_time)) => writeln!(f, "MOVE {from} TO {to_time}"),
(Some(to), Some(to_time)) => writeln!(f, "MOVE {from} TO {to} {to_time}"), (Some(to), Some(to_time)) => writeln!(f, "MOVE {from} TO {to} {to_time}"),
}, },
Self::Remind(Some(delta)) => writeln!(f, "REMIND {delta}"), Statement::Remind(Some(delta)) => writeln!(f, "REMIND {delta}"),
Self::Remind(None) => writeln!(f, "REMIND *"), Statement::Remind(None) => writeln!(f, "REMIND *"),
} }
} }
} }
@ -240,15 +240,15 @@ impl fmt::Display for Statement {
impl fmt::Display for DoneDate { impl fmt::Display for DoneDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.simplified() { match self.simplified() {
Self::Date { root } => write!(f, "{root}"), DoneDate::Date { root } => write!(f, "{root}"),
Self::DateTime { root, root_time } => write!(f, "{root} {root_time}"), DoneDate::DateTime { root, root_time } => write!(f, "{root} {root_time}"),
Self::DateToDate { root, other } => write!(f, "{root} -- {other}"), DoneDate::DateToDate { root, other } => write!(f, "{root} -- {other}"),
Self::DateTimeToTime { DoneDate::DateTimeToTime {
root, root,
root_time, root_time,
other_time, other_time,
} => write!(f, "{root} {root_time} -- {other_time}"), } => write!(f, "{root} {root_time} -- {other_time}"),
Self::DateTimeToDateTime { DoneDate::DateTimeToDateTime {
root, root,
root_time, root_time,
other, other,
@ -308,18 +308,18 @@ impl fmt::Display for Log {
impl fmt::Display for Command { impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Include(name) => writeln!(f, "INCLUDE {name}"), Command::Include(name) => writeln!(f, "INCLUDE {name}"),
Self::Timezone(name) => writeln!(f, "TIMEZONE {name}"), Command::Timezone(name) => writeln!(f, "TIMEZONE {name}"),
Self::Capture => writeln!(f, "CAPTURE"), Command::Capture => writeln!(f, "CAPTURE"),
Self::Task(task) => write!(f, "{task}"), Command::Task(task) => write!(f, "{task}"),
Self::Note(note) => write!(f, "{note}"), Command::Note(note) => write!(f, "{note}"),
Self::Log(log) => write!(f, "{log}"), Command::Log(log) => write!(f, "{log}"),
} }
} }
} }
impl File { impl File {
fn sort(commands: &mut [&Command]) { fn sort(commands: &mut Vec<&Command>) {
// Order of commands in a file: // Order of commands in a file:
// 1. Imports, sorted alphabetically // 1. Imports, sorted alphabetically
// 2. Time zone(s) // 2. Time zone(s)

View file

@ -59,33 +59,34 @@ variable = {
| "e" | "e"
} }
prefix_neg = { "-" } unop_neg = { "-" }
prefix_not = { "!" } unop_not = { "!" }
prefix = _{ prefix_neg | prefix_not } unop = _{ unop_neg | unop_not }
infix_add = { "+" } op_add = { "+" }
infix_sub = { "-" } op_sub = { "-" }
infix_mul = { "*" } op_mul = { "*" }
infix_div = { "/" } op_div = { "/" }
infix_mod = { "%" } op_mod = { "%" }
infix_eq = { "=" } op_eq = { "=" }
infix_neq = { "!=" } op_neq = { "!=" }
infix_lt = { "<" } op_lt = { "<" }
infix_lte = { "<=" } op_lte = { "<=" }
infix_gt = { ">" } op_gt = { ">" }
infix_gte = { ">=" } op_gte = { ">=" }
infix_and = { "&" } op_and = { "&" }
infix_or = { "|" } op_or = { "|" }
infix_xor = { "^" } op_xor = { "^" }
infix = _{ op = _{
infix_add | infix_sub | infix_mul | infix_div | infix_mod op_add | op_sub | op_mul | op_div | op_mod
| infix_eq | infix_neq | infix_lt | infix_lte | infix_gt | infix_gte | op_eq | op_neq | op_lt | op_lte | op_gt | op_gte
| infix_and | infix_or | infix_xor | op_and | op_or | op_xor
} }
paren_expr = { "(" ~ expr ~ ")" } paren_expr = { "(" ~ expr ~ ")" }
term = { number | boolean | variable | paren_expr } unop_expr = { unop ~ expr }
expr = { prefix* ~ term ~ (infix ~ prefix* ~ term)* } term = { number | boolean | variable | paren_expr | unop_expr }
expr = { term ~ (op ~ term)* }
date_fixed_start = { datum ~ delta? ~ time? } date_fixed_start = { datum ~ delta? ~ time? }
date_fixed_end = { datum ~ delta? ~ time? | delta ~ time? | time } date_fixed_end = { datum ~ delta? ~ time? | delta ~ time? | time }
@ -151,13 +152,8 @@ file = ${ SOI ~ (empty_line* ~ command)* ~ empty_line* ~ WHITESPACE* ~ EOI }
today = { "today" | "t" } today = { "today" | "t" }
cli_datum = { datum | today } cli_datum = { datum | today }
cli_date = { cli_datum ~ delta? } cli_date = { cli_datum ~ delta? }
cli_ident = { cli_date | number } cli_ident = { SOI ~ (cli_date | number) ~ EOI }
cli_range_start = { cli_datum ~ delta? } cli_range_start = { cli_datum ~ delta? }
cli_range_end = { cli_datum ~ delta? | delta } cli_range_end = { cli_datum ~ delta? | delta }
cli_range = { cli_range_start ~ ("--" ~ cli_range_end)? } cli_range = { SOI ~ cli_range_start ~ ("--" ~ cli_range_end)? ~ EOI }
cli_date_arg = { SOI ~ cli_date ~ EOI }
cli_ident_arg = { SOI ~ cli_ident ~ EOI }
cli_range_arg = { SOI ~ cli_range ~ EOI }
cli_command = ${ SOI ~ empty_line* ~ command ~ empty_line* ~ WHITESPACE* ~ EOI } cli_command = ${ SOI ~ empty_line* ~ command ~ empty_line* ~ WHITESPACE* ~ EOI }

View file

@ -4,7 +4,7 @@ use std::result;
use chrono::NaiveDate; use chrono::NaiveDate;
use pest::error::ErrorVariant; use pest::error::ErrorVariant;
use pest::iterators::Pair; use pest::iterators::Pair;
use pest::pratt_parser::{Assoc, Op, PrattParser}; use pest::prec_climber::{Assoc, Operator, PrecClimber};
use pest::{Parser, Span}; use pest::{Parser, Span};
use super::commands::{ use super::commands::{
@ -18,7 +18,7 @@ use super::primitives::{Spanned, Time, Weekday};
pub struct TodayfileParser; pub struct TodayfileParser;
pub type Error = pest::error::Error<Rule>; pub type Error = pest::error::Error<Rule>;
pub type Result<T> = result::Result<T, Box<Error>>; pub type Result<T> = result::Result<T, Error>;
fn error<S: Into<String>>(span: Span<'_>, message: S) -> Error { fn error<S: Into<String>>(span: Span<'_>, message: S) -> Error {
Error::new_from_span( Error::new_from_span(
@ -30,7 +30,7 @@ fn error<S: Into<String>>(span: Span<'_>, message: S) -> Error {
} }
fn fail<S: Into<String>, T>(span: Span<'_>, message: S) -> Result<T> { fn fail<S: Into<String>, T>(span: Span<'_>, message: S) -> Result<T> {
Err(Box::new(error(span, message))) Err(error(span, message))
} }
fn parse_include(p: Pair<'_, Rule>) -> Spanned<String> { fn parse_include(p: Pair<'_, Rule>) -> Spanned<String> {
@ -293,7 +293,7 @@ fn parse_date_fixed(p: Pair<'_, Rule>) -> Result<DateSpec> {
assert_eq!(p.as_rule(), Rule::date_fixed); assert_eq!(p.as_rule(), Rule::date_fixed);
let mut spec = DateSpec { let mut spec = DateSpec {
start: NaiveDate::from_ymd_opt(0, 1, 1).unwrap(), start: NaiveDate::from_ymd(0, 1, 1),
start_delta: None, start_delta: None,
start_time: None, start_time: None,
end: None, end: None,
@ -359,6 +359,24 @@ fn parse_variable(p: Pair<'_, Rule>) -> Var {
} }
} }
fn parse_unop_expr(p: Pair<'_, Rule>) -> Spanned<Expr> {
assert_eq!(p.as_rule(), Rule::unop_expr);
let span = (&p.as_span()).into();
let mut p = p.into_inner();
let p_op = p.next().unwrap();
let p_expr = p.next().unwrap();
assert_eq!(p.next(), None);
let inner = parse_expr(p_expr);
let expr = match p_op.as_rule() {
Rule::unop_neg => Expr::Neg(Box::new(inner)),
Rule::unop_not => Expr::Not(Box::new(inner)),
_ => unreachable!(),
};
Spanned::new(span, expr)
}
fn parse_paren_expr(p: Pair<'_, Rule>) -> Spanned<Expr> { fn parse_paren_expr(p: Pair<'_, Rule>) -> Spanned<Expr> {
assert_eq!(p.as_rule(), Rule::paren_expr); assert_eq!(p.as_rule(), Rule::paren_expr);
let span = (&p.as_span()).into(); let span = (&p.as_span()).into();
@ -374,43 +392,34 @@ fn parse_term(p: Pair<'_, Rule>) -> Spanned<Expr> {
Rule::number => Spanned::new(span, Expr::Lit(parse_number(p).into())), Rule::number => Spanned::new(span, Expr::Lit(parse_number(p).into())),
Rule::boolean => Spanned::new(span, Expr::Var(parse_boolean(p))), Rule::boolean => Spanned::new(span, Expr::Var(parse_boolean(p))),
Rule::variable => Spanned::new(span, Expr::Var(parse_variable(p))), Rule::variable => Spanned::new(span, Expr::Var(parse_variable(p))),
Rule::unop_expr => parse_unop_expr(p),
Rule::paren_expr => parse_paren_expr(p), Rule::paren_expr => parse_paren_expr(p),
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn parse_prefix(p: Pair<'_, Rule>, s: Spanned<Expr>) -> Spanned<Expr> { fn parse_op(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned<Expr> {
let span = s.span.join((&p.as_span()).into());
let expr = match p.as_rule() {
Rule::prefix_neg => Expr::Neg(Box::new(s)),
Rule::prefix_not => Expr::Not(Box::new(s)),
_ => unreachable!(),
};
Spanned::new(span, expr)
}
fn parse_infix(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned<Expr> {
let span = l.span.join(r.span); let span = l.span.join(r.span);
let expr = match p.as_rule() { let expr = match p.as_rule() {
// Integer-y operations // Integer-y operations
Rule::infix_add => Expr::Add(Box::new(l), Box::new(r)), Rule::op_add => Expr::Add(Box::new(l), Box::new(r)),
Rule::infix_sub => Expr::Sub(Box::new(l), Box::new(r)), Rule::op_sub => Expr::Sub(Box::new(l), Box::new(r)),
Rule::infix_mul => Expr::Mul(Box::new(l), Box::new(r)), Rule::op_mul => Expr::Mul(Box::new(l), Box::new(r)),
Rule::infix_div => Expr::Div(Box::new(l), Box::new(r)), Rule::op_div => Expr::Div(Box::new(l), Box::new(r)),
Rule::infix_mod => Expr::Mod(Box::new(l), Box::new(r)), Rule::op_mod => Expr::Mod(Box::new(l), Box::new(r)),
// Comparisons // Comparisons
Rule::infix_eq => Expr::Eq(Box::new(l), Box::new(r)), Rule::op_eq => Expr::Eq(Box::new(l), Box::new(r)),
Rule::infix_neq => Expr::Neq(Box::new(l), Box::new(r)), Rule::op_neq => Expr::Neq(Box::new(l), Box::new(r)),
Rule::infix_lt => Expr::Lt(Box::new(l), Box::new(r)), Rule::op_lt => Expr::Lt(Box::new(l), Box::new(r)),
Rule::infix_lte => Expr::Lte(Box::new(l), Box::new(r)), Rule::op_lte => Expr::Lte(Box::new(l), Box::new(r)),
Rule::infix_gt => Expr::Gt(Box::new(l), Box::new(r)), Rule::op_gt => Expr::Gt(Box::new(l), Box::new(r)),
Rule::infix_gte => Expr::Gte(Box::new(l), Box::new(r)), Rule::op_gte => Expr::Gte(Box::new(l), Box::new(r)),
// Boolean-y operations // Boolean-y operations
Rule::infix_and => Expr::And(Box::new(l), Box::new(r)), Rule::op_and => Expr::And(Box::new(l), Box::new(r)),
Rule::infix_or => Expr::Or(Box::new(l), Box::new(r)), Rule::op_or => Expr::Or(Box::new(l), Box::new(r)),
Rule::infix_xor => Expr::Xor(Box::new(l), Box::new(r)), Rule::op_xor => Expr::Xor(Box::new(l), Box::new(r)),
_ => unreachable!(), _ => unreachable!(),
}; };
@ -420,23 +429,21 @@ fn parse_infix(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned
fn parse_expr(p: Pair<'_, Rule>) -> Spanned<Expr> { fn parse_expr(p: Pair<'_, Rule>) -> Spanned<Expr> {
assert_eq!(p.as_rule(), Rule::expr); assert_eq!(p.as_rule(), Rule::expr);
PrattParser::new() fn op(rule: Rule) -> Operator<Rule> {
.op(Op::infix(Rule::infix_or, Assoc::Left) | Op::infix(Rule::infix_xor, Assoc::Left)) Operator::new(rule, Assoc::Left)
.op(Op::infix(Rule::infix_and, Assoc::Left)) }
.op(Op::infix(Rule::infix_eq, Assoc::Left) | Op::infix(Rule::infix_neq, Assoc::Left))
.op(Op::infix(Rule::infix_lt, Assoc::Left) let climber = PrecClimber::new(vec![
| Op::infix(Rule::infix_lte, Assoc::Left) // Precedence from low to high
| Op::infix(Rule::infix_gt, Assoc::Left) op(Rule::op_or) | op(Rule::op_xor),
| Op::infix(Rule::infix_gte, Assoc::Left)) op(Rule::op_and),
.op(Op::infix(Rule::infix_mul, Assoc::Left) op(Rule::op_eq) | op(Rule::op_neq),
| Op::infix(Rule::infix_div, Assoc::Left) op(Rule::op_lt) | op(Rule::op_lte) | op(Rule::op_gt) | op(Rule::op_gte),
| Op::infix(Rule::infix_mod, Assoc::Left)) op(Rule::op_mul) | op(Rule::op_div) | op(Rule::op_mod),
.op(Op::infix(Rule::infix_add, Assoc::Left) | Op::infix(Rule::infix_sub, Assoc::Left)) op(Rule::op_add) | op(Rule::op_sub),
.op(Op::prefix(Rule::prefix_neg) | Op::prefix(Rule::prefix_not)) ]);
.map_primary(parse_term)
.map_prefix(parse_prefix) climber.climb(p.into_inner(), parse_term, parse_op)
.map_infix(parse_infix)
.parse(p.into_inner())
} }
fn parse_date_expr_start(p: Pair<'_, Rule>, spec: &mut FormulaSpec) -> Result<()> { fn parse_date_expr_start(p: Pair<'_, Rule>, spec: &mut FormulaSpec) -> Result<()> {
@ -851,5 +858,5 @@ pub fn parse(path: &Path, input: &str) -> Result<File> {
let file_pair = pairs.next().unwrap(); let file_pair = pairs.next().unwrap();
assert_eq!(pairs.next(), None); assert_eq!(pairs.next(), None);
parse_file(file_pair).map_err(|e| Box::new(e.with_path(&pathstr))) parse_file(file_pair).map_err(|e| e.with_path(&pathstr))
} }

View file

@ -206,13 +206,13 @@ impl Weekday {
/// `Saturday`, `Sunday`). /// `Saturday`, `Sunday`).
pub fn full_name(self) -> &'static str { pub fn full_name(self) -> &'static str {
match self { match self {
Self::Monday => "Monday", Weekday::Monday => "Monday",
Self::Tuesday => "Tuesday", Weekday::Tuesday => "Tuesday",
Self::Wednesday => "Wednesday", Weekday::Wednesday => "Wednesday",
Self::Thursday => "Thursday", Weekday::Thursday => "Thursday",
Self::Friday => "Friday", Weekday::Friday => "Friday",
Self::Saturday => "Saturday", Weekday::Saturday => "Saturday",
Self::Sunday => "Sunday", Weekday::Sunday => "Sunday",
} }
} }

View file

@ -1,3 +1,8 @@
#![warn(future_incompatible)]
#![warn(rust_2018_idioms)]
#![warn(clippy::all)]
#![warn(clippy::use_self)]
mod cli; mod cli;
mod error; mod error;
mod eval; mod eval;