aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/csl.rs92
-rw-r--r--src/filters.rs12
-rw-r--r--src/main.rs34
3 files changed, 106 insertions, 32 deletions
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<Item=Self>;
+ 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<T: Eq + Hash + fmt::Debug> {
+ values: HashSet<T>,
+}
+
+#[derive(Debug, Clone)]
+pub enum ParseError<E> {
+ Underlying(E),
+}
+
+impl<E: fmt::Display> fmt::Display for ParseError<E> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ParseError::Underlying(ref e) => e.fmt(f),
+ }
+ }
+}
+
+impl<T> FromStr for CommaSeparatedList<T>
+ where T: FromStr + Variants + Hash + Eq + fmt::Debug
+{
+ type Err = ParseError<T::Err>;
+
+ fn from_str(input: &str) -> Result<Self, Self::Err> {
+ 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::<HashSet<_>>();
+ 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::<Result<HashSet<_>, _>>()
+ .map_err(ParseError::Underlying)?;
+ Ok(CommaSeparatedList { values })
+ }
+ }
+}
+
+impl<T> CommaSeparatedList<T>
+ 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<SearchField>,
/// Only display fights with the given outcome.
- #[structopt(short = "o", long = "outcome")]
- outcome: Option<FightOutcome>,
+ #[structopt(short = "o", long = "outcome", default_value = "*")]
+ outcome: CommaSeparatedList<FightOutcome>,
/// 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<Self, Self::Err> {
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,