Improve error handling

This commit is contained in:
Joscha 2021-12-18 18:27:48 +01:00
parent 96f7aa77dc
commit d67bf0aeea
6 changed files with 197 additions and 34 deletions

View file

@ -1,14 +1,15 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::{process, result}; use std::{process, result};
use chrono::{Duration, NaiveDate}; use chrono::{Duration, NaiveDate, NaiveDateTime};
use directories::ProjectDirs; use directories::ProjectDirs;
use structopt::StructOpt; use structopt::StructOpt;
use crate::eval::{DateRange, EntryMode}; use crate::eval::{DateRange, Entry, EntryMode};
use crate::files::{self, Files}; use crate::files::{self, Files};
use self::error::Result; use self::error::Result;
use self::layout::line::LineLayout;
mod done; mod done;
mod error; mod error;
@ -67,7 +68,63 @@ fn load_files(opt: &Opt) -> result::Result<Files, files::Error> {
Files::load(&file) Files::load(&file)
} }
pub fn run() -> Result<()> { fn find_now(opt: &Opt, files: &Files) -> NaiveDateTime {
let now = files.now().naive_local();
if let Some(date) = opt.date {
date.and_time(now.time())
} else {
now
}
}
fn find_range(opt: &Opt, now: NaiveDateTime) -> DateRange {
let range_date = opt.date.unwrap_or_else(|| now.date());
DateRange::new(
range_date - Duration::days(opt.before.into()),
range_date + Duration::days(opt.after.into()),
)
.expect("determine range")
}
fn find_entries(files: &Files, range: DateRange) -> Result<Vec<Entry>> {
Ok(files.eval(EntryMode::Relevant, range)?)
}
fn find_layout(
files: &Files,
entries: &[Entry],
range: DateRange,
now: NaiveDateTime,
) -> LineLayout {
layout::layout(files, entries, range, now)
}
fn run_command(opt: &Opt, files: &mut Files, range: DateRange, now: NaiveDateTime) -> Result<()> {
match &opt.command {
None => {
let entries = find_entries(files, range)?;
let layout = find_layout(files, &entries, range, now);
print::print(&layout);
}
Some(Command::Show { entries: ns }) => {
let entries = find_entries(files, range)?;
let layout = find_layout(files, &entries, range, now);
show::show(files, &entries, &layout, ns)?;
}
Some(Command::Done { entries: ns }) => {
let entries = find_entries(files, range)?;
let layout = find_layout(files, &entries, range, now);
done::done(files, &entries, &layout, ns, now)?;
let entries = find_entries(files, range)?;
let layout = find_layout(files, &entries, range, now);
print::print(&layout);
}
Some(Command::Fmt) => files.mark_all_dirty(),
}
Ok(())
}
pub fn run() {
let opt = Opt::from_args(); let opt = Opt::from_args();
let mut files = match load_files(&opt) { let mut files = match load_files(&opt) {
@ -78,33 +135,16 @@ pub fn run() -> Result<()> {
} }
}; };
let now = files.now().naive_local(); let now = find_now(&opt, &files);
let range = find_range(&opt, now);
let range_date = opt.date.unwrap_or_else(|| now.date()); if let Err(e) = run_command(&opt, &mut files, range, now) {
let range = DateRange::new( e.print(&files);
range_date - Duration::days(opt.before.into()), process::exit(1);
range_date + Duration::days(opt.after.into()),
)
.expect("determine range");
let entries = files.eval(EntryMode::Relevant, range)?;
let layout = layout::layout(&files, &entries, range, now);
match opt.command {
None => print::print(&layout),
Some(Command::Show { entries: numbers }) => {
show::show(&files, &entries, &layout, &numbers)?
}
Some(Command::Done { entries: numbers }) => {
done::done(&mut files, &entries, &layout, &numbers, now)?
}
Some(Command::Fmt) => files.mark_all_dirty(),
} }
if let Err(e) = files.save() { if let Err(e) = files.save() {
e.print(); e.print();
process::exit(1); process::exit(1);
} }
Ok(())
} }

View file

@ -1,19 +1,35 @@
use std::result; use std::result;
use crate::{eval, files}; use crate::eval;
use crate::files::Files;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("{0}")]
Files(#[from] files::Error),
#[error("{0}")] #[error("{0}")]
Eval(#[from] eval::Error), Eval(#[from] eval::Error),
#[error("No number specified")]
NoNumber,
#[error("No entry with number {0}")] #[error("No entry with number {0}")]
NoSuchEntry(usize), NoSuchEntry(usize),
#[error("Not a task")] #[error("Not a task")]
NotATask(Vec<usize>), NotATask(Vec<usize>),
} }
impl Error {
pub fn print(&self, files: &Files) {
match self {
Error::Eval(e) => e.print(files),
Error::NoSuchEntry(n) => eprintln!("No entry with number {}", n),
Error::NotATask(ns) => {
if ns.is_empty() {
eprintln!("Not a task.");
} else if ns.len() == 1 {
eprintln!("{} is not a task.", ns[0]);
} else {
let ns = ns.iter().map(|n| n.to_string()).collect::<Vec<_>>();
eprintln!("{} are not tasks.", ns.join(", "));
}
}
}
}
}
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;

View file

@ -3,6 +3,7 @@ use std::result;
use chrono::NaiveDate; use chrono::NaiveDate;
use crate::files::primitives::{Span, Time}; use crate::files::primitives::{Span, Time};
use crate::files::Files;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -62,4 +63,105 @@ pub enum Error {
}, },
} }
impl Error {
fn print_at(files: &Files, file: &usize, span: &Span, message: String) {
use pest::error as pe;
let (name, content) = files.file(*file).expect("file index is valid");
let span = pest::Span::new(content, span.start, span.end).expect("span is valid");
let variant = pe::ErrorVariant::<()>::CustomError { message };
let error = pe::Error::new_from_span(variant, span).with_path(&name.to_string_lossy());
eprintln!("{}", error);
}
fn fmt_date_time(date: NaiveDate, time: Option<Time>) -> String {
match time {
None => format!("{}", date),
Some(time) => format!("{} {}", date, time),
}
}
pub fn print(&self, files: &Files) {
match self {
Error::DeltaInvalidStep {
file,
span,
start,
start_time,
prev,
prev_time,
} => {
let msg = format!(
"Delta step resulted in invalid date\
\nInitial start: {}\
\nPrevious step: {}",
Self::fmt_date_time(*start, *start_time),
Self::fmt_date_time(*prev, *prev_time),
);
Self::print_at(files, file, span, msg);
}
Error::DeltaNoTime {
file,
span,
start,
prev,
} => {
let msg = format!(
"Time-based delta step applied to date without time\
\nInitial start: {}\
\nPrevious step: {}",
start, prev
);
Self::print_at(files, file, span, msg);
}
Error::RepeatDidNotMoveForwards {
file,
span,
from,
to,
} => {
let msg = format!(
"Repeat delta did not move forwards\
\nMoved from {} to {}",
from, to
);
Self::print_at(files, file, span, msg);
}
Error::MoveWithoutSource { file, span } => {
let msg = "Tried to move nonexisting entry".to_string();
Self::print_at(files, file, span, msg);
}
Error::DivByZero { file, span, date } => {
let msg = format!(
"Tried to divide by zero\
\nAt date: {}",
date
);
Self::print_at(files, file, span, msg);
}
Error::ModByZero { file, span, date } => {
let msg = format!(
"Tried to modulo by zero\
\nAt date: {}",
date
);
Self::print_at(files, file, span, msg);
}
Error::Easter {
file,
span,
date,
msg,
} => {
let msg = format!(
"Failed to calculate easter\
\nAt date: {}\
\nReason: {}",
date, msg
);
Self::print_at(files, file, span, msg);
}
}
}
}
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;

View file

@ -163,6 +163,12 @@ impl Files {
&self.files[source.file].file.commands[source.command] &self.files[source.file].file.commands[source.command]
} }
pub fn file(&self, file: usize) -> Option<(&Path, &str)> {
self.files
.get(file)
.map(|f| (&f.name as &Path, &f.file.contents as &str))
}
/// Add a [`Done`] statement to the task identified by `source`. /// Add a [`Done`] statement to the task identified by `source`.
/// ///
/// Returns whether the addition was successful. It can fail if the entry /// Returns whether the addition was successful. It can fail if the entry

View file

@ -27,7 +27,7 @@ pub enum Error {
} }
impl Error { impl Error {
pub fn print(self) { pub fn print(&self) {
match self { match self {
Error::ResolvePath { path, error } => { Error::ResolvePath { path, error } => {
eprintln!("Could not resolve path {:?}:", path); eprintln!("Could not resolve path {:?}:", path);

View file

@ -7,7 +7,6 @@ mod cli;
mod eval; mod eval;
mod files; mod files;
fn main() -> anyhow::Result<()> { fn main() {
cli::run()?; cli::run();
Ok(())
} }