From fb2a6088dcc7b57a2c1ac93ec6a8fbcc52584734 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 13 May 2020 13:49:43 +0200 Subject: first attempt at sorting output This does currently not work yet, as we cannot call .finish() on dyn Aggregator. This needs to be adjusted. However, this provides the basic infrastructure for producing sorted output, including the required command line parsing. --- src/output/sorting.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/output/sorting.rs (limited to 'src/output/sorting.rs') diff --git a/src/output/sorting.rs b/src/output/sorting.rs new file mode 100644 index 0000000..8b271a9 --- /dev/null +++ b/src/output/sorting.rs @@ -0,0 +1,148 @@ +//! Definitions and functions to produce sorted output. + +use std::{ + cmp::Ordering, + fmt::{self, Display, Formatter}, + str::FromStr, +}; + +use itertools::Itertools; +use thiserror::Error; + +use crate::LogResult; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Error)] +#[error("an invalid sorting component was given")] +pub struct InvalidComponent; + +/// A [`Component`][Component] is anything that can be used to sort the result by. +/// +/// Note that some components may not provide a true "sorting" (is Skorvald coming before Dhuum or +/// not?), but are more of a convenience that allows to group logs of the same component together. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum Component { + /// Sort the result date. + Date, + /// Sort by the boss. + Boss, + /// Sort by the outcome. + Outcome, + /// Sort based on whether the Challenge Mote was active or not. + ChallengeMote, + + /// Sort by the given component but in reverse direction. + Reverse(Box), +} + +impl FromStr for Component { + type Err = InvalidComponent; + + fn from_str(s: &str) -> Result { + if s.starts_with('-') { + return s[1..].parse().map(|c| Component::Reverse(Box::new(c))); + } + match &s.to_lowercase() as &str { + "date" => Ok(Component::Date), + "boss" => Ok(Component::Boss), + "outcome" => Ok(Component::Outcome), + "cm" => Ok(Component::ChallengeMote), + _ => Err(InvalidComponent), + } + } +} + +impl Display for Component { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Component::Date => write!(f, "date"), + Component::Boss=> write!(f, "boss"), + Component::Outcome => write!(f, "outcome"), + Component::ChallengeMote => write!(f, "cm"), + Component::Reverse(ref inner) => write!(f, "-{}", inner), + } + } +} + +fn boss_id(log: &LogResult) -> u32 { + log.boss.map(|x| x as u32).unwrap_or(0) +} + +impl Component { + pub fn cmp(&self, lhs: &LogResult, rhs: &LogResult) -> Ordering { + match self { + Component::Date => lhs.time.cmp(&rhs.time), + Component::Boss => boss_id(lhs).cmp(&boss_id(rhs)), + Component::Outcome => lhs.outcome.cmp(&rhs.outcome), + Component::ChallengeMote => lhs.is_cm.cmp(&rhs.is_cm), + Component::Reverse(ref inner) => inner.cmp(lhs, rhs).reverse(), + } + } +} + +/// A sorting. +/// +/// The sorting goes lexicographically, in the sense that if the first chosen component is the same +/// for two elements, the next component will be used to compare two logs. +#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)] +pub struct Sorting(Vec); + +impl FromStr for Sorting { + type Err = InvalidComponent; + + fn from_str(s: &str) -> Result { + if s == "" { + return Ok(Sorting::default()); + } + let parts = s.split(','); + parts.map(FromStr::from_str).collect::, _>>().map(Sorting::new) + } +} + +impl Display for Sorting { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let string = self.0.iter().map(ToString::to_string).join(","); + f.write_str(&string) + } +} + +impl Sorting { + pub fn new(components: Vec) -> Sorting { + Sorting(components) + } + + pub fn cmp(&self, lhs: &LogResult, rhs: &LogResult) -> Ordering { + for component in &self.0 { + let result = component.cmp(lhs, rhs); + if result != Ordering::Equal { + return result; + } + } + Ordering::Equal + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_component() { + assert_eq!("date".parse(), Ok(Component::Date)); + assert_eq!("boss".parse(), Ok(Component::Boss)); + assert_eq!("outcome".parse(), Ok(Component::Outcome)); + assert_eq!("cm".parse(), Ok(Component::ChallengeMote)); + + assert_eq!("foobar".parse::(), Err(InvalidComponent)); + + assert_eq!("-date".parse(), Ok(Component::Reverse(Box::new(Component::Date)))); + } + + #[test] + fn parse_sorting() { + use Component::*; + assert_eq!("date".parse(), Ok(Sorting::new(vec![Date]))); + assert_eq!("date,boss".parse(), Ok(Sorting::new(vec![Date, Boss]))); + assert_eq!("date,-boss".parse(), Ok(Sorting::new(vec![Date, Reverse(Box::new(Boss))]))); + assert_eq!("".parse(), Ok(Sorting::default())); + } +} -- cgit v1.2.3