1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//! `pristine` finds reclaimable build artifacts and vendored dependency directories across
//! every ecosystem on a machine, and names what each one is before you delete it.
//!
//! The core is a single parallel walk that prunes at every directory it claims, driven by a
//! ruleset that lives in TOML rather than in code:
//!
//! ```no_run
//! use std::sync::Arc;
//! use pristine::{Ruleset, Walker};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let ruleset = Arc::new(Ruleset::load(None)?);
//! let (tree, outcome) = Walker::new("/Users/me/repos", ruleset).run_to_tree();
//! println!("{} directories, {} not yet priced", outcome.hits, tree.unmeasured());
//! # Ok(())
//! # }
//! ```
//!
//! A scan does not measure what it claims. Pruning at `node_modules` and then walking it to
//! size it would give back the cost the pruning saved, so sizes arrive as
//! [`Size::Unmeasured`] until a caller asks for a breakdown with
//! [`SizeMode::Breakdown`](size::SizeMode::Breakdown) — which costs an order of magnitude more
//! than the scan it prices. [`SizeMode::BreakdownUnder`](size::SizeMode::BreakdownUnder) buys
//! the same answer for one subtree at that subtree's price.
//!
//! When a breakdown is asked for, **the prices do not hold the claims up**. A claim is
//! published as [`Found::Claim`] the moment it is judged and a pool of threads prices it
//! afterwards, reporting [`Found::Priced`] for the same path; over one real `~/repos` that
//! moves the last row of the listing from 60.1 s to 7.5 s while the totals take the same
//! minute either way. [`Walker::run`] explains the shape and carries the measurements.
//!
//! Detection is marker-anchored and never name-anchored: a rule is "a directory named
//! `target` whose parent holds a `Cargo.toml`", never "a directory named `target`".
//! `target` is Rust's and Maven's, `vendor` is Go's and Composer's and Bundler's, and `build`
//! is Gradle's, Dart's and — in a CMake project — hand-written source. See [`rules`].
//!
//! A curated ruleset only ever covers the ecosystems somebody wrote a rule for, so there is a
//! second tier underneath it: inside a git work tree, a directory that is gitignored, holds no
//! tracked file and no git checkout at any depth, and clears a size floor is reclaimable by
//! inference even when no rule names it. It reports honestly that it does not know what the
//! directory is, and outside a work tree it is inert rather than guessing from directory
//! names. See [`fallback`].
//!
//! Removing what either tier found is [`delete`], and it is split in two on purpose. A
//! [`Planner`] resolves every path and applies every check in the safety model; a [`Deleter`]
//! executes the resulting [`Plan`] and decides nothing. That split is what makes a dry run
//! honest: the plan a preview prints is the same object the removal consumes.
//!
//! What a person actually uses to steer all of this is [`tui`]: the filesystem tree with the
//! reclaimable bytes rolled up into every ancestor, collapsed by default, where marking one
//! closed row covers everything beneath it and the batch it commits goes through the same
//! [`Planner`] and [`Deleter`].
//!
//! All of the above is the *sweep*: point it at a tree of unrelated projects and ask what is
//! reclaimable across all of them. The second mode is [`repo`], which points at one git
//! checkout and replaces `git clean -fdx`. It enumerates nothing itself — `git clean -n -d`
//! and `git clean -n -d -X` are the authority, so nested ignore files, negations,
//! `info/exclude` and global excludes are inherited exactly rather than reimplemented — and it
//! feeds the same [`Planner`] and [`Deleter`] as the sweep.
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;