diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 124 |
1 files changed, 25 insertions, 99 deletions
diff --git a/src/main.rs b/src/main.rs index bea03f6..8edd75a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,25 +5,22 @@ use std::io::{BufReader, Read, Seek}; use std::path::PathBuf; use std::str::FromStr; -use anyhow::{anyhow, Result}; -use chrono::{Duration, NaiveDateTime, Weekday}; +use anyhow::Result; +use chrono::{NaiveDateTime, Weekday}; use log::debug; use num_traits::cast::FromPrimitive; -use regex::Regex; use structopt::StructOpt; use walkdir::{DirEntry, WalkDir}; use evtclib::{AgentKind, AgentName, EventKind, Log}; +mod fexpr; mod filters; use filters::{log::LogFilter, Inclusion}; mod guilds; mod logger; mod output; -mod csl; -use csl::CommaSeparatedList; - macro_rules! unwrap { ($p:pat = $e:expr => { $r:expr} ) => { if let $p = $e { @@ -43,18 +40,6 @@ pub struct Opt { #[structopt(short = "d", long = "dir", default_value = ".", parse(from_os_str))] path: PathBuf, - /// The fields which should be searched. - #[structopt(short = "f", long = "fields", default_value = "account,character")] - field: CommaSeparatedList<SearchField>, - - /// Only display fights with the given outcome. - #[structopt(short = "o", long = "outcome", default_value = "*")] - outcome: CommaSeparatedList<FightOutcome>, - - /// Invert the regular expression (show fights that do not match) - #[structopt(short = "v", long = "invert-match")] - invert: bool, - /// Only show the name of matching files. #[structopt(short = "l", long = "files-with-matches")] file_name_only: bool, @@ -63,35 +48,6 @@ pub struct Opt { #[structopt(long = "no-color")] no_color: bool, - /// Only show logs that are younger than the given time. - #[structopt( - short = "a", - long = "younger", - parse(try_from_str = parse_time_arg) - )] - after: Option<NaiveDateTime>, - - /// Only show logs that are older than the given time. - #[structopt( - short = "b", - long = "older", - parse(try_from_str = parse_time_arg) - )] - before: Option<NaiveDateTime>, - - /// Only show logs from the given weekdays. - #[structopt( - short = "w", - long = "weekdays", - default_value = "*", - parse(try_from_str = try_from_str_simple_error) - )] - weekdays: CommaSeparatedList<Weekday>, - - /// Only show logs from the given encounters. - #[structopt(short = "e", long = "bosses", default_value = "*")] - bosses: CommaSeparatedList<evtclib::statistics::gamedata::Boss>, - /// Print more debugging information to stderr. #[structopt(long = "debug")] debug: bool, @@ -100,9 +56,8 @@ pub struct Opt { #[structopt(long = "guilds")] guilds: bool, - /// The regular expression to search for. - #[structopt(name = "EXPR")] - expression: Regex, + /// The filter expression. + expression: Vec<String>, } /// A flag indicating which fields should be searched. @@ -180,26 +135,6 @@ impl FromStr for FightOutcome { } } -fn parse_time_arg(input: &str) -> Result<NaiveDateTime> { - if let Ok(duration) = humantime::parse_duration(input) { - let now = chrono::Local::now().naive_local(); - let chrono_dur = Duration::from_std(duration).expect("Duration out of range!"); - return Ok(now - chrono_dur); - } - if let Ok(time) = humantime::parse_rfc3339_weak(input) { - let timestamp = time - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - return Ok(NaiveDateTime::from_timestamp(timestamp as i64, 0)); - } - Err(anyhow!("unknown time format")) -} - -fn try_from_str_simple_error<T: FromStr>(input: &str) -> Result<T, String> { - T::from_str(input).map_err(|_| format!("'{}' is an invalid value", input)) -} - enum ZipWrapper<R: Read + Seek> { Raw(Option<R>), Zipped(zip::ZipArchive<R>), @@ -223,6 +158,13 @@ impl<R: Read + Seek> ZipWrapper<R> { } fn main() { + let result = run(); + if let Err(err) = result { + eprintln!("Error: {}", err); + } +} + +fn run() -> Result<()> { let opt = Opt::from_args(); if opt.no_color { @@ -239,17 +181,15 @@ fn main() { guilds::prepare_cache(); } - let result = grep(&opt); - match result { - Ok(_) => {} - Err(e) => { - eprintln!("Error: {}", e); - } - } + let filter = build_filter(&opt)?; + + grep(&opt, &*filter)?; if opt.guilds { guilds::save_cache(); } + + Ok(()) } /// Check if the given entry represents a log file, based on the file name. @@ -261,32 +201,18 @@ fn is_log_file(entry: &DirEntry) -> bool { .unwrap_or(false) } -fn build_filter(opt: &Opt) -> Box<dyn LogFilter> { - let player_filter = opt - .field - .values() - .iter() - .map(|field| filters::player::NameFilter::new(*field, opt.expression.clone())) - .fold(filters::Const::new(false), |a, f| a | f); - - let mut filter = filters::player::any(player_filter); - if opt.invert { - filter = !filter; - } - - filter = filter - & filters::log::BossFilter::new(opt.bosses.values().clone()) - & filters::log::OutcomeFilter::new(opt.outcome.values().clone()) - & filters::log::WeekdayFilter::new(opt.weekdays.values().clone()) - & filters::log::TimeFilter::new(opt.after, opt.before); - - filter +fn build_filter(opt: &Opt) -> Result<Box<dyn LogFilter>> { + // Our error needs access to the string, so we make our lives easier by just leaking it into a + // 'static lifetime. Otherwise we'd need to build this string in main() and pass it in. + // 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)?) } /// Run the grep search with the given options. -fn grep(opt: &Opt) -> Result<()> { +fn grep(opt: &Opt, filter: &dyn LogFilter) -> Result<()> { let pipeline = &output::build_pipeline(opt); - let filter: &dyn LogFilter = &*build_filter(opt); rayon::scope(|s| { let walker = WalkDir::new(&opt.path); for entry in walker { |