aboutsummaryrefslogtreecommitdiff
path: root/src/output/sorting.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/sorting.rs')
-rw-r--r--src/output/sorting.rs148
1 files changed, 148 insertions, 0 deletions
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<Component>),
+}
+
+impl FromStr for Component {
+ type Err = InvalidComponent;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ 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<Component>);
+
+impl FromStr for Sorting {
+ type Err = InvalidComponent;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s == "" {
+ return Ok(Sorting::default());
+ }
+ let parts = s.split(',');
+ parts.map(FromStr::from_str).collect::<Result<Vec<Component>, _>>().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<Component>) -> 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::<Component>(), 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()));
+ }
+}