Skip to main content

fancy_tree/config/main/
mod.rs

1//! Module for the main config.
2use super::ConfigFile;
3use crate::color::ColorChoice;
4use crate::lua::interop;
5use crate::sorting;
6use crate::tree::Entry;
7use mlua::{
8    Either::{self, Left, Right},
9    FromLua, Lua,
10};
11use std::cmp::Ordering;
12use std::path::Path;
13
14/// Either a sorting configuration, or a function that takes two values and returns
15/// a negative number for less-than, 0 for equal, or a positive number for greater-than.
16type Sorting = Either<sorting::Sorting, mlua::Function>;
17
18/// The main configuration type.
19#[derive(Debug)]
20pub struct Main {
21    /// Determines when/how the application should show colors.
22    color: ColorChoice,
23    /// Function to determine if a file should be skipped.
24    skip: Option<mlua::Function>,
25    /// Determines how to sort files in a directory.
26    sorting: Sorting,
27    /// How many levels deep to search before stopping.
28    level: Option<usize>,
29}
30
31impl Main {
32    /// Gets the configured color choice.
33    #[inline]
34    pub fn color_choice(&self) -> ColorChoice {
35        self.color
36    }
37    /// Should a file be skipped according to the configuration?
38    ///
39    /// `git_helper` is used to provide interoperability with git, which this config
40    /// type isn't aware of.
41    pub fn should_skip<P, F>(&self, entry: &Entry<P>, git_helper: F) -> bool
42    where
43        P: AsRef<Path>,
44        F: FnOnce() -> bool,
45    {
46        let default = entry.is_hidden() || git_helper();
47        let path = entry.path();
48        let attributes = interop::FileAttributes::from(entry);
49
50        // TODO Report error
51        self.skip
52            .as_ref()
53            .map_or(Ok(default), |f| f.call::<bool>((path, attributes, default)))
54            .unwrap_or(default)
55    }
56
57    /// Compares two paths for sorting.
58    pub fn cmp<L, R>(&self, left: L, right: R) -> Ordering
59    where
60        L: AsRef<Path>,
61        R: AsRef<Path>,
62    {
63        // TODO Report error
64        match self.sorting.as_ref() {
65            Left(sorting) => sorting.cmp(left, right),
66            Right(f) => f
67                .call((left.as_ref(), right.as_ref()))
68                .map(Self::isize_to_ordering)
69                .unwrap_or(Ordering::Equal),
70        }
71    }
72
73    /// Creates the default sorting configuration.
74    fn default_sorting() -> Sorting {
75        Left(Default::default())
76    }
77
78    /// Converts a number returned by a lua function for comparing paths into [`Ordering`].
79    fn isize_to_ordering(n: isize) -> Ordering {
80        match n {
81            ..=-1 => Ordering::Less,
82            0 => Ordering::Equal,
83            1.. => Ordering::Greater,
84        }
85    }
86
87    /// How many levels deep to search before stopping.
88    pub fn level(&self) -> Option<usize> {
89        self.level
90    }
91}
92
93impl Default for Main {
94    fn default() -> Self {
95        Self {
96            color: Default::default(),
97            skip: None,
98            sorting: Self::default_sorting(),
99            level: None,
100        }
101    }
102}
103
104impl ConfigFile for Main {
105    const FILENAME: &'static str = "config.lua";
106    const DEFAULT_MODULE: &'static str = include_str!("./config.lua");
107}
108
109impl FromLua for Main {
110    fn from_lua(value: mlua::Value, _lua: &Lua) -> mlua::Result<Self> {
111        let type_name = value.type_name();
112
113        let conversion_error = || mlua::Error::FromLuaConversionError {
114            from: type_name,
115            to: String::from("config::Main"),
116            message: None,
117        };
118
119        let table = value.as_table().ok_or_else(conversion_error)?;
120        let color = table
121            .get::<Option<ColorChoice>>("color")?
122            .unwrap_or_default();
123        let skip: Option<mlua::Function> = table.get("skip")?;
124        let sorting = table
125            .get::<Option<Sorting>>("sorting")?
126            .unwrap_or_else(Self::default_sorting);
127        let level = table.get("level")?;
128        let main = Main {
129            color,
130            skip,
131            sorting,
132            level,
133        };
134        Ok(main)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use rstest::rstest;
142
143    #[rstest]
144    #[case(-1, Ordering::Less)]
145    #[case(0, Ordering::Equal)]
146    #[case(1, Ordering::Greater)]
147    fn test_isize_to_ordering(#[case] n: isize, #[case] expected: Ordering) {
148        assert_eq!(expected, Main::isize_to_ordering(n));
149    }
150}