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)]
9pub struct GlobRule {
10 pattern: Glob,
11 pub(crate) target: Cow<'static, Utf8Path>,
12 strip_levels: usize,
13 subdir: bool,
14 pub(super) use_links: bool,
15}
16
17impl GlobRule {
18 pub fn new(pattern: Glob, target: impl Into<Cow<'static, Utf8Path>>) -> Self {
19 Self {
20 pattern,
21 target: target.into(),
22 strip_levels: 0,
23 subdir: false,
24 use_links: false,
25 }
26 }
27
28 pub fn try_from_pattern(
29 pattern: impl AsRef<str>,
30 target: impl Into<Cow<'static, Utf8Path>>,
31 ) -> Result<Self, globset::Error> {
32 let pattern = GlobBuilder::new(pattern.as_ref()).build()?;
33 Ok(Self::new(pattern, target))
34 }
35
36 pub fn strip_top_level(mut self, strip_top_level: bool) -> Self {
37 self.strip_levels = if strip_top_level { 1 } else { 0 };
38 self
39 }
40
41 pub fn strip_levels(mut self, strip_levels: usize) -> Self {
42 self.strip_levels = strip_levels;
43 self
44 }
45
46 pub fn use_links(mut self, use_links: bool) -> Self {
47 self.use_links = use_links;
48 self
49 }
50
51 pub fn with_subdir(mut self, subdir: bool) -> Self {
52 self.subdir = subdir;
53 self
54 }
55
56 pub fn matches(&self, path: impl AsRef<Utf8Path>) -> bool {
57 self.pattern.compile_matcher().is_match(path.as_ref())
58 }
59
60 pub fn map_file(
61 &self,
62 path: impl AsRef<Utf8Path>,
63 package: &PackageRef,
64 ) -> Option<Utf8PathBuf> {
65 let path = path.as_ref();
66
67 let suffix = {
68 let mut components = path.components();
69 for _ in 0..self.strip_levels {
70 components.next();
71 }
72
73 if components.clone().next().is_none() {
74 return None;
75 } else {
76 components.as_path()
77 }
78 };
79
80 let mut target = self.target.to_path_buf();
81 if self.subdir {
82 target.push(package.id().as_str());
83 }
84 target.push(suffix);
85
86 Some(target)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use loadsmith_core::{PackageId, Version};
93
94 use super::*;
95
96 #[test]
97 fn matches() {
98 let rule = GlobRule::new(
99 Glob::new("*.rs").unwrap(),
100 Cow::Borrowed(Utf8Path::new("target")),
101 );
102
103 assert!(rule.matches("main.rs"));
104 assert!(rule.matches("src/main.rs"));
105 assert!(!rule.matches("main.txt"));
106 assert!(!rule.matches("src/main.txt"));
107 }
108
109 #[test]
110 fn map_file() {
111 let rule = GlobRule::try_from_pattern("*.rs", Utf8Path::new("target")).unwrap();
112
113 let package = PackageRef::new(PackageId::new("Author-Name"), Version::new(1, 0, 0));
114
115 assert_eq!(
116 rule.map_file("main.rs", &package),
117 Some(Utf8PathBuf::from("target/main.rs"))
118 );
119
120 assert_eq!(
121 rule.map_file("src/main.rs", &package),
122 Some(Utf8PathBuf::from("target/src/main.rs"))
123 );
124
125 assert_eq!(
126 rule.map_file("main.txt", &package),
127 Some(Utf8PathBuf::from("target/main.txt"))
128 );
129 }
130
131 #[test]
132 fn map_file_strip_top() {
133 let rule = GlobRule::try_from_pattern("in/**", Utf8Path::new("out"))
134 .unwrap()
135 .strip_top_level(true);
136 let package = PackageRef::new(PackageId::new("Author-Name"), Version::new(1, 0, 0));
137
138 assert_eq!(
139 rule.map_file("in/file.txt", &package),
140 Some(Utf8PathBuf::from("out/file.txt"))
141 );
142
143 assert_eq!(
144 rule.map_file("in/nested/file.txt", &package),
145 Some(Utf8PathBuf::from("out/nested/file.txt"))
146 );
147
148 assert_eq!(
149 rule.map_file("in/nested/deep/file.txt", &package),
150 Some(Utf8PathBuf::from("out/nested/deep/file.txt"))
151 );
152
153 assert_eq!(rule.map_file("other.txt", &package), None);
154 }
155
156 #[test]
157 fn map_file_strip_multiple() {
158 let rule = GlobRule::try_from_pattern("UE4SS/Mods/*", Utf8Path::new("shimloader/mod"))
159 .unwrap()
160 .strip_levels(2);
161 let package = PackageRef::new(PackageId::new("Author-Name"), Version::new(1, 0, 0));
162
163 assert_eq!(rule.map_file("file.dll", &package), None);
164
165 assert_eq!(rule.map_file("UE4SS/file.dll", &package), None);
166
167 assert_eq!(
168 rule.map_file("UE4SS/Mods/file.dll", &package),
169 Some(Utf8PathBuf::from("shimloader/mod/file.dll"))
170 );
171
172 assert_eq!(
173 rule.map_file("UE4SS/Mods/nested/file.dll", &package),
174 Some(Utf8PathBuf::from("shimloader/mod/nested/file.dll"))
175 );
176
177 assert_eq!(
178 rule.map_file("UE4SS/Mods/nested/deep/file.dll", &package),
179 Some(Utf8PathBuf::from("shimloader/mod/nested/deep/file.dll"))
180 );
181 }
182}