loadsmith_install/rule/
glob.rs1use std::borrow::Cow;
2
3use camino::{Utf8Path, Utf8PathBuf};
4use globset::{Glob, GlobBuilder};
5use loadsmith_core::PackageRef;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct GlobRule {
34 pattern: Glob,
35 pub(crate) target: Cow<'static, Utf8Path>,
36 strip_levels: usize,
37 subdir: bool,
38 pub(super) use_links: bool,
39}
40
41impl GlobRule {
42 pub fn new(pattern: Glob, target: impl Into<Cow<'static, Utf8Path>>) -> Self {
44 Self {
45 pattern,
46 target: target.into(),
47 strip_levels: 0,
48 subdir: false,
49 use_links: false,
50 }
51 }
52
53 pub fn try_from_pattern(
57 pattern: impl AsRef<str>,
58 target: impl Into<Cow<'static, Utf8Path>>,
59 ) -> Result<Self, globset::Error> {
60 let pattern = GlobBuilder::new(pattern.as_ref()).build()?;
61 Ok(Self::new(pattern, target))
62 }
63
64 pub fn strip_top_level(mut self, strip_top_level: bool) -> Self {
66 self.strip_levels = if strip_top_level { 1 } else { 0 };
67 self
68 }
69
70 pub fn strip_levels(mut self, strip_levels: usize) -> Self {
72 self.strip_levels = strip_levels;
73 self
74 }
75
76 pub fn use_links(mut self, use_links: bool) -> Self {
78 self.use_links = use_links;
79 self
80 }
81
82 pub fn with_subdir(mut self, subdir: bool) -> Self {
85 self.subdir = subdir;
86 self
87 }
88
89 pub fn matches(&self, path: impl AsRef<Utf8Path>) -> bool {
91 self.pattern.compile_matcher().is_match(path.as_ref())
92 }
93
94 pub fn map_file(
99 &self,
100 path: impl AsRef<Utf8Path>,
101 package: &PackageRef,
102 ) -> Option<Utf8PathBuf> {
103 let path = path.as_ref();
104
105 let suffix = {
106 let mut components = path.components();
107 for _ in 0..self.strip_levels {
108 components.next();
109 }
110
111 if components.clone().next().is_none() {
112 return None;
113 } else {
114 components.as_path()
115 }
116 };
117
118 let mut target = self.target.to_path_buf();
119 if self.subdir {
120 target.push(package.id().as_str());
121 }
122 target.push(suffix);
123
124 Some(target)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use loadsmith_core::{PackageId, Version};
131
132 use super::*;
133
134 #[test]
135 fn matches() {
136 let rule = GlobRule::new(
137 Glob::new("*.rs").unwrap(),
138 Cow::Borrowed(Utf8Path::new("target")),
139 );
140
141 assert!(rule.matches("main.rs"));
142 assert!(rule.matches("src/main.rs"));
143 assert!(!rule.matches("main.txt"));
144 assert!(!rule.matches("src/main.txt"));
145 }
146
147 #[test]
148 fn map_file() {
149 let rule = GlobRule::try_from_pattern("*.rs", Utf8Path::new("target")).unwrap();
150
151 let package = PackageRef::new(PackageId::new("Author-Name"), Version::new(1, 0, 0));
152
153 assert_eq!(
154 rule.map_file("main.rs", &package),
155 Some(Utf8PathBuf::from("target/main.rs"))
156 );
157
158 assert_eq!(
159 rule.map_file("src/main.rs", &package),
160 Some(Utf8PathBuf::from("target/src/main.rs"))
161 );
162
163 assert_eq!(
164 rule.map_file("main.txt", &package),
165 Some(Utf8PathBuf::from("target/main.txt"))
166 );
167 }
168
169 #[test]
170 fn map_file_strip_top() {
171 let rule = GlobRule::try_from_pattern("in/**", Utf8Path::new("out"))
172 .unwrap()
173 .strip_top_level(true);
174 let package = PackageRef::new(PackageId::new("Author-Name"), Version::new(1, 0, 0));
175
176 assert_eq!(
177 rule.map_file("in/file.txt", &package),
178 Some(Utf8PathBuf::from("out/file.txt"))
179 );
180
181 assert_eq!(
182 rule.map_file("in/nested/file.txt", &package),
183 Some(Utf8PathBuf::from("out/nested/file.txt"))
184 );
185
186 assert_eq!(
187 rule.map_file("in/nested/deep/file.txt", &package),
188 Some(Utf8PathBuf::from("out/nested/deep/file.txt"))
189 );
190
191 assert_eq!(rule.map_file("other.txt", &package), None);
192 }
193
194 #[test]
195 fn map_file_strip_multiple() {
196 let rule = GlobRule::try_from_pattern("UE4SS/Mods/*", Utf8Path::new("shimloader/mod"))
197 .unwrap()
198 .strip_levels(2);
199 let package = PackageRef::new(PackageId::new("Author-Name"), Version::new(1, 0, 0));
200
201 assert_eq!(rule.map_file("file.dll", &package), None);
202
203 assert_eq!(rule.map_file("UE4SS/file.dll", &package), None);
204
205 assert_eq!(
206 rule.map_file("UE4SS/Mods/file.dll", &package),
207 Some(Utf8PathBuf::from("shimloader/mod/file.dll"))
208 );
209
210 assert_eq!(
211 rule.map_file("UE4SS/Mods/nested/file.dll", &package),
212 Some(Utf8PathBuf::from("shimloader/mod/nested/file.dll"))
213 );
214
215 assert_eq!(
216 rule.map_file("UE4SS/Mods/nested/deep/file.dll", &package),
217 Some(Utf8PathBuf::from("shimloader/mod/nested/deep/file.dll"))
218 );
219 }
220}