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}
28
29impl Main {
30    /// Gets the configured color choice.
31    #[inline]
32    pub fn color_choice(&self) -> ColorChoice {
33        self.color
34    }
35    /// Should a file be skipped according to the configuration?
36    ///
37    /// `git_helper` is used to provide interoperability with git, which this config
38    /// type isn't aware of.
39    pub fn should_skip<P, F>(&self, entry: &Entry<P>, git_helper: F) -> bool
40    where
41        P: AsRef<Path>,
42        F: FnOnce() -> bool,
43    {
44        let default = entry.is_hidden() || git_helper();
45        let path = entry.path();
46        let attributes = interop::FileAttributes::from(entry);
47
48        // TODO Report error
49        self.skip
50            .as_ref()
51            .map_or(Ok(default), |f| f.call::<bool>((path, attributes, default)))
52            .unwrap_or(default)
53    }
54
55    /// Compares two paths for sorting.
56    pub fn cmp<L, R>(&self, left: L, right: R) -> Ordering
57    where
58        L: AsRef<Path>,
59        R: AsRef<Path>,
60    {
61        // TODO Report error
62        match self.sorting.as_ref() {
63            Left(sorting) => sorting.cmp(left, right),
64            Right(f) => f
65                .call((left.as_ref(), right.as_ref()))
66                .map(Self::isize_to_ordering)
67                .unwrap_or(Ordering::Equal),
68        }
69    }
70
71    /// Creates the default sorting configuration.
72    fn default_sorting() -> Sorting {
73        Left(Default::default())
74    }
75
76    /// Converts a number returned by a lua function for comparing paths into [`Ordering`].
77    fn isize_to_ordering(n: isize) -> Ordering {
78        match n {
79            ..=-1 => Ordering::Less,
80            0 => Ordering::Equal,
81            1.. => Ordering::Greater,
82        }
83    }
84}
85
86impl Default for Main {
87    fn default() -> Self {
88        Self {
89            color: Default::default(),
90            skip: None,
91            sorting: Self::default_sorting(),
92        }
93    }
94}
95
96impl ConfigFile for Main {
97    const FILENAME: &'static str = "config.lua";
98    const DEFAULT_MODULE: &'static str = include_str!("./config.lua");
99}
100
101impl FromLua for Main {
102    fn from_lua(value: mlua::Value, _lua: &Lua) -> mlua::Result<Self> {
103        let type_name = value.type_name();
104
105        let conversion_error = || mlua::Error::FromLuaConversionError {
106            from: type_name,
107            to: String::from("config::Main"),
108            message: None,
109        };
110
111        let table = value.as_table().ok_or_else(conversion_error)?;
112        let color = table
113            .get::<Option<ColorChoice>>("color")?
114            .unwrap_or_default();
115        let skip: Option<mlua::Function> = table.get("skip")?;
116        let sorting = table
117            .get::<Option<Sorting>>("sorting")?
118            .unwrap_or_else(Self::default_sorting);
119        let main = Main {
120            color,
121            skip,
122            sorting,
123        };
124        Ok(main)
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use rstest::rstest;
132
133    #[rstest]
134    #[case(-1, Ordering::Less)]
135    #[case(0, Ordering::Equal)]
136    #[case(1, Ordering::Greater)]
137    fn test_isize_to_ordering(#[case] n: isize, #[case] expected: Ordering) {
138        assert_eq!(expected, Main::isize_to_ordering(n));
139    }
140}