1use mlua::{FromLua, Lua};
3use std::cmp::Ordering;
4use std::path::Path;
5
6#[derive(Debug, PartialEq, Eq, Clone, Copy)]
8pub enum Directories {
9 Mixed,
11 First,
13 Last,
15}
16
17impl Directories {
18 const MIXED_NAME: &'static str = "mixed";
19 const FIRST_NAME: &'static str = "first";
20 const LAST_NAME: &'static str = "last";
21
22 fn from_string(s: &str) -> Option<Self> {
24 use Directories::*;
25
26 [
27 (Self::MIXED_NAME, Mixed),
28 (Self::FIRST_NAME, First),
29 (Self::LAST_NAME, Last),
30 ]
31 .into_iter()
32 .find_map(|(name, d)| (s == name).then_some(d))
33 }
34
35 pub fn cmp<L, R>(&self, left: L, right: R) -> Ordering
37 where
38 L: AsRef<Path>,
39 R: AsRef<Path>,
40 {
41 self.cmp_impl(left.as_ref(), right.as_ref())
42 }
43
44 fn cmp_impl<L, R>(&self, left: L, right: R) -> Ordering
46 where
47 L: IsDirectory,
48 R: IsDirectory,
49 {
50 if let Self::Mixed = self {
51 return Ordering::Equal;
54 }
55
56 match (self, left.is_directory(), right.is_directory()) {
57 (Self::Mixed, _, _) => unreachable!("Already checked for mixed ordering"),
58 (_, true, true) | (_, false, false) => Ordering::Equal,
59 (Self::First, true, false) | (Self::Last, false, true) => Ordering::Less,
60 (Self::First, false, true) | (Self::Last, true, false) => Ordering::Greater,
61 }
62 }
63}
64
65impl Default for Directories {
66 #[inline]
67 fn default() -> Self {
68 Self::Mixed
69 }
70}
71
72impl FromLua for Directories {
73 fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result<Self> {
74 let type_name = value.type_name();
75
76 let conversion_error = || {
77 let choices = [Self::MIXED_NAME, Self::FIRST_NAME, Self::LAST_NAME].join(", ");
78
79 mlua::Error::FromLuaConversionError {
80 from: type_name,
81 to: String::from("Directories"),
82 message: Some(choices),
83 }
84 };
85
86 let s = String::from_lua(value, lua)?;
87 Self::from_string(&s).ok_or_else(conversion_error)
88 }
89}
90
91trait IsDirectory {
93 fn is_directory(&self) -> bool;
95}
96
97impl IsDirectory for &Path {
98 #[inline]
99 fn is_directory(&self) -> bool {
100 self.is_dir()
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use rstest::rstest;
108
109 #[rstest]
110 #[case(r#""mixed""#, Directories::Mixed)]
111 #[case(r#""first""#, Directories::First)]
112 #[case(r#""last""#, Directories::Last)]
113 fn test_from_lua(#[case] chunk: &str, #[case] expected: Directories) {
114 let lua = Lua::new();
115 let actual: Directories = lua.load(chunk).eval().unwrap();
116 assert_eq!(expected, actual);
117 }
118
119 #[test]
120 fn test_from_lua_err() {
121 let lua = Lua::new();
122 let chunk = r#"1"#;
123 assert!(lua.load(chunk).eval::<Directories>().is_err())
124 }
125
126 #[rstest]
127 #[case(Directories::Mixed, true, false, Ordering::Equal)]
128 #[case(Directories::Mixed, false, true, Ordering::Equal)]
129 #[case(Directories::First, true, true, Ordering::Equal)]
130 #[case(Directories::First, true, false, Ordering::Less)]
131 #[case(Directories::First, false, true, Ordering::Greater)]
132 #[case(Directories::Last, true, true, Ordering::Equal)]
133 #[case(Directories::Last, true, false, Ordering::Greater)]
134 #[case(Directories::Last, false, true, Ordering::Less)]
135 fn test_cmp(
136 #[case] directories: Directories,
137 #[case] left_is_dir: bool,
138 #[case] right_is_dir: bool,
139 #[case] expected: Ordering,
140 ) {
141 struct IsDir(bool);
142 impl IsDirectory for IsDir {
143 fn is_directory(&self) -> bool {
144 self.0
145 }
146 }
147
148 assert_eq!(
149 expected,
150 directories.cmp_impl(IsDir(left_is_dir), IsDir(right_is_dir))
151 )
152 }
153}