Improve error handling
This commit is contained in:
parent
96f7aa77dc
commit
d67bf0aeea
6 changed files with 197 additions and 34 deletions
90
src/cli.rs
90
src/cli.rs
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue