path-clean

Lexically normalize Rust file paths without touching the filesystem, Go-style.

Library
Cargo
v1.0.1
46stars
MIT OR Apache-2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
28/100Needs Attention
Development Activity0
Maintenance20
Community20
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
72/100Good
Architecture72
Code Quality82
Innovation78
Learning Curve55

path-clean is a small, dependency-free Rust crate that normalizes filesystem paths purely through lexical analysis, without ever touching the underlying filesystem. It ports the cleanname procedure from the Plan 9 C library, the same algorithm behind Go’s path.Clean, collapsing redundant slashes, eliminating . segments, and resolving .. segments against the path component that precedes them.

The crate exposes both a free clean() function and a PathClean trait implemented for std::path::Path and PathBuf, so callers can normalize a path with either a direct function call or a fluent .clean() method. Because the transform is purely lexical, it never resolves symlinks or absolute paths, making it predictable and safe to use on paths that don’t yet exist on disk.

What You Get

  • clean() function - A free function that takes any AsRef<Path> and returns a normalized PathBuf.
  • PathClean trait - Adds a .clean() method directly onto Path and PathBuf for fluent call sites.
  • Zero runtime dependencies - The core crate ships with no dependencies, keeping compile times and binary size minimal.
  • Deterministic lexical normalization - The same input path string always produces the same output, with no filesystem or symlink resolution involved.

Common Use Cases

  • Normalizing user-supplied paths - Cleaning paths built from CLI arguments or config files before comparing or displaying them.
  • Deduplicating path-based cache keys - Collapsing equivalent path strings (a/b/../c vs a/c) to the same canonical form for cache or map keys.
  • Preparing paths for display or logging - Removing redundant ./ and ../ noise from constructed paths before showing them to users.
  • Pre-processing paths before joining - Cleaning intermediate path segments assembled from multiple sources prior to further path manipulation.

Under The Hood

Architecture The entire crate is a single file (src/lib.rs, ~200 lines): one pure function, clean(), that walks a path’s Component iterator and accumulates the result into a Vec<Component>, discarding CurDir components and popping the last Normal component whenever a ParentDir is encountered (unless it would cross a root or another ..). The PathClean trait is a thin ergonomic wrapper implemented for both Path and PathBuf that simply delegates to clean(). There is no internal layering, no I/O, and no mutable state beyond the local accumulator, so the entire behavioral contract lives in one function and changing it affects exactly one file.

Tech Stack Pure Rust using only std::path (Component, Path, PathBuf) with zero runtime dependencies. The only dev-dependency is criterion 0.2, used for a micro-benchmark in benches/clean_benchmark.rs. CI (GitHub Actions) runs cargo check/cargo test across a stable/nightly x ubuntu/windows/macOS matrix, plus cargo clippy -D warnings, cargo fmt --check, and cargo doc --no-deps, with RUSTFLAGS=-Dwarnings turning all compiler warnings into build failures. The crate ships on crates.io with docs auto-published to docs.rs.

Code Quality Tests are comprehensive, table-driven unit tests embedded in a #[cfg(test)] mod tests block, covering empty paths, repeated slashes, . elimination, multiple .. elimination scenarios, both trait implementations, and a dedicated #[cfg(target_os = "windows")] block for Windows-specific separators and drive prefixes. There’s no separate integration-test directory, but the core algorithm’s edge cases are well covered. #![forbid(unsafe_code)] guarantees the crate contains no unsafe blocks, and CI enforces clippy and rustfmt on every push, so style and lint drift can’t accumulate silently.

API Design The public API is deliberately minimal: one free function plus a trait offering a fluent .clean() method, mirroring the ergonomics Rust developers already expect from extension traits like str::trim(). A runnable example lives directly in the crate-level doc comment, so a consumer can go from cargo add path-clean to working code in under a minute with zero configuration. The tradeoff for that simplicity is scope: the crate does exactly one thing and makes no attempt to become a general path-manipulation toolkit, which keeps the entire API surface learnable at a glance.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search