putzen_cli/caches/
defaults.rs1#[macro_export]
11macro_rules! roots {
12 (@build [$($acc:tt)*]) => { &[ $($acc)* ] };
14
15 (@build [$($acc:tt)*]
17 #[doc = $_a:literal] #[doc = $_b:literal] $path:literal $(, $($rest:tt)*)?
18 ) => {
19 compile_error!(concat!(
20 "putzen: cache root \"", $path, "\" has multiple `///` lines. ",
21 "Use exactly one — extend prose with plain `//` instead."
22 ));
23 };
24
25 (@build [$($acc:tt)*]
27 #[doc = $label:literal] $path:literal $(, $($rest:tt)*)?
28 ) => {
29 roots!(@build [
30 $($acc)*
31 $crate::caches::defaults::DefaultRoot {
32 label: $crate::caches::defaults::strip_leading_spaces($label),
33 path: $path,
34 },
35 ] $($($rest)*)?)
36 };
37
38 (@build [$($acc:tt)*] $path:literal $(, $($rest:tt)*)?) => {
40 compile_error!(concat!(
41 "putzen: cache root \"", $path, "\" is missing its `///` label. ",
42 "Add a one-line `///` doc comment above this entry."
43 ));
44 };
45
46 ( $($t:tt)* ) => { roots!(@build [] $($t)*) };
48}
49
50pub struct DefaultRoot {
51 pub label: &'static str,
52 pub path: &'static str, }
54
55pub const fn strip_leading_spaces(s: &'static str) -> &'static str {
63 let bytes = s.as_bytes();
64 let mut i = 0;
65 while i < bytes.len() && bytes[i] == b' ' {
66 i += 1;
67 }
68 s.split_at(i).1
70}
71
72pub const fn _missing_doc_check() {}
80
81pub const fn _multi_doc_check() {}
91
92pub const SEEDS: &[DefaultRoot] = roots![
94 ".cargo/registry/cache",
97 ".cargo/registry/src",
99 ".cargo/registry/index",
101 ".cargo/git/checkouts",
103 ".cargo/git/db",
105 "go/pkg/mod",
108 ".m2/repository",
110 ".gradle/caches",
112 ".gradle/wrapper/dists",
114 ".ivy2/cache",
116 ".sbt/boot",
118 ".nuget/packages",
120 ".hex/packages",
123 ".opam/download-cache",
125 ".gitlibs",
127 ".cabal/packages",
129 ".ollama/models",
132 ".triton/cache",
134 ".nv/ComputeCache",
136];
137
138#[cfg(target_family = "unix")]
139pub const SEEDS_OS: &[DefaultRoot] = roots![
140 ".cache",
142 "Library/Caches",
144 "Library/Developer/Xcode/DerivedData",
146 "Library/Developer/Xcode/Archives",
148 "Library/Developer/Xcode/iOS DeviceSupport",
150 "Library/Developer/CoreSimulator/Caches",
152 ".npm",
154 ".yarn/cache",
156 ".bun/install/cache",
158 ".pnpm-store",
160 ".cache/sccache",
162 "Library/Caches/Mozilla.sccache",
164];
165
166#[cfg(target_family = "windows")]
167pub const SEEDS_OS: &[DefaultRoot] = roots![
168 "AppData/Local/Microsoft/Windows/INetCache",
170 "AppData/Local/Microsoft/Edge/User Data/Default/Cache",
172 "AppData/Local/Google/Chrome/User Data/Default/Cache",
174 "AppData/Roaming/npm-cache",
176 "AppData/Local/Yarn/Cache",
178 "AppData/Local/pnpm",
180 "AppData/Local/pip/Cache",
182 "AppData/Local/uv/cache",
184 "AppData/Local/huggingface",
186 "AppData/Local/go-build",
188 "AppData/Local/JetBrains",
190 "AppData/Roaming/Code/CachedData",
192 "AppData/Local/Mozilla/sccache",
194];
195
196pub fn defaults() -> impl Iterator<Item = &'static DefaultRoot> {
197 SEEDS.iter().chain(SEEDS_OS.iter())
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn macro_emits_label_and_path() {
206 const SAMPLE: &[DefaultRoot] = roots![
207 ".cargo/registry/cache",
209 ];
210 assert_eq!(SAMPLE.len(), 1);
211 assert_eq!(SAMPLE[0].label, "cargo registry");
212 assert_eq!(SAMPLE[0].path, ".cargo/registry/cache");
213 }
214
215 #[test]
216 fn macro_emits_multiple_entries() {
217 const SAMPLE: &[DefaultRoot] = roots![
218 ".a",
220 ".b",
222 ".c",
224 ];
225 let labels: Vec<_> = SAMPLE.iter().map(|r| r.label).collect();
226 let paths: Vec<_> = SAMPLE.iter().map(|r| r.path).collect();
227 assert_eq!(labels, ["alpha", "beta", "gamma"]);
228 assert_eq!(paths, [".a", ".b", ".c"]);
229 }
230
231 #[test]
232 fn label_has_no_leading_space() {
233 const SAMPLE: &[DefaultRoot] = roots![
234 ".x",
236 ];
237 assert_eq!(SAMPLE[0].label, "no leading space");
238 assert!(!SAMPLE[0].label.starts_with(' '));
239 }
240
241 #[test]
242 fn defaults_contains_cargo_registry() {
243 assert!(defaults().any(|r| r.path == ".cargo/registry/cache"));
244 }
245
246 #[test]
247 fn defaults_has_no_empty_labels() {
248 for r in defaults() {
249 assert!(!r.label.is_empty(), "empty label for {}", r.path);
250 }
251 }
252
253 #[test]
254 fn defaults_paths_are_unique() {
255 let mut seen = std::collections::HashSet::new();
256 for r in defaults() {
257 assert!(seen.insert(r.path), "duplicate path {}", r.path);
258 }
259 }
260}