From dfbf78622b0869f070d062b3edd40c4a97ce7dfd Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 16 Feb 2019 01:48:59 +0100 Subject: introduce CommaSeparatedList This gives a common interface for command line flags which take multiple values, possibly with negation. This might come in useful if we add filtering by boss, e.g. "--boss !deimos" to ignore all deimos logs. --- src/csl.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/filters.rs | 12 +++----- src/main.rs | 34 +++++++--------------- 3 files changed, 106 insertions(+), 32 deletions(-) create mode 100644 src/csl.rs (limited to 'src') diff --git a/src/csl.rs b/src/csl.rs new file mode 100644 index 0000000..83f2e14 --- /dev/null +++ b/src/csl.rs @@ -0,0 +1,92 @@ +use std::collections::HashSet; +use std::hash::Hash; +use std::str::FromStr; +use std::fmt; + +use super::{SearchField, FightOutcome}; + +pub trait Variants: Copy { + type Output: Iterator; + fn variants() -> Self::Output; +} + +macro_rules! variants { + ($target:ident => $($var:ident),+) => { + impl Variants for $target { + type Output = ::std::iter::Cloned<::std::slice::Iter<'static, Self>>; + fn variants() -> Self::Output { + // Exhaustiveness check + #[allow(dead_code)] + fn exhaustiveness_check(value: $target) { + match value { + $($target :: $var => ()),+ + } + } + // Actual result + [ + $($target :: $var),+ + ].iter().cloned() + } + } + } +} + +variants! { SearchField => Account, Character } +variants! { FightOutcome => Success, Wipe } + +/// The character that delimits items from each other. +const DELIMITER: char = ','; +/// The character that negates the result. +const NEGATOR: char = '!'; + +/// A list that is given as comma-separated values. +#[derive(Debug, Clone)] +pub struct CommaSeparatedList { + values: HashSet, +} + +#[derive(Debug, Clone)] +pub enum ParseError { + Underlying(E), +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ParseError::Underlying(ref e) => e.fmt(f), + } + } +} + +impl FromStr for CommaSeparatedList + where T: FromStr + Variants + Hash + Eq + fmt::Debug +{ + type Err = ParseError; + + fn from_str(input: &str) -> Result { + if input == "*" { + Ok(CommaSeparatedList { values: T::variants().collect() }) + } else if input.starts_with(NEGATOR) { + let no_csl = CommaSeparatedList::from_str(&input[1..])?; + let all_values = T::variants().collect::>(); + Ok(CommaSeparatedList { + values: all_values.difference(&no_csl.values).cloned().collect() + }) + } else { + let parts = input.split(DELIMITER); + let values = parts + .map(FromStr::from_str) + .collect::, _>>() + .map_err(ParseError::Underlying)?; + Ok(CommaSeparatedList { values }) + } + } +} + +impl CommaSeparatedList + where T: Hash + Eq + fmt::Debug +{ + pub fn contains(&self, value: &T) -> bool { + self.values.contains(value) + } +} diff --git a/src/filters.rs b/src/filters.rs index 6980900..f9db377 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -1,6 +1,6 @@ use evtclib::{AgentName, Log}; -use super::{LogResult, Opt}; +use super::{SearchField, FightOutcome, LogResult, Opt}; /// Do filtering based on the character or account name. pub fn filter_name(log: &Log, opt: &Opt) -> bool { @@ -11,8 +11,8 @@ pub fn filter_name(log: &Log, opt: &Opt) -> bool { character_name, .. } => { - if (opt.field.search_account() && opt.expression.is_match(account_name)) - || (opt.field.search_character() && opt.expression.is_match(character_name)) + if (opt.field.contains(&SearchField::Account) && opt.expression.is_match(account_name)) + || (opt.field.contains(&SearchField::Character) && opt.expression.is_match(character_name)) { return true; } @@ -25,11 +25,7 @@ pub fn filter_name(log: &Log, opt: &Opt) -> bool { /// Do filtering based on the fight outcome. pub fn filter_outcome(result: &LogResult, opt: &Opt) -> bool { - match opt.outcome { - Some(o) if o == result.outcome => true, - None => true, - _ => false, - } + opt.outcome.contains(&result.outcome) } /// Do filtering based on encounter time. diff --git a/src/main.rs b/src/main.rs index a77a952..13d0a76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,9 @@ mod output; mod filters; +mod csl; +use csl::CommaSeparatedList; + macro_rules! unwrap { ($p:pat = $e:expr => { $r:expr} ) => { if let $p = $e { @@ -75,12 +78,12 @@ pub struct Opt { path: PathBuf, /// The fields which should be searched. - #[structopt(short = "f", long = "fields", default_value = "all")] - field: SearchField, + #[structopt(short = "f", long = "fields", default_value = "*")] + field: CommaSeparatedList, /// Only display fights with the given outcome. - #[structopt(short = "o", long = "outcome")] - outcome: Option, + #[structopt(short = "o", long = "outcome", default_value = "*")] + outcome: CommaSeparatedList, /// Disable colored output. #[structopt(long = "no-color")] @@ -112,39 +115,22 @@ pub struct Opt { } /// A flag indicating which fields should be searched. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] enum SearchField { - /// Search all fields. - All, /// Only search the account name. Account, /// Only search the character name. Character, } -impl SearchField { - /// True if the state says that the account name should be searched. - #[inline] - fn search_account(self) -> bool { - self == SearchField::All || self == SearchField::Account - } - - /// True if the state says that the character name should be searched. - #[inline] - fn search_character(self) -> bool { - self == SearchField::All || self == SearchField::Character - } -} - impl FromStr for SearchField { type Err = &'static str; fn from_str(s: &str) -> Result { match s { - "all" => Ok(SearchField::All), "account" => Ok(SearchField::Account), "character" => Ok(SearchField::Character), - _ => Err("Must be all, account or character"), + _ => Err("Must be account or character"), } } } @@ -178,7 +164,7 @@ pub struct Player { } /// Outcome of the fight. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum FightOutcome { Success, Wipe, -- cgit v1.2.3