From 0e4e148a0890ba206df40cffe5a5f1cc47c8079e Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 20 Apr 2020 14:27:42 +0200 Subject: hook up new expression parser to command line args This method is not perfect yet, because 1. The items are not documented as they were before 2. You need to separate the args with --, otherwise Clap tries to parse them as optional flags This should be fixed (especially the documentation part) before merging into master. --- src/main.rs | 124 ++++++++++++------------------------------------------------ 1 file changed, 25 insertions(+), 99 deletions(-) (limited to 'src/main.rs') 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, - - /// Only display fights with the given outcome. - #[structopt(short = "o", long = "outcome", default_value = "*")] - outcome: CommaSeparatedList, - - /// 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, - - /// Only show logs that are older than the given time. - #[structopt( - short = "b", - long = "older", - parse(try_from_str = parse_time_arg) - )] - before: Option, - - /// 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, - - /// Only show logs from the given encounters. - #[structopt(short = "e", long = "bosses", default_value = "*")] - bosses: CommaSeparatedList, - /// 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, } /// A flag indicating which fields should be searched. @@ -180,26 +135,6 @@ impl FromStr for FightOutcome { } } -fn parse_time_arg(input: &str) -> Result { - 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(input: &str) -> Result { - T::from_str(input).map_err(|_| format!("'{}' is an invalid value", input)) -} - enum ZipWrapper { Raw(Option), Zipped(zip::ZipArchive), @@ -223,6 +158,13 @@ impl ZipWrapper { } 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 { - 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> { + // 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 { -- cgit v1.2.3