Skip to main content

pristine/
lib.rs

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