Make error formatting more generic
Now, some errors are generic over the FileId type, meaning I can now pretty-print command line argument errors as well as normal errors with the same machinery. This is accomplished via the Eprint trait, whose generics are a tad complex but probably still fine.
This commit is contained in:
parent
8ae691bc3c
commit
5e47dddd4c
6 changed files with 111 additions and 59 deletions
22
src/cli.rs
22
src/cli.rs
|
|
@ -3,9 +3,11 @@ use std::str::FromStr;
|
|||
use std::{process, result};
|
||||
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use codespan_reporting::files::SimpleFile;
|
||||
use directories::ProjectDirs;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::error;
|
||||
// use crate::eval::{DateRange, Entry, EntryMode, SourceInfo};
|
||||
use crate::files::arguments::Range;
|
||||
use crate::files::{self, Files};
|
||||
|
|
@ -72,8 +74,6 @@ fn load_files(opt: &Opt, files: &mut Files) -> result::Result<(), files::Error>
|
|||
files.load(&file)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
fn find_now(opt: &Opt, files: &Files) -> NaiveDateTime {
|
||||
let now = files.now().naive_local();
|
||||
if let Some(date) = opt.date {
|
||||
|
|
@ -83,6 +83,8 @@ fn find_now(opt: &Opt, files: &Files) -> NaiveDateTime {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
fn find_entries(files: &Files, range: DateRange) -> Result<Vec<Entry>> {
|
||||
Ok(files.eval(EntryMode::Relevant, range)?)
|
||||
}
|
||||
|
|
@ -136,24 +138,20 @@ pub fn run() {
|
|||
|
||||
let mut files = Files::new();
|
||||
if let Err(e) = load_files(&opt, &mut files) {
|
||||
e.print(&files);
|
||||
error::eprint_error(&files, &e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
let now = find_now(&opt, &files);
|
||||
|
||||
// Kinda ugly, but it can stay for now (until it grows at least).
|
||||
let range = match Range::from_str(&opt.range) {
|
||||
Ok(range) => match range.eval(0, now.date()) {
|
||||
Ok(range) => match range.eval((), now.date()) {
|
||||
Ok(range) => range,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to evaluate --range:");
|
||||
e.print(&[SourceInfo {
|
||||
name: Some("--range".to_string()),
|
||||
content: &opt.range,
|
||||
}]);
|
||||
let file = SimpleFile::new("--range", &opt.range);
|
||||
error::eprint_error(&file, &e);
|
||||
process::exit(1)
|
||||
}
|
||||
},
|
||||
|
|
@ -163,6 +161,8 @@ pub fn run() {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
if let Err(e) = run_command(&opt, &mut files, range, now) {
|
||||
e.print(&files.sources());
|
||||
process::exit(1);
|
||||
|
|
@ -171,7 +171,7 @@ pub fn run() {
|
|||
*/
|
||||
|
||||
if let Err(e) = files.save() {
|
||||
e.print(&files);
|
||||
error::eprint_error(&files, &e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/error.rs
Normal file
28
src/error.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use codespan_reporting::files::Files;
|
||||
use codespan_reporting::term::{self, Config};
|
||||
use termcolor::StandardStream;
|
||||
|
||||
pub trait Eprint<'a, F: Files<'a>> {
|
||||
fn eprint_diagnostic<'f: 'a>(
|
||||
files: &'f F,
|
||||
config: &Config,
|
||||
diagnostic: &Diagnostic<F::FileId>,
|
||||
) {
|
||||
let mut out = StandardStream::stderr(termcolor::ColorChoice::Auto);
|
||||
if let Err(e) = term::emit(&mut out, config, files, diagnostic) {
|
||||
panic!("Error while reporting error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config);
|
||||
}
|
||||
|
||||
pub fn eprint_error<'a, 'f: 'a, F, E>(files: &'f F, e: &E)
|
||||
where
|
||||
F: Files<'a>,
|
||||
E: Eprint<'a, F>,
|
||||
{
|
||||
let config = Config::default();
|
||||
e.eprint(files, &config);
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
use chrono::NaiveDate;
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::files::Files;
|
||||
use codespan_reporting::term::Config;
|
||||
|
||||
use crate::error::Eprint;
|
||||
use crate::files::primitives::{Span, Time};
|
||||
use crate::files::{FileSource, Files};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error<S> {
|
||||
|
|
@ -76,15 +78,17 @@ pub enum Error<S> {
|
|||
},
|
||||
}
|
||||
|
||||
impl Error<FileSource> {
|
||||
impl<S> Error<S> {
|
||||
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) {
|
||||
impl<'a, F: Files<'a>> Eprint<'a, F> for Error<F::FileId> {
|
||||
fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) {
|
||||
let diagnostic = match self {
|
||||
Error::DeltaInvalidStep {
|
||||
index,
|
||||
|
|
@ -99,7 +103,7 @@ impl Error<FileSource> {
|
|||
Diagnostic::error()
|
||||
.with_message("Delta step resulted in invalid date")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("At this step")
|
||||
Label::primary(*index, span).with_message("At this step")
|
||||
])
|
||||
.with_notes(vec![
|
||||
format!("Date before applying delta: {}", start_str),
|
||||
|
|
@ -114,7 +118,7 @@ impl Error<FileSource> {
|
|||
} => Diagnostic::error()
|
||||
.with_message("Time-based delta step applied to date without time")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("At this step")
|
||||
Label::primary(*index, span).with_message("At this step")
|
||||
])
|
||||
.with_notes(vec![
|
||||
format!("Date before applying delta: {}", start),
|
||||
|
|
@ -127,9 +131,7 @@ impl Error<FileSource> {
|
|||
to,
|
||||
} => Diagnostic::error()
|
||||
.with_message("Repeat delta did not move forwards")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("This delta")
|
||||
])
|
||||
.with_labels(vec![Label::primary(*index, span).with_message("This delta")])
|
||||
.with_notes(vec![format!("Moved from {} to {}", from, to)]),
|
||||
Error::RemindDidNotMoveBackwards {
|
||||
index,
|
||||
|
|
@ -138,30 +140,24 @@ impl Error<FileSource> {
|
|||
to,
|
||||
} => Diagnostic::error()
|
||||
.with_message("Remind delta did not move backwards")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("This delta")
|
||||
])
|
||||
.with_labels(vec![Label::primary(*index, span).with_message("This delta")])
|
||||
.with_notes(vec![format!("Moved from {} to {}", from, to)]),
|
||||
Error::MoveWithoutSource { index, span } => Diagnostic::error()
|
||||
.with_message("Tried to move nonexistent entry")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("Here")
|
||||
]),
|
||||
.with_labels(vec![Label::primary(*index, span).with_message("Here")]),
|
||||
Error::TimedMoveWithoutTime { index, span } => Diagnostic::error()
|
||||
.with_message("Tried to move un-timed entry to new time")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("Here")
|
||||
]),
|
||||
.with_labels(vec![Label::primary(*index, span).with_message("Here")]),
|
||||
Error::DivByZero { index, span, date } => Diagnostic::error()
|
||||
.with_message("Tried to divide by zero")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("This expression")
|
||||
Label::primary(*index, span).with_message("This expression")
|
||||
])
|
||||
.with_notes(vec![format!("At date: {}", date)]),
|
||||
Error::ModByZero { index, span, date } => Diagnostic::error()
|
||||
.with_message("Tried to modulo by zero")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("This expression")
|
||||
Label::primary(*index, span).with_message("This expression")
|
||||
])
|
||||
.with_notes(vec![format!("At date: {}", date)]),
|
||||
Error::Easter {
|
||||
|
|
@ -172,13 +168,13 @@ impl Error<FileSource> {
|
|||
} => Diagnostic::error()
|
||||
.with_message("Failed to calculate easter")
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*index), span).with_message("This expression")
|
||||
Label::primary(*index, span).with_message("This expression")
|
||||
])
|
||||
.with_notes(vec![
|
||||
format!("At date: {}", date),
|
||||
format!("Reason: {}", msg),
|
||||
]),
|
||||
};
|
||||
files.eprint_diagnostic(&diagnostic);
|
||||
Self::eprint_diagnostic(files, config, &diagnostic);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
src/files.rs
53
src/files.rs
|
|
@ -1,13 +1,10 @@
|
|||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, result};
|
||||
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use codespan_reporting::files::SimpleFiles;
|
||||
use codespan_reporting::term::{self, Config};
|
||||
use termcolor::StandardStream;
|
||||
use tzfile::Tz;
|
||||
|
||||
use self::commands::{Command, File};
|
||||
|
|
@ -54,7 +51,7 @@ pub struct Source {
|
|||
}
|
||||
|
||||
// TODO Rename to `SourceFile`?
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FileSource(usize);
|
||||
|
||||
impl Source {
|
||||
|
|
@ -82,6 +79,42 @@ pub struct Files {
|
|||
logs: HashMap<NaiveDate, Source>,
|
||||
}
|
||||
|
||||
impl<'a> codespan_reporting::files::Files<'a> for Files {
|
||||
type FileId = FileSource;
|
||||
type Name = String;
|
||||
type Source = &'a str;
|
||||
|
||||
fn name(
|
||||
&'a self,
|
||||
id: Self::FileId,
|
||||
) -> result::Result<Self::Name, codespan_reporting::files::Error> {
|
||||
self.cs_files.name(self.cs_id(id))
|
||||
}
|
||||
|
||||
fn source(
|
||||
&'a self,
|
||||
id: Self::FileId,
|
||||
) -> result::Result<Self::Source, codespan_reporting::files::Error> {
|
||||
self.cs_files.source(self.cs_id(id))
|
||||
}
|
||||
|
||||
fn line_index(
|
||||
&'a self,
|
||||
id: Self::FileId,
|
||||
byte_index: usize,
|
||||
) -> result::Result<usize, codespan_reporting::files::Error> {
|
||||
self.cs_files.line_index(self.cs_id(id), byte_index)
|
||||
}
|
||||
|
||||
fn line_range(
|
||||
&'a self,
|
||||
id: Self::FileId,
|
||||
line_index: usize,
|
||||
) -> result::Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
|
||||
self.cs_files.line_range(self.cs_id(id), line_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Files {
|
||||
/* Loading */
|
||||
|
||||
|
|
@ -323,15 +356,7 @@ impl Files {
|
|||
|
||||
/* Errors */
|
||||
|
||||
pub fn cs_id(&self, file: FileSource) -> usize {
|
||||
fn cs_id(&self, file: FileSource) -> usize {
|
||||
self.files[file.0].cs_id
|
||||
}
|
||||
|
||||
pub fn eprint_diagnostic(&self, diagnostic: &Diagnostic<usize>) {
|
||||
let mut out = StandardStream::stderr(termcolor::ColorChoice::Auto);
|
||||
let config = Config::default();
|
||||
if let Err(e) = term::emit(&mut out, &config, &self.cs_files, diagnostic) {
|
||||
panic!("Error while reporting error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ use std::{io, result};
|
|||
|
||||
use chrono::NaiveDate;
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::term::Config;
|
||||
|
||||
use crate::error::Eprint;
|
||||
|
||||
use super::primitives::Span;
|
||||
use super::{parse, FileSource, Files};
|
||||
|
|
@ -45,8 +48,8 @@ pub enum Error {
|
|||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn print(&self, files: &Files) {
|
||||
impl<'a> Eprint<'a, Files> for Error {
|
||||
fn eprint<'f: 'a>(&self, files: &'f Files, config: &Config) {
|
||||
match self {
|
||||
Error::ResolvePath { path, error } => {
|
||||
eprintln!("Could not resolve path {:?}:", path);
|
||||
|
|
@ -68,10 +71,11 @@ impl Error {
|
|||
} => {
|
||||
let diagnostic = Diagnostic::error()
|
||||
.with_message(format!("Could not resolve time zone {}", tz))
|
||||
.with_labels(vec![Label::primary(files.cs_id(*file), span)
|
||||
.with_message("Time zone defined here")])
|
||||
.with_labels(vec![
|
||||
Label::primary(*file, span).with_message("Time zone defined here")
|
||||
])
|
||||
.with_notes(vec![format!("{}", error)]);
|
||||
files.eprint_diagnostic(&diagnostic);
|
||||
Self::eprint_diagnostic(files, config, &diagnostic);
|
||||
}
|
||||
Error::LocalTz { error } => {
|
||||
eprintln!("Could not determine local timezone:");
|
||||
|
|
@ -90,15 +94,13 @@ impl Error {
|
|||
let diagnostic = Diagnostic::error()
|
||||
.with_message(format!("Time zone conflict between {} and {}", tz1, tz2))
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*file1), span1)
|
||||
.with_message("Time zone defined here"),
|
||||
Label::primary(files.cs_id(*file2), span2)
|
||||
.with_message("Time zone defined here"),
|
||||
Label::primary(*file1, span1).with_message("Time zone defined here"),
|
||||
Label::primary(*file2, span2).with_message("Time zone defined here"),
|
||||
])
|
||||
.with_notes(vec![
|
||||
"All TIMEZONE commands must set the same time zone.".to_string()
|
||||
]);
|
||||
files.eprint_diagnostic(&diagnostic);
|
||||
Self::eprint_diagnostic(files, config, &diagnostic);
|
||||
}
|
||||
Error::LogConflict {
|
||||
file1,
|
||||
|
|
@ -110,11 +112,11 @@ impl Error {
|
|||
let diagnostic = Diagnostic::error()
|
||||
.with_message(format!("Duplicate log entries for {}", date))
|
||||
.with_labels(vec![
|
||||
Label::primary(files.cs_id(*file1), span1).with_message("Log defined here"),
|
||||
Label::primary(files.cs_id(*file2), span2).with_message("Log defined here"),
|
||||
Label::primary(*file1, span1).with_message("Log defined here"),
|
||||
Label::primary(*file2, span2).with_message("Log defined here"),
|
||||
])
|
||||
.with_notes(vec!["A day can have at most one LOG entry.".to_string()]);
|
||||
files.eprint_diagnostic(&diagnostic);
|
||||
Self::eprint_diagnostic(files, config, &diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#![warn(clippy::use_self)]
|
||||
|
||||
mod cli;
|
||||
mod error;
|
||||
mod eval;
|
||||
mod files;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue