From 9d7539229af4e1f9ef30a3f40c1bb19a8fb0e67c Mon Sep 17 00:00:00 2001 From: Dhruv Bhanushali Date: Sat, 15 Jul 2023 00:55:25 +0400 Subject: [PATCH] Define trait and constants for importance --- src/models/constants.rs | 95 +++++++++++++++++++++++++++++++++++++++++ src/traits.rs | 2 + src/traits/imp.rs | 79 ++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 src/traits/imp.rs diff --git a/src/models/constants.rs b/src/models/constants.rs index 8d332d3c..97e47854 100644 --- a/src/models/constants.rs +++ b/src/models/constants.rs @@ -28,6 +28,12 @@ pub struct Constants { pub symlink: HashMap, /// configuration for the table view pub table: TableInfo, + /// pairings of importance levels with styling directives + pub imp: Vec<(i8, String)>, + + /// mapping of importance levels to styling directives, derived from `imp` + #[serde(skip)] + pub imp_map: HashMap, } impl Default for Constants { @@ -156,10 +162,48 @@ impl Default for Constants { .map(|(k, v)| (k, v.to_string())) .collect(), }, + imp: [(-1, "dimmed"), (1, "italic"), (2, "underline")] + .into_iter() + .map(|(k, v)| (k, v.to_string())) + .collect(), + + imp_map: HashMap::new(), // set in Constants::set_imp_map } } } +impl Constants { + /// Set `imp_map` from `imp` and then use it to clean the latter. + /// + /// If a level has been defined multiple times, only the last definition + /// will be retained in `imp_map`. The levels will be sorted by importance + /// in `imp`. + pub fn massage_imps(&mut self) { + self.imp_map = self.imp.iter().map(|(k, v)| (*k, v.to_string())).collect(); + + self.imp = self.imp_map.clone().into_iter().collect(); + self.imp.sort_by_cached_key(|entry| entry.0); + } + + /// Get the lowest configured importance level, i.e. zeroth index in `imp`. + pub fn min_imp(&self) -> i8 { + self.get_imp(0) + } + + /// Get the highest configured importance level, i.e. last index in `imp`. + pub fn max_imp(&self) -> i8 { + self.get_imp(self.imp.len() - 1) + } + + /// Get the importance level at the given index. + /// + /// This returns 0 (default for `i8`) if the `imp` vector does not have the + /// given index. + fn get_imp(&self, idx: usize) -> i8 { + self.imp.get(idx).map(|row| row.0).unwrap_or_default() + } +} + #[derive(Serialize, Deserialize)] pub struct NlinkStyles { /// style to use when file has one hard link @@ -217,3 +261,54 @@ pub struct TableInfo { /// the styles to apply to the text in the header row pub header_style: String, } + +#[cfg(test)] +mod tests { + use super::Constants; + use std::collections::HashMap; + + macro_rules! make_massage_imps_test { + ( $($name:ident: $imp:expr => $exp_imp:expr,)* ) => { + $( + #[test] + fn $name() { + let imp: Vec<(i8, String)> = $imp + .into_iter() + .map(|(k, v): (i8, &str)| (k, v.to_string())) + .collect(); + let exp_imp: Vec<(i8, String)> = $exp_imp + .into_iter() + .map(|(k, v): (i8, &str)| (k, v.to_string())) + .collect(); + let exp_imp_map: HashMap = $exp_imp + .into_iter() + .map(|(k, v): (i8, &str)| (k, v.to_string())) + .collect(); + + let mut constants = Constants { + imp, + ..Constants::default() + }; + constants.massage_imps(); + assert_eq!(constants.imp, exp_imp); + assert_eq!(constants.imp_map, exp_imp_map); + } + )* + } + } + + make_massage_imps_test!( + test_empty: vec![] => vec![], + test_sorting: vec![ + (2, "underline"), + (1, "italic"), + ] => vec![(1, "italic"), (2, "underline")], + test_deduplication: vec![ + (2, "bold"), + (2, "dimmed"), + (1, "reversed"), + (2, "underline"), + (1, "italic"), + ] => vec![(1, "italic"), (2, "underline")], + ); +} diff --git a/src/traits.rs b/src/traits.rs index e28d5057..3017aa2d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,7 +1,9 @@ mod detail; +mod imp; mod name; mod sym; pub use detail::Detail; +pub use imp::Imp; pub use name::Name; pub use sym::Sym; diff --git a/src/traits/imp.rs b/src/traits/imp.rs new file mode 100644 index 00000000..f6bd21a2 --- /dev/null +++ b/src/traits/imp.rs @@ -0,0 +1,79 @@ +use crate::config::{Args, Conf}; +use crate::models::Node; +use log::debug; + +pub trait Imp { + fn default_imp(&self) -> i8; + fn imp_val(&self, args: &Args) -> i8; + + fn is_visible(&self, conf: &Conf, args: &Args) -> bool; + + fn directives(&self, conf: &Conf, args: &Args) -> Option; +} + +impl Imp for Node<'_> { + /// Get the implicit relative importance of the node. + /// + /// This is the importance associated with a node if it has not been set by + /// any matching spec. By default we assume nodes with a leading dot to be + /// less important, as they are normally hidden by the `ls(1)` command. + fn default_imp(&self) -> i8 { + if self.name.starts_with('.') { + -1 + } else { + 0 + } + } + + /// Get the relative importance of the node. + /// + /// This iterates through the specs in reverse, finding the first available + /// importance or falling back the the [default](Imp::default_imp). Then it + /// subtracts the baseline level from the CLI args. + fn imp_val(&self, args: &Args) -> i8 { + self.specs + .iter() + .rev() + .find_map(|spec| spec.importance) + .unwrap_or(self.default_imp()) + - args.imp + } + + /// Determine whether the node should be displayed in the list. + /// + /// Elements below the lowest-defined relative-importance are hidden. + fn is_visible(&self, conf: &Conf, args: &Args) -> bool { + debug!("Checking visibility of \"{self}\" based on importance."); + let rel_imp = self.imp_val(args); + let min_val = conf.constants.min_imp(); + + let is_visible = rel_imp >= min_val; + if !is_visible { + debug!("\"{self}\" with relative importance {rel_imp} (min: {min_val}) is hidden.") + } + is_visible + } + + /* Directives */ + /* ========== */ + + /// Get the directives associated with the node's relative importance. + /// + /// The directives are read from the configuration with any missing values + /// having no directives set for them. + /// + /// If the node's importance is above the maximum defined, it will be set to + /// the maximum. If it is below the minimum defined, it will already be + /// hidden by [`is_visible`](Imp::is_visible). + fn directives(&self, conf: &Conf, args: &Args) -> Option { + let mut rel_imp = self.imp_val(args); + let max_val = conf.constants.max_imp(); + let min_val = conf.constants.min_imp(); + debug!("\"{self}\" has relative importance {rel_imp} (min: {min_val}, max: {max_val})"); + + if rel_imp > max_val { + rel_imp = max_val; + } + conf.constants.imp_map.get(&rel_imp).cloned() + } +}