Compare commits
20 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abf4d5a502 | |||
| 22eec53c5a | |||
| 4001d0653b | |||
| cb470d201e | |||
| 4529f383fe | |||
| 64c41b1073 | |||
| f3792fae64 | |||
| 23b0a5e5fc | |||
| 11d9a2f1c7 | |||
| f01c3818c0 | |||
| c5a2e5dccb | |||
| 57da1026c2 | |||
| 36d8082c9d | |||
| aaa3537e90 | |||
| bc0d2481c8 | |||
| a63529b972 | |||
| 972a590ba9 | |||
| f42cf01a15 | |||
| 74433eccbe | |||
| f231fee508 |
26 changed files with 1373 additions and 919 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -4,6 +4,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## 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
|
||||
|
||||
### Added
|
||||
|
|
|
|||
742
Cargo.lock
generated
742
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
32
Cargo.toml
32
Cargo.toml
|
|
@ -4,16 +4,32 @@ version = "0.2.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.19"
|
||||
chrono = "0.4.23"
|
||||
clap = { version = "4.1.4", features = ["derive"] }
|
||||
codespan-reporting = "0.11.1"
|
||||
colored = "2.0.0"
|
||||
computus = "1.0.0"
|
||||
directories = "4.0.1"
|
||||
edit = "0.1.3"
|
||||
pest = "2.1.3"
|
||||
pest_derive = "2.1.0"
|
||||
promptly = "0.3.0"
|
||||
structopt = "0.3.26"
|
||||
termcolor = "1.1.3"
|
||||
thiserror = "1.0.30"
|
||||
edit = "0.1.4"
|
||||
pest = "2.5.5"
|
||||
pest_derive = "2.5.5"
|
||||
promptly = "0.3.1"
|
||||
termcolor = "1.2.0"
|
||||
thiserror = "1.0.38"
|
||||
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"
|
||||
|
|
|
|||
45
src/cli.rs
45
src/cli.rs
|
|
@ -3,9 +3,9 @@ use std::str::FromStr;
|
|||
use std::{process, result};
|
||||
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use clap::Parser;
|
||||
use codespan_reporting::files::SimpleFile;
|
||||
use directories::ProjectDirs;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::eval::{self, DateRange, Entry, EntryMode};
|
||||
use crate::files::cli::{CliDate, CliIdent, CliRange};
|
||||
|
|
@ -24,77 +24,76 @@ mod print;
|
|||
mod show;
|
||||
mod util;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, clap::Parser)]
|
||||
pub struct Opt {
|
||||
/// File to load
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
#[clap(short, long)]
|
||||
file: Option<PathBuf>,
|
||||
/// Overwrite the current date
|
||||
#[structopt(short, long, default_value = "t")]
|
||||
#[clap(short, long, default_value = "t")]
|
||||
date: String,
|
||||
/// Range of days to focus on
|
||||
#[structopt(short, long, default_value = "t-2d--t+13d")]
|
||||
#[clap(short, long, default_value = "t-2d--t+2w")]
|
||||
range: String,
|
||||
#[structopt(subcommand)]
|
||||
#[clap(subcommand)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Command {
|
||||
/// Shows individual entries in detail
|
||||
#[structopt(alias = "s")]
|
||||
#[clap(alias = "s")]
|
||||
Show {
|
||||
/// Entries and days to show
|
||||
#[structopt(required = true)]
|
||||
#[clap(required = true)]
|
||||
identifiers: Vec<String>,
|
||||
},
|
||||
/// Create a new entry based on a template
|
||||
#[structopt(alias = "n")]
|
||||
#[clap(alias = "n")]
|
||||
New {
|
||||
#[structopt(subcommand)]
|
||||
#[clap(subcommand)]
|
||||
template: Template,
|
||||
},
|
||||
/// Marks one or more entries as done
|
||||
#[structopt(alias = "d")]
|
||||
#[clap(alias = "d")]
|
||||
Done {
|
||||
/// Entries to mark as done
|
||||
#[structopt(required = true)]
|
||||
#[clap(required = true)]
|
||||
entries: Vec<usize>,
|
||||
},
|
||||
/// Marks one or more entries as canceled
|
||||
#[structopt(alias = "c")]
|
||||
#[clap(alias = "c")]
|
||||
Cancel {
|
||||
/// Entries to mark as done
|
||||
#[structopt(required = true)]
|
||||
#[clap(required = true)]
|
||||
entries: Vec<usize>,
|
||||
},
|
||||
/// Edits or creates a log entry
|
||||
#[structopt(alias = "l")]
|
||||
#[clap(alias = "l")]
|
||||
Log {
|
||||
#[structopt(default_value = "t")]
|
||||
#[clap(default_value = "t")]
|
||||
date: String,
|
||||
},
|
||||
/// Reformats all loaded files
|
||||
Fmt,
|
||||
}
|
||||
|
||||
// TODO Add templates for tasks and notes
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Template {
|
||||
/// Adds a task
|
||||
#[structopt(alias = "t")]
|
||||
#[clap(alias = "t")]
|
||||
Task {
|
||||
/// If specified, the task is dated to this date
|
||||
date: Option<String>,
|
||||
},
|
||||
/// Adds a note
|
||||
#[structopt(alias = "n")]
|
||||
#[clap(alias = "n")]
|
||||
Note {
|
||||
/// If specified, the note is dated to this date
|
||||
date: Option<String>,
|
||||
},
|
||||
/// Adds an undated task marked as done today
|
||||
#[structopt(alias = "d")]
|
||||
#[clap(alias = "d")]
|
||||
Done,
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +221,7 @@ fn run_with_files(opt: Opt, files: &mut Files) -> Result<()> {
|
|||
}
|
||||
|
||||
pub fn run() {
|
||||
let opt = Opt::from_args();
|
||||
let opt = Opt::parse();
|
||||
|
||||
let mut files = Files::new();
|
||||
if let Err(e) = load_files(&opt, &mut files) {
|
||||
|
|
|
|||
|
|
@ -40,14 +40,15 @@ impl<'a, F> Eprint<'a, F> for Error
|
|||
where
|
||||
F: Files<'a, FileId = FileSource>,
|
||||
{
|
||||
#[allow(single_use_lifetimes)]
|
||||
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
|
||||
match self {
|
||||
Error::Eval(e) => e.eprint(files, config),
|
||||
Error::ArgumentParse { file, error } => error.eprint(file, config),
|
||||
Error::ArgumentEval { file, error } => error.eprint(file, config),
|
||||
Error::NoSuchEntry(n) => eprintln!("No entry with number {n}"),
|
||||
Error::NoSuchLog(date) => eprintln!("No log for {date}"),
|
||||
Error::NotATask(ns) => {
|
||||
Self::Eval(e) => e.eprint(files, config),
|
||||
Self::ArgumentParse { file, error } => error.eprint(file, config),
|
||||
Self::ArgumentEval { file, error } => error.eprint(file, config),
|
||||
Self::NoSuchEntry(n) => eprintln!("No entry with number {n}"),
|
||||
Self::NoSuchLog(date) => eprintln!("No log for {date}"),
|
||||
Self::NotATask(ns) => {
|
||||
if ns.is_empty() {
|
||||
eprintln!("Not a task.");
|
||||
} else if ns.len() == 1 {
|
||||
|
|
@ -57,8 +58,8 @@ where
|
|||
eprintln!("{} are not tasks.", ns.join(", "));
|
||||
}
|
||||
}
|
||||
Error::NoCaptureFile => eprintln!("No capture file found"),
|
||||
Error::EditingIo(error) => {
|
||||
Self::NoCaptureFile => eprintln!("No capture file found"),
|
||||
Self::EditingIo(error) => {
|
||||
eprintln!("Error while editing:");
|
||||
eprintln!(" {error}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ impl DayLayout {
|
|||
}
|
||||
}
|
||||
|
||||
fn sort_entries(entries: &mut Vec<(usize, &Entry)>) {
|
||||
fn sort_entries(entries: &mut [(usize, &Entry)]) {
|
||||
// Entries should be sorted by these factors, in descending order of
|
||||
// significance:
|
||||
// 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()));
|
||||
}
|
||||
|
||||
fn sort_day(day: &mut Vec<DayEntry>) {
|
||||
fn sort_day(day: &mut [DayEntry]) {
|
||||
// In a day, entries should be sorted into these categories:
|
||||
// 1. Untimed entries that end at the current day
|
||||
// 2. Timed entries, based on
|
||||
|
|
|
|||
|
|
@ -36,15 +36,17 @@ impl SpanStyle {
|
|||
pub enum SpanSegment {
|
||||
Start(SpanStyle),
|
||||
Middle(SpanStyle),
|
||||
Mark(SpanStyle),
|
||||
End(SpanStyle),
|
||||
}
|
||||
|
||||
impl SpanSegment {
|
||||
fn style(&self) -> SpanStyle {
|
||||
match self {
|
||||
SpanSegment::Start(s) => *s,
|
||||
SpanSegment::Middle(s) => *s,
|
||||
SpanSegment::End(s) => *s,
|
||||
Self::Start(s) => *s,
|
||||
Self::Middle(s) => *s,
|
||||
Self::Mark(s) => *s,
|
||||
Self::End(s) => *s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +58,7 @@ pub enum Times {
|
|||
FromTo(Time, Time),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LineKind {
|
||||
Task,
|
||||
Done,
|
||||
|
|
@ -79,6 +81,7 @@ pub enum LineEntry {
|
|||
Entry {
|
||||
number: Option<usize>,
|
||||
spans: Vec<Option<SpanSegment>>,
|
||||
today: bool,
|
||||
time: Times,
|
||||
kind: LineKind,
|
||||
text: String,
|
||||
|
|
@ -121,17 +124,18 @@ impl LineLayout {
|
|||
self.step_spans();
|
||||
|
||||
for day in layout.range.days() {
|
||||
let today = day == layout.today;
|
||||
let spans = self.spans_for_line();
|
||||
self.line(LineEntry::Day {
|
||||
spans,
|
||||
date: day,
|
||||
today: day == layout.today,
|
||||
today,
|
||||
has_log: files.log(day).is_some(),
|
||||
});
|
||||
|
||||
let layout_entries = layout.days.get(&day).expect("got nonexisting day");
|
||||
for layout_entry in layout_entries {
|
||||
self.render_layout_entry(entries, layout_entry);
|
||||
self.render_layout_entry(entries, layout_entry, today);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,11 +161,11 @@ impl LineLayout {
|
|||
.ok_or(Error::NoSuchEntry(number))
|
||||
}
|
||||
|
||||
fn render_layout_entry(&mut self, entries: &[Entry], l_entry: &DayEntry) {
|
||||
fn render_layout_entry(&mut self, entries: &[Entry], l_entry: &DayEntry, today: bool) {
|
||||
match l_entry {
|
||||
DayEntry::End(i) => {
|
||||
self.stop_span(*i);
|
||||
self.line_entry(entries, *i, Times::Untimed, None);
|
||||
self.line_entry(entries, *i, today, Times::Untimed, None);
|
||||
}
|
||||
DayEntry::Now(t) => self.line(LineEntry::Now {
|
||||
spans: self.spans_for_line(),
|
||||
|
|
@ -169,17 +173,15 @@ impl LineLayout {
|
|||
}),
|
||||
DayEntry::TimedEnd(i, t) => {
|
||||
self.stop_span(*i);
|
||||
self.line_entry(entries, *i, Times::At(*t), None);
|
||||
self.line_entry(entries, *i, today, Times::At(*t), None);
|
||||
}
|
||||
DayEntry::TimedAt(i, t, t2) => {
|
||||
let time = t2
|
||||
.map(|t2| Times::FromTo(*t, t2))
|
||||
.unwrap_or_else(|| Times::At(*t));
|
||||
self.line_entry(entries, *i, time, None);
|
||||
let time = t2.map(|t2| Times::FromTo(*t, t2)).unwrap_or(Times::At(*t));
|
||||
self.line_entry(entries, *i, today, time, None);
|
||||
}
|
||||
DayEntry::TimedStart(i, t) => {
|
||||
self.start_span(*i);
|
||||
self.line_entry(entries, *i, Times::At(*t), None);
|
||||
self.line_entry(entries, *i, today, Times::At(*t), None);
|
||||
}
|
||||
DayEntry::ReminderSince(i, d) => {
|
||||
let extra = if *d == 1 {
|
||||
|
|
@ -187,22 +189,23 @@ impl LineLayout {
|
|||
} else {
|
||||
format!("{d} days ago")
|
||||
};
|
||||
self.line_entry(entries, *i, Times::Untimed, Some(extra));
|
||||
self.line_entry(entries, *i, today, Times::Untimed, Some(extra));
|
||||
}
|
||||
DayEntry::At(i) => {
|
||||
self.line_entry(entries, *i, Times::Untimed, None);
|
||||
self.line_entry(entries, *i, today, Times::Untimed, None);
|
||||
}
|
||||
DayEntry::ReminderWhile(i, d) => {
|
||||
let plural = if *d == 1 { "" } else { "s" };
|
||||
let extra = format!("{d} day{plural} left");
|
||||
self.line_entry(entries, *i, Times::Untimed, Some(extra));
|
||||
self.mark_span(*i);
|
||||
self.line_entry(entries, *i, today, Times::Untimed, Some(extra));
|
||||
}
|
||||
DayEntry::Undated(i) => {
|
||||
self.line_entry(entries, *i, Times::Untimed, None);
|
||||
self.line_entry(entries, *i, today, Times::Untimed, None);
|
||||
}
|
||||
DayEntry::Start(i) => {
|
||||
self.start_span(*i);
|
||||
self.line_entry(entries, *i, Times::Untimed, None);
|
||||
self.line_entry(entries, *i, today, Times::Untimed, None);
|
||||
}
|
||||
DayEntry::ReminderUntil(i, d) => {
|
||||
let extra = if *d == 1 {
|
||||
|
|
@ -210,7 +213,7 @@ impl LineLayout {
|
|||
} else {
|
||||
format!("in {d} days")
|
||||
};
|
||||
self.line_entry(entries, *i, Times::Untimed, Some(extra));
|
||||
self.line_entry(entries, *i, today, Times::Untimed, Some(extra));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -246,6 +249,15 @@ impl LineLayout {
|
|||
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) {
|
||||
for span in self.spans.iter_mut() {
|
||||
match span {
|
||||
|
|
@ -258,7 +270,9 @@ impl LineLayout {
|
|||
fn step_spans(&mut self) {
|
||||
for span in self.spans.iter_mut() {
|
||||
match span {
|
||||
Some((_, s @ SpanSegment::Start(_))) => *s = SpanSegment::Middle(s.style()),
|
||||
Some((_, s @ (SpanSegment::Start(_) | SpanSegment::Mark(_)))) => {
|
||||
*s = SpanSegment::Middle(s.style())
|
||||
}
|
||||
Some((_, SpanSegment::End(_))) => *span = None,
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -277,7 +291,14 @@ impl LineLayout {
|
|||
self.step_spans();
|
||||
}
|
||||
|
||||
fn line_entry(&mut self, entries: &[Entry], index: usize, time: Times, extra: Option<String>) {
|
||||
fn line_entry(
|
||||
&mut self,
|
||||
entries: &[Entry],
|
||||
index: usize,
|
||||
today: bool,
|
||||
time: Times,
|
||||
extra: Option<String>,
|
||||
) {
|
||||
let entry = &entries[index];
|
||||
|
||||
let number = match self.numbers.get(&index) {
|
||||
|
|
@ -292,6 +313,7 @@ impl LineLayout {
|
|||
self.line(LineEntry::Entry {
|
||||
number: Some(number),
|
||||
spans: self.spans_for_line(),
|
||||
today,
|
||||
time,
|
||||
kind: Self::entry_kind(entry),
|
||||
text: Self::entry_title(entry),
|
||||
|
|
|
|||
|
|
@ -35,12 +35,14 @@ impl ShowLines {
|
|||
LineEntry::Entry {
|
||||
number,
|
||||
spans,
|
||||
today,
|
||||
time,
|
||||
kind,
|
||||
text,
|
||||
has_desc,
|
||||
extra,
|
||||
} => self.display_line_entry(*number, spans, *time, *kind, text, *has_desc, extra),
|
||||
} => self
|
||||
.display_line_entry(*number, spans, *today, *time, *kind, text, *has_desc, extra),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +101,7 @@ impl ShowLines {
|
|||
&mut self,
|
||||
number: Option<usize>,
|
||||
spans: &[Option<SpanSegment>],
|
||||
today: bool,
|
||||
time: Times,
|
||||
kind: LineKind,
|
||||
text: &str,
|
||||
|
|
@ -110,6 +113,12 @@ impl ShowLines {
|
|||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let text = if kind == LineKind::Birthday && today {
|
||||
util::display_current_birthday_text(text)
|
||||
} else {
|
||||
text.into()
|
||||
};
|
||||
|
||||
self.push(&format!(
|
||||
"{:>nw$} {} {}{} {}{}{}\n",
|
||||
num.bright_black(),
|
||||
|
|
@ -132,6 +141,7 @@ impl ShowLines {
|
|||
SpanSegment::Middle(SpanStyle::Solid) => "│".bright_black(),
|
||||
SpanSegment::Middle(SpanStyle::Dashed) => "╎".bright_black(),
|
||||
SpanSegment::Middle(SpanStyle::Dotted) => "┊".bright_black(),
|
||||
SpanSegment::Mark(_) => "┝".bright_black(),
|
||||
SpanSegment::End(_) => "└".bright_black(),
|
||||
};
|
||||
result.push_str(&format!("{colored_str}"));
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ 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> {
|
||||
edit::edit(input).map_err(Error::EditingIo)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use codespan_reporting::term::{self, Config};
|
|||
use termcolor::StandardStream;
|
||||
|
||||
pub trait Eprint<'a, F: Files<'a>> {
|
||||
#[allow(single_use_lifetimes)]
|
||||
fn eprint_diagnostic<'f: 'a>(
|
||||
files: &'f F,
|
||||
config: &Config,
|
||||
|
|
@ -15,9 +16,11 @@ pub trait Eprint<'a, F: Files<'a>> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(single_use_lifetimes)]
|
||||
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)
|
||||
where
|
||||
F: Files<'a>,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use super::super::date::Dates;
|
|||
use super::super::error::Error;
|
||||
use super::super::EntryKind;
|
||||
|
||||
impl<'a> CommandState<'a> {
|
||||
impl CommandState<'_> {
|
||||
pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<(), Error<FileSource>> {
|
||||
let range = match self.limit_from_until(self.range_with_remind()) {
|
||||
Some(range) => range,
|
||||
|
|
@ -35,8 +35,8 @@ impl<'a> CommandState<'a> {
|
|||
assert_eq!(spec.date.month(), 2);
|
||||
assert_eq!(spec.date.day(), 29);
|
||||
|
||||
let first = NaiveDate::from_ymd(year, 2, 28);
|
||||
let second = NaiveDate::from_ymd(year, 3, 1);
|
||||
let first = NaiveDate::from_ymd_opt(year, 2, 28).unwrap();
|
||||
let second = NaiveDate::from_ymd_opt(year, 3, 1).unwrap();
|
||||
self.add(self.entry_with_remind(kind, Some(Dates::new(first, second)))?);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ impl DateSpec {
|
|||
let range_from = s
|
||||
.command
|
||||
.last_done_root()
|
||||
.map(|date| date.succ())
|
||||
.map(|date| date.succ_opt().unwrap())
|
||||
.unwrap_or(self.start);
|
||||
let range = s
|
||||
.range_with_remind()
|
||||
|
|
@ -137,7 +137,7 @@ impl DateSpec {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> CommandState<'a> {
|
||||
impl CommandState<'_> {
|
||||
pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<(), Error<FileSource>> {
|
||||
let index = self.source.file();
|
||||
if let Some(repeat) = &spec.repeat {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -43,84 +43,84 @@ impl DeltaStep {
|
|||
/// A lower bound on days
|
||||
fn lower_bound(&self) -> i32 {
|
||||
match self {
|
||||
DeltaStep::Year(n) => {
|
||||
Self::Year(n) => {
|
||||
if *n < 0 {
|
||||
*n * 366
|
||||
} else {
|
||||
*n * 365
|
||||
}
|
||||
}
|
||||
DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => {
|
||||
Self::Month(n) | Self::MonthReverse(n) => {
|
||||
if *n < 0 {
|
||||
*n * 31
|
||||
} else {
|
||||
*n * 28
|
||||
}
|
||||
}
|
||||
DeltaStep::Day(n) => *n,
|
||||
DeltaStep::Week(n) => *n * 7,
|
||||
DeltaStep::Hour(n) => {
|
||||
Self::Day(n) => *n,
|
||||
Self::Week(n) => *n * 7,
|
||||
Self::Hour(n) => {
|
||||
if *n < 0 {
|
||||
*n / 24 + (*n % 24).signum()
|
||||
} else {
|
||||
*n / 24
|
||||
}
|
||||
}
|
||||
DeltaStep::Minute(n) => {
|
||||
Self::Minute(n) => {
|
||||
if *n < 0 {
|
||||
*n / (24 * 60) + (*n % (24 * 60)).signum()
|
||||
} else {
|
||||
*n / (24 * 60)
|
||||
}
|
||||
}
|
||||
DeltaStep::Weekday(n, _) => match n.cmp(&0) {
|
||||
Self::Weekday(n, _) => match n.cmp(&0) {
|
||||
Ordering::Less => *n * 7 - 1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => *n * 7 - 7,
|
||||
},
|
||||
DeltaStep::Time(_) => 0,
|
||||
Self::Time(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// An upper bound on days
|
||||
fn upper_bound(&self) -> i32 {
|
||||
match self {
|
||||
DeltaStep::Year(n) => {
|
||||
Self::Year(n) => {
|
||||
if *n > 0 {
|
||||
*n * 366
|
||||
} else {
|
||||
*n * 365
|
||||
}
|
||||
}
|
||||
DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => {
|
||||
Self::Month(n) | Self::MonthReverse(n) => {
|
||||
if *n > 0 {
|
||||
*n * 31
|
||||
} else {
|
||||
*n * 28
|
||||
}
|
||||
}
|
||||
DeltaStep::Day(n) => *n,
|
||||
DeltaStep::Week(n) => *n * 7,
|
||||
DeltaStep::Hour(n) => {
|
||||
Self::Day(n) => *n,
|
||||
Self::Week(n) => *n * 7,
|
||||
Self::Hour(n) => {
|
||||
if *n > 0 {
|
||||
*n / 24 + (*n % 24).signum()
|
||||
} else {
|
||||
*n / 24
|
||||
}
|
||||
}
|
||||
DeltaStep::Minute(n) => {
|
||||
Self::Minute(n) => {
|
||||
if *n > 0 {
|
||||
*n / (24 * 60) + (*n % (24 * 60)).signum()
|
||||
} else {
|
||||
*n / (24 * 60)
|
||||
}
|
||||
}
|
||||
DeltaStep::Weekday(n, _) => match n.cmp(&0) {
|
||||
Self::Weekday(n, _) => match n.cmp(&0) {
|
||||
Ordering::Less => *n * 7 - 7,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => *n * 7 - 1,
|
||||
},
|
||||
DeltaStep::Time(_) => 1,
|
||||
Self::Time(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -298,7 +298,7 @@ impl<S: Copy> DeltaEval<S> {
|
|||
};
|
||||
|
||||
if time < curr_time {
|
||||
self.curr = self.curr.succ();
|
||||
self.curr = self.curr.succ_opt().unwrap();
|
||||
}
|
||||
self.curr_time = Some(time);
|
||||
Ok(())
|
||||
|
|
@ -359,13 +359,13 @@ mod tests {
|
|||
}
|
||||
|
||||
fn apply_d(step: Step, from: (i32, u32, u32)) -> Result<NaiveDate, Error<()>> {
|
||||
delta(step).apply_date((), NaiveDate::from_ymd(from.0, from.1, from.2))
|
||||
delta(step).apply_date((), NaiveDate::from_ymd_opt(from.0, from.1, from.2).unwrap())
|
||||
}
|
||||
|
||||
fn test_d(step: Step, from: (i32, u32, u32), expected: (i32, u32, u32)) {
|
||||
assert_eq!(
|
||||
apply_d(step, from).unwrap(),
|
||||
NaiveDate::from_ymd(expected.0, expected.1, expected.2)
|
||||
NaiveDate::from_ymd_opt(expected.0, expected.1, expected.2).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +375,7 @@ mod tests {
|
|||
) -> Result<(NaiveDate, Time), Error<()>> {
|
||||
delta(step).apply_date_time(
|
||||
(),
|
||||
NaiveDate::from_ymd(from.0, from.1, from.2),
|
||||
NaiveDate::from_ymd_opt(from.0, from.1, from.2).unwrap(),
|
||||
Time::new(from.3, from.4),
|
||||
)
|
||||
}
|
||||
|
|
@ -385,7 +385,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
apply_dt(step, from).unwrap(),
|
||||
(
|
||||
NaiveDate::from_ymd(expected.0, expected.1, expected.2),
|
||||
NaiveDate::from_ymd_opt(expected.0, expected.1, expected.2).unwrap(),
|
||||
Time::new(expected.3, expected.4)
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ impl Entry {
|
|||
|
||||
/// Mode that determines how entries are filtered when they are added to
|
||||
/// an [`Entries`].
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EntryMode {
|
||||
/// The entry's root date must be contained in the range.
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ impl<S> Error<S> {
|
|||
}
|
||||
|
||||
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) {
|
||||
let diagnostic = match self {
|
||||
Error::DeltaInvalidStep {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ impl DateRange {
|
|||
/// Return a new range with its [`Self::until`] set to a new value.
|
||||
///
|
||||
/// Returns [`None`] if the new value is earlier than [`Self::from`].
|
||||
#[allow(dead_code)]
|
||||
pub fn with_until(&self, until: NaiveDate) -> Option<Self> {
|
||||
if self.from <= until {
|
||||
Some(Self::new(self.from, until))
|
||||
|
|
@ -75,7 +76,7 @@ impl DateRange {
|
|||
|
||||
pub fn days(&self) -> impl Iterator<Item = NaiveDate> {
|
||||
(self.from.num_days_from_ce()..=self.until.num_days_from_ce())
|
||||
.map(NaiveDate::from_num_days_from_ce)
|
||||
.map(|days| NaiveDate::from_num_days_from_ce_opt(days).unwrap())
|
||||
}
|
||||
|
||||
pub fn years(&self) -> RangeInclusive<i32> {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ pub fn is_iso_leap_year(year: i32) -> bool {
|
|||
}
|
||||
|
||||
pub fn year_length(year: i32) -> u32 {
|
||||
NaiveDate::from_ymd(year, 12, 31).ordinal()
|
||||
NaiveDate::from_ymd_opt(year, 12, 31).unwrap().ordinal()
|
||||
}
|
||||
|
||||
pub fn month_length(year: i32, month: u32) -> u32 {
|
||||
NaiveDate::from_ymd_opt(year, month + 1, 1)
|
||||
.unwrap_or_else(|| NaiveDate::from_ymd(year + 1, 1, 1))
|
||||
.pred()
|
||||
.unwrap_or_else(|| NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap())
|
||||
.pred_opt()
|
||||
.unwrap()
|
||||
.day()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ fn from_str_via_parse<P, R>(s: &str, rule: Rule, parse: P) -> result::Result<R,
|
|||
where
|
||||
P: FnOnce(Pair<'_, Rule>) -> Result<R>,
|
||||
{
|
||||
let mut pairs = TodayfileParser::parse(rule, s).map_err(|e| ParseError::new((), e))?;
|
||||
let mut pairs =
|
||||
TodayfileParser::parse(rule, s).map_err(|e| ParseError::new((), Box::new(e)))?;
|
||||
let p = pairs.next().unwrap();
|
||||
assert_eq!(pairs.next(), None);
|
||||
|
||||
|
|
@ -57,11 +58,17 @@ fn parse_cli_date(p: Pair<'_, Rule>) -> Result<CliDate> {
|
|||
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 {
|
||||
type Err = ParseError<()>;
|
||||
|
||||
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
|
||||
from_str_via_parse(s, Rule::cli_date, parse_cli_date)
|
||||
from_str_via_parse(s, Rule::cli_date_arg, parse_cli_date_arg)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,11 +88,17 @@ 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 {
|
||||
type Err = ParseError<()>;
|
||||
|
||||
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
|
||||
from_str_via_parse(s, Rule::cli_ident, parse_cli_ident)
|
||||
from_str_via_parse(s, Rule::cli_ident_arg, parse_cli_ident_arg)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,11 +148,12 @@ fn parse_cli_range(p: Pair<'_, Rule>) -> Result<CliRange> {
|
|||
|
||||
let (start, start_delta) = parse_cli_range_start(p.next().unwrap())?;
|
||||
let (end, end_delta) = match p.next() {
|
||||
// For some reason, the EOI gets captured but the SOI doesn't.
|
||||
Some(p) if p.as_rule() != Rule::EOI => parse_cli_range_end(p)?,
|
||||
_ => (None, None),
|
||||
Some(p) => parse_cli_range_end(p)?,
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
assert_eq!(p.next(), None);
|
||||
|
||||
Ok(CliRange {
|
||||
start,
|
||||
start_delta,
|
||||
|
|
@ -148,11 +162,17 @@ 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 {
|
||||
type Err = ParseError<()>;
|
||||
|
||||
fn from_str(s: &str) -> result::Result<Self, ParseError<()>> {
|
||||
from_str_via_parse(s, Rule::cli_range, parse_cli_range)
|
||||
from_str_via_parse(s, Rule::cli_range_arg, parse_cli_range_arg)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,27 +27,27 @@ pub enum DeltaStep {
|
|||
impl DeltaStep {
|
||||
pub fn amount(&self) -> i32 {
|
||||
match self {
|
||||
DeltaStep::Year(i) => *i,
|
||||
DeltaStep::Month(i) => *i,
|
||||
DeltaStep::MonthReverse(i) => *i,
|
||||
DeltaStep::Day(i) => *i,
|
||||
DeltaStep::Week(i) => *i,
|
||||
DeltaStep::Hour(i) => *i,
|
||||
DeltaStep::Minute(i) => *i,
|
||||
DeltaStep::Weekday(i, _) => *i,
|
||||
Self::Year(i) => *i,
|
||||
Self::Month(i) => *i,
|
||||
Self::MonthReverse(i) => *i,
|
||||
Self::Day(i) => *i,
|
||||
Self::Week(i) => *i,
|
||||
Self::Hour(i) => *i,
|
||||
Self::Minute(i) => *i,
|
||||
Self::Weekday(i, _) => *i,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
DeltaStep::Year(_) => "y",
|
||||
DeltaStep::Month(_) => "m",
|
||||
DeltaStep::MonthReverse(_) => "M",
|
||||
DeltaStep::Day(_) => "d",
|
||||
DeltaStep::Week(_) => "w",
|
||||
DeltaStep::Hour(_) => "h",
|
||||
DeltaStep::Minute(_) => "min",
|
||||
DeltaStep::Weekday(_, wd) => wd.name(),
|
||||
Self::Year(_) => "y",
|
||||
Self::Month(_) => "m",
|
||||
Self::MonthReverse(_) => "M",
|
||||
Self::Day(_) => "d",
|
||||
Self::Week(_) => "w",
|
||||
Self::Hour(_) => "h",
|
||||
Self::Minute(_) => "min",
|
||||
Self::Weekday(_, wd) => wd.name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,39 +168,39 @@ impl Var {
|
|||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
// Constants
|
||||
Var::True => "true",
|
||||
Var::False => "false",
|
||||
Var::Monday => "mon",
|
||||
Var::Tuesday => "tue",
|
||||
Var::Wednesday => "wed",
|
||||
Var::Thursday => "thu",
|
||||
Var::Friday => "fri",
|
||||
Var::Saturday => "sat",
|
||||
Var::Sunday => "sun",
|
||||
Self::True => "true",
|
||||
Self::False => "false",
|
||||
Self::Monday => "mon",
|
||||
Self::Tuesday => "tue",
|
||||
Self::Wednesday => "wed",
|
||||
Self::Thursday => "thu",
|
||||
Self::Friday => "fri",
|
||||
Self::Saturday => "sat",
|
||||
Self::Sunday => "sun",
|
||||
// Variables
|
||||
Var::JulianDay => "j",
|
||||
Var::Year => "y",
|
||||
Var::YearLength => "yl",
|
||||
Var::YearDay => "yd",
|
||||
Var::YearDayReverse => "yD",
|
||||
Var::YearWeek => "yw",
|
||||
Var::YearWeekReverse => "yW",
|
||||
Var::Month => "m",
|
||||
Var::MonthLength => "ml",
|
||||
Var::MonthWeek => "mw",
|
||||
Var::MonthWeekReverse => "mW",
|
||||
Var::Day => "d",
|
||||
Var::DayReverse => "D",
|
||||
Var::IsoYear => "iy",
|
||||
Var::IsoYearLength => "iyl",
|
||||
Var::IsoWeek => "iw",
|
||||
Var::Weekday => "wd",
|
||||
Var::Easter => "e",
|
||||
Self::JulianDay => "j",
|
||||
Self::Year => "y",
|
||||
Self::YearLength => "yl",
|
||||
Self::YearDay => "yd",
|
||||
Self::YearDayReverse => "yD",
|
||||
Self::YearWeek => "yw",
|
||||
Self::YearWeekReverse => "yW",
|
||||
Self::Month => "m",
|
||||
Self::MonthLength => "ml",
|
||||
Self::MonthWeek => "mw",
|
||||
Self::MonthWeekReverse => "mW",
|
||||
Self::Day => "d",
|
||||
Self::DayReverse => "D",
|
||||
Self::IsoYear => "iy",
|
||||
Self::IsoYearLength => "iyl",
|
||||
Self::IsoWeek => "iw",
|
||||
Self::Weekday => "wd",
|
||||
Self::Easter => "e",
|
||||
// Variables with "boolean" values
|
||||
Var::IsWeekday => "isWeekday",
|
||||
Var::IsWeekend => "isWeekend",
|
||||
Var::IsLeapYear => "isLeapYear",
|
||||
Var::IsIsoLeapYear => "isIsoLeapYear",
|
||||
Self::IsWeekday => "isWeekday",
|
||||
Self::IsWeekend => "isWeekend",
|
||||
Self::IsLeapYear => "isLeapYear",
|
||||
Self::IsIsoLeapYear => "isIsoLeapYear",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -301,11 +301,11 @@ pub enum DoneDate {
|
|||
impl DoneDate {
|
||||
pub fn root(self) -> NaiveDate {
|
||||
match self {
|
||||
DoneDate::Date { root } => root,
|
||||
DoneDate::DateTime { root, .. } => root,
|
||||
DoneDate::DateToDate { root, .. } => root,
|
||||
DoneDate::DateTimeToTime { root, .. } => root,
|
||||
DoneDate::DateTimeToDateTime { root, .. } => root,
|
||||
Self::Date { root } => root,
|
||||
Self::DateTime { root, .. } => root,
|
||||
Self::DateToDate { root, .. } => root,
|
||||
Self::DateTimeToTime { root, .. } => root,
|
||||
Self::DateTimeToDateTime { root, .. } => root,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +346,9 @@ pub enum DoneKind {
|
|||
#[derive(Debug)]
|
||||
pub struct Done {
|
||||
pub kind: DoneKind,
|
||||
/// The date of the task the DONE refers to.
|
||||
pub date: Option<DoneDate>,
|
||||
/// When the task was actually completed.
|
||||
pub done_at: NaiveDate,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ use super::{parse, FileSource, Files};
|
|||
#[error("{error}")]
|
||||
pub struct ParseError<S> {
|
||||
file: S,
|
||||
error: parse::Error,
|
||||
error: Box<parse::Error>,
|
||||
}
|
||||
|
||||
impl<S> ParseError<S> {
|
||||
pub fn new(file: S, error: parse::Error) -> Self {
|
||||
pub fn new(file: S, error: Box<parse::Error>) -> Self {
|
||||
Self { file, error }
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +69,7 @@ impl<'a, F> Eprint<'a, F> for ParseError<F::FileId>
|
|||
where
|
||||
F: codespan_reporting::files::Files<'a>,
|
||||
{
|
||||
#[allow(single_use_lifetimes)]
|
||||
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
|
||||
let range = match self.error.location {
|
||||
InputLocation::Pos(at) => at..at,
|
||||
|
|
@ -103,7 +104,7 @@ pub enum Error {
|
|||
#[error("{error}")]
|
||||
Parse {
|
||||
file: FileSource,
|
||||
error: parse::Error,
|
||||
error: Box<parse::Error>,
|
||||
},
|
||||
#[error("Conflicting time zones {tz1} and {tz2}")]
|
||||
TzConflict {
|
||||
|
|
@ -132,21 +133,22 @@ pub enum Error {
|
|||
}
|
||||
|
||||
impl<'a> Eprint<'a, Files> for Error {
|
||||
#[allow(single_use_lifetimes)]
|
||||
fn eprint<'f: 'a>(&self, files: &'f Files, config: &Config) {
|
||||
match self {
|
||||
Error::ResolvePath { path, error } => {
|
||||
Self::ResolvePath { path, error } => {
|
||||
eprintln!("Could not resolve path {path:?}:");
|
||||
eprintln!(" {error}");
|
||||
}
|
||||
Error::ReadFile { file, error } => {
|
||||
Self::ReadFile { file, error } => {
|
||||
eprintln!("Could not read file {file:?}:");
|
||||
eprintln!(" {error}");
|
||||
}
|
||||
Error::WriteFile { file, error } => {
|
||||
Self::WriteFile { file, error } => {
|
||||
eprintln!("Could not write file {file:?}:");
|
||||
eprintln!(" {error}");
|
||||
}
|
||||
Error::ResolveTz {
|
||||
Self::ResolveTz {
|
||||
file,
|
||||
span,
|
||||
tz,
|
||||
|
|
@ -158,14 +160,14 @@ impl<'a> Eprint<'a, Files> for Error {
|
|||
.with_notes(vec![format!("{error}")]);
|
||||
Self::eprint_diagnostic(files, config, &diagnostic);
|
||||
}
|
||||
Error::LocalTz { error } => {
|
||||
Self::LocalTz { error } => {
|
||||
eprintln!("Could not determine local timezone:");
|
||||
eprintln!(" {error}");
|
||||
}
|
||||
Error::Parse { file, error } => {
|
||||
Self::Parse { file, error } => {
|
||||
ParseError::new(*file, error.clone()).eprint(files, config)
|
||||
}
|
||||
Error::TzConflict {
|
||||
Self::TzConflict {
|
||||
file1,
|
||||
span1,
|
||||
tz1,
|
||||
|
|
@ -184,7 +186,7 @@ impl<'a> Eprint<'a, Files> for Error {
|
|||
]);
|
||||
Self::eprint_diagnostic(files, config, &diagnostic);
|
||||
}
|
||||
Error::MultipleCapture {
|
||||
Self::MultipleCapture {
|
||||
file1,
|
||||
span1,
|
||||
file2,
|
||||
|
|
@ -201,7 +203,7 @@ impl<'a> Eprint<'a, Files> for Error {
|
|||
]);
|
||||
Self::eprint_diagnostic(files, config, &diagnostic);
|
||||
}
|
||||
Error::LogConflict {
|
||||
Self::LogConflict {
|
||||
file1,
|
||||
span1,
|
||||
file2,
|
||||
|
|
|
|||
|
|
@ -75,10 +75,10 @@ impl fmt::Display for DateSpec {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Start
|
||||
write!(f, "{}", self.start)?;
|
||||
for delta in &self.start_delta {
|
||||
if let Some(delta) = &self.start_delta {
|
||||
write!(f, " {delta}")?;
|
||||
}
|
||||
for time in &self.start_time {
|
||||
if let Some(time) = &self.start_time {
|
||||
write!(f, " {time}")?;
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ impl fmt::Display for WeekdaySpec {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Start
|
||||
write!(f, "{}", self.start)?;
|
||||
for time in &self.start_time {
|
||||
if let Some(time) = &self.start_time {
|
||||
write!(f, " {time}")?;
|
||||
}
|
||||
|
||||
|
|
@ -140,25 +140,25 @@ impl fmt::Display for Var {
|
|||
impl fmt::Display for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Expr::Lit(i) => write!(f, "{i}"),
|
||||
Expr::Var(v) => write!(f, "{v}"),
|
||||
Expr::Paren(e) => write!(f, "({e})"),
|
||||
Expr::Neg(e) => write!(f, "-{e}"),
|
||||
Expr::Add(a, b) => write!(f, "{a} + {b}"),
|
||||
Expr::Sub(a, b) => write!(f, "{a} - {b}"),
|
||||
Expr::Mul(a, b) => write!(f, "{a} * {b}"),
|
||||
Expr::Div(a, b) => write!(f, "{a} / {b}"),
|
||||
Expr::Mod(a, b) => write!(f, "{a} % {b}"),
|
||||
Expr::Eq(a, b) => write!(f, "{a} = {b}"),
|
||||
Expr::Neq(a, b) => write!(f, "{a} != {b}"),
|
||||
Expr::Lt(a, b) => write!(f, "{a} < {b}"),
|
||||
Expr::Lte(a, b) => write!(f, "{a} <= {b}"),
|
||||
Expr::Gt(a, b) => write!(f, "{a} > {b}"),
|
||||
Expr::Gte(a, b) => write!(f, "{a} >= {b}"),
|
||||
Expr::Not(e) => write!(f, "!{e}"),
|
||||
Expr::And(a, b) => write!(f, "{a} & {b}"),
|
||||
Expr::Or(a, b) => write!(f, "{a} | {b}"),
|
||||
Expr::Xor(a, b) => write!(f, "{a} ^ {b}"),
|
||||
Self::Lit(i) => write!(f, "{i}"),
|
||||
Self::Var(v) => write!(f, "{v}"),
|
||||
Self::Paren(e) => write!(f, "({e})"),
|
||||
Self::Neg(e) => write!(f, "-{e}"),
|
||||
Self::Add(a, b) => write!(f, "{a} + {b}"),
|
||||
Self::Sub(a, b) => write!(f, "{a} - {b}"),
|
||||
Self::Mul(a, b) => write!(f, "{a} * {b}"),
|
||||
Self::Div(a, b) => write!(f, "{a} / {b}"),
|
||||
Self::Mod(a, b) => write!(f, "{a} % {b}"),
|
||||
Self::Eq(a, b) => write!(f, "{a} = {b}"),
|
||||
Self::Neq(a, b) => write!(f, "{a} != {b}"),
|
||||
Self::Lt(a, b) => write!(f, "{a} < {b}"),
|
||||
Self::Lte(a, b) => write!(f, "{a} <= {b}"),
|
||||
Self::Gt(a, b) => write!(f, "{a} > {b}"),
|
||||
Self::Gte(a, b) => write!(f, "{a} >= {b}"),
|
||||
Self::Not(e) => write!(f, "!{e}"),
|
||||
Self::And(a, b) => write!(f, "{a} & {b}"),
|
||||
Self::Or(a, b) => write!(f, "{a} | {b}"),
|
||||
Self::Xor(a, b) => write!(f, "{a} ^ {b}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -171,10 +171,10 @@ impl fmt::Display for FormulaSpec {
|
|||
} else {
|
||||
write!(f, "*")?;
|
||||
}
|
||||
for delta in &self.start_delta {
|
||||
if let Some(delta) = &self.start_delta {
|
||||
write!(f, " {delta}")?;
|
||||
}
|
||||
for time in &self.start_time {
|
||||
if let Some(time) = &self.start_time {
|
||||
write!(f, " {time}")?;
|
||||
}
|
||||
|
||||
|
|
@ -196,9 +196,9 @@ impl fmt::Display for FormulaSpec {
|
|||
impl fmt::Display for Spec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Spec::Date(spec) => write!(f, "{spec}"),
|
||||
Spec::Weekday(spec) => write!(f, "{spec}"),
|
||||
Spec::Formula(spec) => write!(f, "{spec}"),
|
||||
Self::Date(spec) => write!(f, "{spec}"),
|
||||
Self::Weekday(spec) => write!(f, "{spec}"),
|
||||
Self::Formula(spec) => write!(f, "{spec}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -216,14 +216,14 @@ impl fmt::Display for BirthdaySpec {
|
|||
impl fmt::Display for Statement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Statement::Date(spec) => writeln!(f, "DATE {spec}"),
|
||||
Statement::BDate(spec) => writeln!(f, "BDATE {spec}"),
|
||||
Statement::From(Some(date)) => writeln!(f, "FROM {date}"),
|
||||
Statement::From(None) => writeln!(f, "FROM *"),
|
||||
Statement::Until(Some(date)) => writeln!(f, "UNTIL {date}"),
|
||||
Statement::Until(None) => writeln!(f, "UNTIL *"),
|
||||
Statement::Except(date) => writeln!(f, "EXCEPT {date}"),
|
||||
Statement::Move {
|
||||
Self::Date(spec) => writeln!(f, "DATE {spec}"),
|
||||
Self::BDate(spec) => writeln!(f, "BDATE {spec}"),
|
||||
Self::From(Some(date)) => writeln!(f, "FROM {date}"),
|
||||
Self::From(None) => writeln!(f, "FROM *"),
|
||||
Self::Until(Some(date)) => writeln!(f, "UNTIL {date}"),
|
||||
Self::Until(None) => writeln!(f, "UNTIL *"),
|
||||
Self::Except(date) => writeln!(f, "EXCEPT {date}"),
|
||||
Self::Move {
|
||||
from, to, to_time, ..
|
||||
} => match (to, to_time) {
|
||||
(None, None) => unreachable!(),
|
||||
|
|
@ -231,8 +231,8 @@ impl fmt::Display for Statement {
|
|||
(None, Some(to_time)) => writeln!(f, "MOVE {from} TO {to_time}"),
|
||||
(Some(to), Some(to_time)) => writeln!(f, "MOVE {from} TO {to} {to_time}"),
|
||||
},
|
||||
Statement::Remind(Some(delta)) => writeln!(f, "REMIND {delta}"),
|
||||
Statement::Remind(None) => writeln!(f, "REMIND *"),
|
||||
Self::Remind(Some(delta)) => writeln!(f, "REMIND {delta}"),
|
||||
Self::Remind(None) => writeln!(f, "REMIND *"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -240,15 +240,15 @@ impl fmt::Display for Statement {
|
|||
impl fmt::Display for DoneDate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.simplified() {
|
||||
DoneDate::Date { root } => write!(f, "{root}"),
|
||||
DoneDate::DateTime { root, root_time } => write!(f, "{root} {root_time}"),
|
||||
DoneDate::DateToDate { root, other } => write!(f, "{root} -- {other}"),
|
||||
DoneDate::DateTimeToTime {
|
||||
Self::Date { root } => write!(f, "{root}"),
|
||||
Self::DateTime { root, root_time } => write!(f, "{root} {root_time}"),
|
||||
Self::DateToDate { root, other } => write!(f, "{root} -- {other}"),
|
||||
Self::DateTimeToTime {
|
||||
root,
|
||||
root_time,
|
||||
other_time,
|
||||
} => write!(f, "{root} {root_time} -- {other_time}"),
|
||||
DoneDate::DateTimeToDateTime {
|
||||
Self::DateTimeToDateTime {
|
||||
root,
|
||||
root_time,
|
||||
other,
|
||||
|
|
@ -308,18 +308,18 @@ impl fmt::Display for Log {
|
|||
impl fmt::Display for Command {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Command::Include(name) => writeln!(f, "INCLUDE {name}"),
|
||||
Command::Timezone(name) => writeln!(f, "TIMEZONE {name}"),
|
||||
Command::Capture => writeln!(f, "CAPTURE"),
|
||||
Command::Task(task) => write!(f, "{task}"),
|
||||
Command::Note(note) => write!(f, "{note}"),
|
||||
Command::Log(log) => write!(f, "{log}"),
|
||||
Self::Include(name) => writeln!(f, "INCLUDE {name}"),
|
||||
Self::Timezone(name) => writeln!(f, "TIMEZONE {name}"),
|
||||
Self::Capture => writeln!(f, "CAPTURE"),
|
||||
Self::Task(task) => write!(f, "{task}"),
|
||||
Self::Note(note) => write!(f, "{note}"),
|
||||
Self::Log(log) => write!(f, "{log}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
fn sort(commands: &mut Vec<&Command>) {
|
||||
fn sort(commands: &mut [&Command]) {
|
||||
// Order of commands in a file:
|
||||
// 1. Imports, sorted alphabetically
|
||||
// 2. Time zone(s)
|
||||
|
|
|
|||
|
|
@ -59,34 +59,33 @@ variable = {
|
|||
| "e"
|
||||
}
|
||||
|
||||
unop_neg = { "-" }
|
||||
unop_not = { "!" }
|
||||
unop = _{ unop_neg | unop_not }
|
||||
prefix_neg = { "-" }
|
||||
prefix_not = { "!" }
|
||||
prefix = _{ prefix_neg | prefix_not }
|
||||
|
||||
op_add = { "+" }
|
||||
op_sub = { "-" }
|
||||
op_mul = { "*" }
|
||||
op_div = { "/" }
|
||||
op_mod = { "%" }
|
||||
op_eq = { "=" }
|
||||
op_neq = { "!=" }
|
||||
op_lt = { "<" }
|
||||
op_lte = { "<=" }
|
||||
op_gt = { ">" }
|
||||
op_gte = { ">=" }
|
||||
op_and = { "&" }
|
||||
op_or = { "|" }
|
||||
op_xor = { "^" }
|
||||
op = _{
|
||||
op_add | op_sub | op_mul | op_div | op_mod
|
||||
| op_eq | op_neq | op_lt | op_lte | op_gt | op_gte
|
||||
| op_and | op_or | op_xor
|
||||
infix_add = { "+" }
|
||||
infix_sub = { "-" }
|
||||
infix_mul = { "*" }
|
||||
infix_div = { "/" }
|
||||
infix_mod = { "%" }
|
||||
infix_eq = { "=" }
|
||||
infix_neq = { "!=" }
|
||||
infix_lt = { "<" }
|
||||
infix_lte = { "<=" }
|
||||
infix_gt = { ">" }
|
||||
infix_gte = { ">=" }
|
||||
infix_and = { "&" }
|
||||
infix_or = { "|" }
|
||||
infix_xor = { "^" }
|
||||
infix = _{
|
||||
infix_add | infix_sub | infix_mul | infix_div | infix_mod
|
||||
| infix_eq | infix_neq | infix_lt | infix_lte | infix_gt | infix_gte
|
||||
| infix_and | infix_or | infix_xor
|
||||
}
|
||||
|
||||
paren_expr = { "(" ~ expr ~ ")" }
|
||||
unop_expr = { unop ~ expr }
|
||||
term = { number | boolean | variable | paren_expr | unop_expr }
|
||||
expr = { term ~ (op ~ term)* }
|
||||
term = { number | boolean | variable | paren_expr }
|
||||
expr = { prefix* ~ term ~ (infix ~ prefix* ~ term)* }
|
||||
|
||||
date_fixed_start = { datum ~ delta? ~ time? }
|
||||
date_fixed_end = { datum ~ delta? ~ time? | delta ~ time? | time }
|
||||
|
|
@ -152,8 +151,13 @@ file = ${ SOI ~ (empty_line* ~ command)* ~ empty_line* ~ WHITESPACE* ~ EOI }
|
|||
today = { "today" | "t" }
|
||||
cli_datum = { datum | today }
|
||||
cli_date = { cli_datum ~ delta? }
|
||||
cli_ident = { SOI ~ (cli_date | number) ~ EOI }
|
||||
cli_ident = { cli_date | number }
|
||||
cli_range_start = { cli_datum ~ delta? }
|
||||
cli_range_end = { cli_datum ~ delta? | delta }
|
||||
cli_range = { SOI ~ cli_range_start ~ ("--" ~ cli_range_end)? ~ EOI }
|
||||
cli_range = { cli_range_start ~ ("--" ~ cli_range_end)? }
|
||||
|
||||
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 }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::result;
|
|||
use chrono::NaiveDate;
|
||||
use pest::error::ErrorVariant;
|
||||
use pest::iterators::Pair;
|
||||
use pest::prec_climber::{Assoc, Operator, PrecClimber};
|
||||
use pest::pratt_parser::{Assoc, Op, PrattParser};
|
||||
use pest::{Parser, Span};
|
||||
|
||||
use super::commands::{
|
||||
|
|
@ -18,7 +18,7 @@ use super::primitives::{Spanned, Time, Weekday};
|
|||
pub struct TodayfileParser;
|
||||
|
||||
pub type Error = pest::error::Error<Rule>;
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
pub type Result<T> = result::Result<T, Box<Error>>;
|
||||
|
||||
fn error<S: Into<String>>(span: Span<'_>, message: S) -> Error {
|
||||
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> {
|
||||
Err(error(span, message))
|
||||
Err(Box::new(error(span, message)))
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let mut spec = DateSpec {
|
||||
start: NaiveDate::from_ymd(0, 1, 1),
|
||||
start: NaiveDate::from_ymd_opt(0, 1, 1).unwrap(),
|
||||
start_delta: None,
|
||||
start_time: None,
|
||||
end: None,
|
||||
|
|
@ -359,24 +359,6 @@ 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> {
|
||||
assert_eq!(p.as_rule(), Rule::paren_expr);
|
||||
let span = (&p.as_span()).into();
|
||||
|
|
@ -392,34 +374,43 @@ fn parse_term(p: Pair<'_, Rule>) -> Spanned<Expr> {
|
|||
Rule::number => Spanned::new(span, Expr::Lit(parse_number(p).into())),
|
||||
Rule::boolean => Spanned::new(span, Expr::Var(parse_boolean(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),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_op(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned<Expr> {
|
||||
fn parse_prefix(p: Pair<'_, Rule>, s: 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 expr = match p.as_rule() {
|
||||
// Integer-y operations
|
||||
Rule::op_add => Expr::Add(Box::new(l), Box::new(r)),
|
||||
Rule::op_sub => Expr::Sub(Box::new(l), Box::new(r)),
|
||||
Rule::op_mul => Expr::Mul(Box::new(l), Box::new(r)),
|
||||
Rule::op_div => Expr::Div(Box::new(l), Box::new(r)),
|
||||
Rule::op_mod => Expr::Mod(Box::new(l), Box::new(r)),
|
||||
Rule::infix_add => Expr::Add(Box::new(l), Box::new(r)),
|
||||
Rule::infix_sub => Expr::Sub(Box::new(l), Box::new(r)),
|
||||
Rule::infix_mul => Expr::Mul(Box::new(l), Box::new(r)),
|
||||
Rule::infix_div => Expr::Div(Box::new(l), Box::new(r)),
|
||||
Rule::infix_mod => Expr::Mod(Box::new(l), Box::new(r)),
|
||||
|
||||
// Comparisons
|
||||
Rule::op_eq => Expr::Eq(Box::new(l), Box::new(r)),
|
||||
Rule::op_neq => Expr::Neq(Box::new(l), Box::new(r)),
|
||||
Rule::op_lt => Expr::Lt(Box::new(l), Box::new(r)),
|
||||
Rule::op_lte => Expr::Lte(Box::new(l), Box::new(r)),
|
||||
Rule::op_gt => Expr::Gt(Box::new(l), Box::new(r)),
|
||||
Rule::op_gte => Expr::Gte(Box::new(l), Box::new(r)),
|
||||
Rule::infix_eq => Expr::Eq(Box::new(l), Box::new(r)),
|
||||
Rule::infix_neq => Expr::Neq(Box::new(l), Box::new(r)),
|
||||
Rule::infix_lt => Expr::Lt(Box::new(l), Box::new(r)),
|
||||
Rule::infix_lte => Expr::Lte(Box::new(l), Box::new(r)),
|
||||
Rule::infix_gt => Expr::Gt(Box::new(l), Box::new(r)),
|
||||
Rule::infix_gte => Expr::Gte(Box::new(l), Box::new(r)),
|
||||
|
||||
// Boolean-y operations
|
||||
Rule::op_and => Expr::And(Box::new(l), Box::new(r)),
|
||||
Rule::op_or => Expr::Or(Box::new(l), Box::new(r)),
|
||||
Rule::op_xor => Expr::Xor(Box::new(l), Box::new(r)),
|
||||
Rule::infix_and => Expr::And(Box::new(l), Box::new(r)),
|
||||
Rule::infix_or => Expr::Or(Box::new(l), Box::new(r)),
|
||||
Rule::infix_xor => Expr::Xor(Box::new(l), Box::new(r)),
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
|
@ -429,21 +420,23 @@ fn parse_op(l: Spanned<Expr>, p: Pair<'_, Rule>, r: Spanned<Expr>) -> Spanned<Ex
|
|||
fn parse_expr(p: Pair<'_, Rule>) -> Spanned<Expr> {
|
||||
assert_eq!(p.as_rule(), Rule::expr);
|
||||
|
||||
fn op(rule: Rule) -> Operator<Rule> {
|
||||
Operator::new(rule, Assoc::Left)
|
||||
}
|
||||
|
||||
let climber = PrecClimber::new(vec![
|
||||
// Precedence from low to high
|
||||
op(Rule::op_or) | op(Rule::op_xor),
|
||||
op(Rule::op_and),
|
||||
op(Rule::op_eq) | op(Rule::op_neq),
|
||||
op(Rule::op_lt) | op(Rule::op_lte) | op(Rule::op_gt) | op(Rule::op_gte),
|
||||
op(Rule::op_mul) | op(Rule::op_div) | op(Rule::op_mod),
|
||||
op(Rule::op_add) | op(Rule::op_sub),
|
||||
]);
|
||||
|
||||
climber.climb(p.into_inner(), parse_term, parse_op)
|
||||
PrattParser::new()
|
||||
.op(Op::infix(Rule::infix_or, Assoc::Left) | Op::infix(Rule::infix_xor, 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)
|
||||
| Op::infix(Rule::infix_lte, Assoc::Left)
|
||||
| Op::infix(Rule::infix_gt, Assoc::Left)
|
||||
| Op::infix(Rule::infix_gte, Assoc::Left))
|
||||
.op(Op::infix(Rule::infix_mul, Assoc::Left)
|
||||
| Op::infix(Rule::infix_div, Assoc::Left)
|
||||
| Op::infix(Rule::infix_mod, Assoc::Left))
|
||||
.op(Op::infix(Rule::infix_add, Assoc::Left) | Op::infix(Rule::infix_sub, Assoc::Left))
|
||||
.op(Op::prefix(Rule::prefix_neg) | Op::prefix(Rule::prefix_not))
|
||||
.map_primary(parse_term)
|
||||
.map_prefix(parse_prefix)
|
||||
.map_infix(parse_infix)
|
||||
.parse(p.into_inner())
|
||||
}
|
||||
|
||||
fn parse_date_expr_start(p: Pair<'_, Rule>, spec: &mut FormulaSpec) -> Result<()> {
|
||||
|
|
@ -858,5 +851,5 @@ pub fn parse(path: &Path, input: &str) -> Result<File> {
|
|||
let file_pair = pairs.next().unwrap();
|
||||
assert_eq!(pairs.next(), None);
|
||||
|
||||
parse_file(file_pair).map_err(|e| e.with_path(&pathstr))
|
||||
parse_file(file_pair).map_err(|e| Box::new(e.with_path(&pathstr)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,13 +206,13 @@ impl Weekday {
|
|||
/// `Saturday`, `Sunday`).
|
||||
pub fn full_name(self) -> &'static str {
|
||||
match self {
|
||||
Weekday::Monday => "Monday",
|
||||
Weekday::Tuesday => "Tuesday",
|
||||
Weekday::Wednesday => "Wednesday",
|
||||
Weekday::Thursday => "Thursday",
|
||||
Weekday::Friday => "Friday",
|
||||
Weekday::Saturday => "Saturday",
|
||||
Weekday::Sunday => "Sunday",
|
||||
Self::Monday => "Monday",
|
||||
Self::Tuesday => "Tuesday",
|
||||
Self::Wednesday => "Wednesday",
|
||||
Self::Thursday => "Thursday",
|
||||
Self::Friday => "Friday",
|
||||
Self::Saturday => "Saturday",
|
||||
Self::Sunday => "Sunday",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
#![warn(future_incompatible)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![warn(clippy::all)]
|
||||
#![warn(clippy::use_self)]
|
||||
|
||||
mod cli;
|
||||
mod error;
|
||||
mod eval;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue