fancy_tree/sorting/
mod.rs1pub use direction::Direction;
3pub use directories::Directories;
4pub use method::Method;
5use mlua::{FromLua, Lua};
6use std::borrow::Cow;
7use std::cmp::Ordering;
8use std::ffi::OsStr;
9use std::path::Path;
10
11mod direction;
12mod directories;
13mod method;
14
15#[derive(Debug)]
22#[non_exhaustive]
23pub struct Sorting {
24 pub method: Method,
26 pub direction: Direction,
28 pub directories: Directories,
30 pub ignore_case: bool,
34 pub ignore_dot: bool,
50}
51
52impl Sorting {
53 const DEFAULT_IGNORE_CASE: bool = cfg!(windows);
55
56 const DEFAULT_IGNORE_DOT: bool = false;
58
59 fn clean_dot<'a>(&self, os_str: &'a OsStr) -> &'a OsStr {
61 if self.ignore_dot {
62 let bytes = os_str.as_encoded_bytes();
63 let bytes = bytes.strip_prefix(b".").unwrap_or(bytes);
64 unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
67 } else {
68 os_str
69 }
70 }
71
72 fn clean_casing<'a>(&self, os_str: &'a OsStr) -> Cow<'a, OsStr> {
74 if self.ignore_case {
75 Cow::from(os_str.to_ascii_lowercase())
76 } else {
77 Cow::from(os_str)
78 }
79 }
80
81 fn clean_path<'a>(&self, path: &'a Path) -> Cow<'a, OsStr> {
83 let file_name = path
84 .file_name()
85 .expect("Path should always terminate in a named component");
86 let file_name = self.clean_dot(file_name);
87 self.clean_casing(file_name)
88 }
89
90 pub fn cmp<L, R>(&self, left: L, right: R) -> Ordering
92 where
93 L: AsRef<Path>,
94 R: AsRef<Path>,
95 {
96 let ordering = self.directories.cmp(&left, &right).then_with(|| {
97 let left = self.clean_path(left.as_ref());
98 let right = self.clean_path(right.as_ref());
99 self.method.cmp(left, right)
100 });
101 match self.direction {
102 Direction::Asc => ordering,
103 Direction::Desc => ordering.reverse(),
104 }
105 }
106}
107
108impl Default for Sorting {
109 fn default() -> Self {
110 Self {
111 method: Default::default(),
112 direction: Default::default(),
113 directories: Default::default(),
114 ignore_case: Self::DEFAULT_IGNORE_CASE,
115 ignore_dot: Self::DEFAULT_IGNORE_DOT,
116 }
117 }
118}
119
120impl FromLua for Sorting {
121 fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result<Self> {
122 let table = mlua::Table::from_lua(value, lua)?;
123 let method = table.get::<Option<Method>>("method")?.unwrap_or_default();
124 let direction = table
125 .get::<Option<Direction>>("direction")?
126 .unwrap_or_default();
127 let directories = table
128 .get::<Option<Directories>>("directories")?
129 .unwrap_or_default();
130 let ignore_case = table
131 .get::<Option<bool>>("ignore_case")?
132 .unwrap_or(Self::DEFAULT_IGNORE_CASE);
133 let ignore_dot = table
134 .get::<Option<bool>>("ignore_dot")?
135 .unwrap_or(Self::DEFAULT_IGNORE_DOT);
136
137 let sorting = Self {
138 method,
139 direction,
140 directories,
141 ignore_case,
142 ignore_dot,
143 };
144 Ok(sorting)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use rstest::rstest;
152
153 #[rstest]
154 #[case(false, ".env", ".env")]
155 #[case(true, ".env", "env")]
156 #[case(true, "foo.txt", "foo.txt")]
157 fn test_clean_dot(#[case] ignore_dot: bool, #[case] s: &str, #[case] expected: &str) {
158 let sorting = Sorting {
159 ignore_dot,
160 ..Default::default()
161 };
162
163 assert_eq!(OsStr::new(expected), sorting.clean_dot(OsStr::new(s)))
164 }
165
166 #[rstest]
167 #[case(false, "Dockerfile", "Dockerfile")]
168 #[case(true, "Dockerfile", "dockerfile")]
169 fn test_clean_casing(#[case] ignore_case: bool, #[case] s: &str, #[case] expected: &str) {
170 let sorting = Sorting {
171 ignore_case,
172 ..Default::default()
173 };
174
175 assert_eq!(OsStr::new(expected), sorting.clean_casing(OsStr::new(s)))
176 }
177}