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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! pathlint library — verifies that commands on PATH resolve from the expected installer.
//!
//! # Public API surface (0.0.15+)
//!
//! The supported library surface is **ten modules**, each
//! described below by a few representative symbols. The
//! authoritative contract is `tests/public_api.rs`, which
//! imports every symbol pathlint promises here and fails the
//! build if any is moved or removed.
//!
//! - [`config`]: TOML schema. Headlines: `Config`, `Expectation`,
//! `SourceDef`, `Relation`, `Severity`, `Kind`.
//! - [`lint`]: core PATH evaluation. Headlines: `evaluate`,
//! `exit_code`, `Outcome`, `Status`, `Diagnosis`,
//! `CheckOutcomeView`. Resolver closures take
//! `&str -> Option<std::path::PathBuf>` (0.0.16+).
//! - [`trace`]: provenance lookup. Headlines: `locate`,
//! `TraceOutcome`, `Found`, `Provenance`, `UninstallHint`.
//! - [`sort`]: PATH repair proposals. Headlines: `sort_path`,
//! `SortPlan`, `EntryMove`, `SortNote`.
//! - [`doctor`]: PATH hygiene. Headlines: `analyze`,
//! `analyze_real`, `fs_list_dir_real` (the production wrapper
//! for the 0.0.19 `fs_list_dir` closure parameter),
//! `Diagnostic`, `Filter`, plus the `Kind` / `Severity` enums.
//! - [`catalog`]: built-in source catalog. Headlines: `builtin`,
//! `builtin_relations`, `merge_with_user`,
//! `merge_with_user_relations`, `check_acyclic`,
//! `version_check`, `embedded_version`.
//! - [`source_match`]: path → source matching. Headlines: `find`,
//! `names_only`, `validate_sources`, `Match`, `SourceWarning`.
//! - [`os_detect`]: runtime OS dispatch. Headlines: `Os`,
//! `os_filter_applies`.
//! - [`expand`]: env-var expansion + slash normalisation.
//! Headlines: `expand_env`, `normalize`, `expand_and_normalize`.
//! - [`path_entry`]: PATH entry duality (raw + expanded). Headlines:
//! `PathEntry`, `PathEntry::from_raw`. 0.0.23+.
//!
//! Anything not exported through one of those modules — including
//! the CLI plumbing, presentation layer, registry reader, and
//! orchestration glue — is **internal** and may change between
//! patch releases without notice.
//!
//! Across 0.0.x the library surface is treated as additive-only
//! best-effort; intentional breaks land at `0.0.x → 0.0.(x+1)`
//! boundaries (Cargo's MAJOR-equivalent for `0.0.y`) and are
//! flagged in release notes.
//!
//! # Quick example
//!
//! Evaluate one expectation against an in-process PATH without
//! reading a `pathlint.toml` from disk:
//!
//! ```
//! use pathlint::config::{Config, Expectation, Severity};
//! use pathlint::lint;
//! use pathlint::os_detect::Os;
//! use std::path::PathBuf;
//!
//! // Caller-supplied resolver. Production wiring would use a
//! // `which`-style PATH walk; this stub pretends `rg` lives in
//! // `~/.cargo/bin`. The closure boundary intentionally takes
//! // and returns standard-library types only — pathlint never
//! // exposes its own internal resolver type to embedders.
//! let resolver = |cmd: &str| -> Option<PathBuf> {
//! match cmd {
//! "rg" => Some(PathBuf::from("/home/me/.cargo/bin/rg")),
//! _ => None,
//! }
//! };
//!
//! let cfg = Config::default();
//! let expectations = vec![Expectation {
//! command: "rg".into(),
//! prefer: vec!["cargo".into()],
//! avoid: vec![],
//! os: None,
//! optional: false,
//! kind: None,
//! severity: Severity::Error,
//! }];
//!
//! let sources = pathlint::catalog::merge_with_user(&cfg.source);
//! let deps = lint::EvaluateDeps {
//! common: pathlint::CommonDeps { env_lookup: Box::new(|_v: &str| -> Option<String> { None }) },
//! resolver: Box::new(resolver),
//! shape_check: Box::new(lint::check_shape_filesystem), // R2 shape check; unused here
//! };
//! let outcomes = lint::evaluate(&expectations, &sources, Os::current(), deps);
//!
//! // outcomes[0].status is Status::Ok because the resolver picked
//! // a path under cargo's built-in source.
//! assert_eq!(outcomes.len(), 1);
//! ```
//!
//! For the full pipeline (read `pathlint.toml`, walk `$PATH`,
//! print to stdout, set the exit code) see the binary at
//! `src/bin/pathlint/run.rs` — the library is shaped so the binary
//! is a thin orchestration on top of the same primitives.
// Internal modules — `#[doc(hidden)] pub` for the ones the
// `pathlint` binary in `src/bin/pathlint/` needs to call across
// the lib/bin boundary, `pub(crate)` for everything strictly
// internal to the lib. Neither tier appears on docs.rs and
// neither is part of the library contract.
//
// 0.0.17 moved `cli` and `run` out of the lib entirely; they now
// live in `src/bin/pathlint/` alongside `main.rs`. The binary
// still consumes some lib internals (formatter, presentation,
// init template, OS PATH reader, command resolver) — those stay
// `#[doc(hidden)] pub` so Cargo can route the call across the
// crate boundary without putting them on the public surface.
pub
/// Shared dependency carrier embedded in every per-function `*Deps`
/// (see [`doctor::AnalyzeDeps`], [`lint::EvaluateDeps`],
/// [`trace::LocateDeps`], [`sort::SortDeps`]).
///
/// Today this only holds `env_lookup`, but the four
/// per-function carriers all reach for the same env oracle, so
/// concentrating it here keeps the "how does pathlint read the
/// process env?" decision in a single place. Future cross-cutting
/// concerns (logging hook, deterministic clock, etc.) belong here
/// too.
///
/// Production callers usually start from [`CommonDeps::production`]
/// and pass the resulting value into one of the per-function
/// carriers. Embedders supplying a deterministic env oracle
/// construct the struct directly.
///
/// 0.0.27+.
/// `Fn(&str) -> Option<String>` env oracle, type-erased through a
/// trait object. Used as the type of `CommonDeps::env_lookup` and
/// (transitively) every per-function `*Deps`. The `'a` lifetime lets
/// the closure borrow from the test stack; production wiring uses
/// `'static`. 0.0.27+.
pub type EnvLookupFn<'a> = ;