aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/fexpr/grammar.lalrpop62
-rw-r--r--src/fexpr/mod.rs57
-rw-r--r--src/filters/mod.rs2
-rw-r--r--src/main.rs45
4 files changed, 135 insertions, 31 deletions
diff --git a/src/fexpr/grammar.lalrpop b/src/fexpr/grammar.lalrpop
index 48349a1..4e6ac89 100644
--- a/src/fexpr/grammar.lalrpop
+++ b/src/fexpr/grammar.lalrpop
@@ -1,5 +1,6 @@
use super::{
FError,
+ FErrorKind,
FightOutcome,
filters,
SearchField,
@@ -76,40 +77,68 @@ PlayerPredicate: Box<dyn filters::player::PlayerFilter> = {
}
Regex: Regex = {
- <s:r#""[^"]*""#> =>? Regex::new(&s[1..s.len() - 1]).map_err(|_| ParseError::User {
- error: FError::InvalidRegex(s.into()),
+ <l:@L> <s:regex> =>? Regex::new(&s[1..s.len() - 1]).map_err(|error| ParseError::User {
+ error: FError {
+ location: l,
+ data: s.to_string(),
+ kind: error.into(),
+ }
}),
- <s:word> =>? Regex::new(s).map_err(|e| ParseError::User {
- error: FError::InvalidRegex(s.into()),
+ <l:@L> <s:word> =>? Regex::new(s).map_err(|error| ParseError::User {
+ error: FError {
+ location: l,
+ data: s.to_string(),
+ kind: error.into(),
+ }
}),
}
FightOutcome: FightOutcome = {
- <word> =>? <>.parse().map_err(|_| ParseError::User {
- error: FError::InvalidFightOutcome(<>.into()),
+ <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User {
+ error: FError {
+ location: l,
+ data: w.into(),
+ kind: FErrorKind::InvalidFightOutcome,
+ }
}),
}
Weekday: Weekday = {
- <word> =>? <>.parse().map_err(|_| ParseError::User {
- error: FError::InvalidWeekday(<>.into()),
+ <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User {
+ error: FError {
+ location: l,
+ data: w.into(),
+ kind: FErrorKind::InvalidWeekday,
+ }
}),
}
Boss: Boss = {
- <word> =>? <>.parse().map_err(|_| ParseError::User {
- error: FError::InvalidBoss(<>.into()),
+ <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User {
+ error: FError {
+ location: l,
+ data: w.into(),
+ kind: FErrorKind::InvalidBoss,
+ }
}),
}
Date: NaiveDateTime = {
- <datetime> =>? NaiveDateTime::parse_from_str(<>, "%Y-%m-%d %H:%M:%S")
- .map_err(|_| ParseError::User {
- error: FError::InvalidTimestamp(<>.into()),
+ <l:@L> <d:datetime> =>? NaiveDateTime::parse_from_str(d, "%Y-%m-%d %H:%M:%S")
+ .map_err(|error| ParseError::User {
+ error: FError {
+ location: l,
+ data: d.into(),
+ kind: error.into(),
+ }
}),
- <date> =>? NaiveDateTime::parse_from_str(&format!("{} 00:00:00", <>), "%Y-%m-%d %H:%M:%S")
- .map_err(|_| ParseError::User {
- error: FError::InvalidTimestamp(<>.into()),
+ <l:@L> <d:date> =>? NaiveDateTime::parse_from_str(&format!("{} 00:00:00", d), "%Y-%m-%d %H:%M:%S")
+ .map_err(|error| ParseError::User {
+ error: FError {
+ location: l,
+ data: d.into(),
+ kind: error.into(),
+ }
}),
}
@@ -131,6 +160,7 @@ match {
r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d" => datetime,
r"\d\d\d\d-\d\d-\d\d" => date,
r"\w+" => word,
+ r#""[^"]*""# => regex,
_
}
diff --git a/src/fexpr/mod.rs b/src/fexpr/mod.rs
index aafdea7..5754d94 100644
--- a/src/fexpr/mod.rs
+++ b/src/fexpr/mod.rs
@@ -4,28 +4,63 @@
//! type and convert it to a [`Filter`][super::filters::Filter].
// Make it available in the grammar mod.
use super::{filters, FightOutcome, SearchField, Weekday};
-use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
+use std::{error, fmt};
+
+use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
use thiserror::Error;
-lalrpop_mod!(pub grammar, "/fexpr/grammar.rs");
+lalrpop_mod!(#[allow(clippy::all)] pub grammar, "/fexpr/grammar.rs");
+
+#[derive(Debug)]
+pub struct FError {
+ location: usize,
+ data: String,
+ kind: FErrorKind,
+}
+
+impl fmt::Display for FError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} (at {})", self.kind, self.location)
+ }
+}
+
+impl error::Error for FError {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ Some(&self.kind)
+ }
+}
#[derive(Debug, Error)]
-pub enum FError {
+pub enum FErrorKind {
#[error("invalid regular expression: {0}")]
- InvalidRegex(String),
- #[error("invalid fight outcome: {0}")]
- InvalidFightOutcome(String),
- #[error("invalid weekday: {0}")]
- InvalidWeekday(String),
+ InvalidRegex(#[from] regex::Error),
+ #[error("invalid fight outcome")]
+ InvalidFightOutcome,
+ #[error("invalid weekday")]
+ InvalidWeekday,
#[error("invalid timestamp: {0}")]
- InvalidTimestamp(String),
- #[error("invalid boss name: {0}")]
- InvalidBoss(String),
+ InvalidTimestamp(#[from] chrono::format::ParseError),
+ #[error("invalid boss name")]
+ InvalidBoss,
}
+/// Shortcut to create a new parser and parse the given input.
pub fn parse_logfilter(
input: &str,
) -> Result<Box<dyn filters::log::LogFilter>, ParseError<usize, Token, FError>> {
grammar::LogFilterParser::new().parse(input)
}
+
+/// Extract the location from the given error.
+pub fn location<T>(err: &ParseError<usize, T, FError>) -> usize {
+ match *err {
+ ParseError::InvalidToken { location } => location,
+ ParseError::UnrecognizedEOF { location, .. } => location,
+ ParseError::UnrecognizedToken {
+ token: (l, _, _), ..
+ } => l,
+ ParseError::ExtraToken { token: (l, _, _) } => l,
+ ParseError::User { ref error } => error.location,
+ }
+}
diff --git a/src/filters/mod.rs b/src/filters/mod.rs
index 525ff27..3d0868b 100644
--- a/src/filters/mod.rs
+++ b/src/filters/mod.rs
@@ -1,5 +1,5 @@
#![allow(clippy::new_ret_no_self)]
-use std::{ops, fmt};
+use std::{fmt, ops};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive as _;
diff --git a/src/main.rs b/src/main.rs
index 8edd75a..2e0c82f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,14 @@
#![feature(trait_alias)]
use std::collections::HashMap;
+use std::fmt;
use std::fs::File;
use std::io::{BufReader, Read, Seek};
use std::path::PathBuf;
use std::str::FromStr;
-use anyhow::Result;
+use anyhow::{anyhow, Error, Result};
use chrono::{NaiveDateTime, Weekday};
+use colored::Colorize;
use log::debug;
use num_traits::cast::FromPrimitive;
use structopt::StructOpt;
@@ -157,10 +159,38 @@ impl<R: Read + Seek> ZipWrapper<R> {
}
}
+#[derive(Clone, Debug)]
+struct InputError {
+ line: String,
+ location: usize,
+ msg: String,
+}
+
+impl fmt::Display for InputError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let prefix = "Input:";
+ writeln!(f, "{} {}", prefix.yellow(), self.line)?;
+ let prefix_len = prefix.len() + self.location;
+ writeln!(f, "{}{}", " ".repeat(prefix_len), " ^-".red())?;
+ write!(f, "{}: {}", "Error".red(), self.msg)?;
+ Ok(())
+ }
+}
+
+impl std::error::Error for InputError {}
+
fn main() {
let result = run();
if let Err(err) = result {
- eprintln!("Error: {}", err);
+ display_error(&err);
+ }
+}
+
+fn display_error(err: &Error) {
+ if let Some(err) = err.downcast_ref::<InputError>() {
+ eprintln!("{}", err);
+ } else {
+ eprintln!("{}: {}", "Error".red(), err);
}
}
@@ -207,7 +237,16 @@ fn build_filter(opt: &Opt) -> Result<Box<dyn LogFilter>> {
// We're fine with the small memory leak, as we're only dealing with a small string in a
// short-lived program.
let expr_string = Box::leak(Box::new(opt.expression.join(" ")));
- Ok(fexpr::parse_logfilter(expr_string)?)
+ if expr_string.trim().is_empty() {
+ return Err(anyhow!("Expected a filter to be given"));
+ }
+ Ok(
+ fexpr::parse_logfilter(expr_string).map_err(|error| InputError {
+ line: expr_string.to_string(),
+ location: fexpr::location(&error),
+ msg: error.to_string(),
+ })?,
+ )
}
/// Run the grep search with the given options.