Host-native lexical path manipulation for Rust, as extension methods on standard path and string types.
SugarPath adds normalization, absolutization, relative paths, and slash conversion without a wrapper path type. Path-producing methods accept the full native Path domain, including non-UTF-8 paths. Conversion to str or String makes the Unicode policy explicit.
cargo add sugar_path@3CI and local development use the toolchain pinned in rust-toolchain.toml. The crate does not currently declare a separate minimum supported Rust version.
use std::path::{Path, PathBuf};
use sugar_path::{SugarPath, SugarPathBuf};
let input = PathBuf::from("workspace")
.join("src")
.join("..")
.join("dist")
.join("assets");
let normalized = input.normalize();
let expected = Path::new("workspace").join("dist").join("assets");
assert_eq!(&*normalized, expected);
// The receiver is the target: target.relative(base).
let relative = normalized.relative("workspace");
assert_eq!(&*relative, Path::new("dist").join("assets"));
let portable = relative.into_owned().into_slash();
assert_eq!(portable, "dist/assets");Import SugarPath for borrowed operations on Path, str, and values that dereference to them. Import SugarPathBuf for consuming operations that may reuse an owned PathBuf.
| Task | Borrowed / non-consuming | Consuming PathBuf |
Notes |
|---|---|---|---|
| Normalize | normalize() |
into_normalized() |
Cow<Path> or PathBuf |
| Make absolute | absolutize(), try_absolutize(), absolutize_with() |
— | ambient panic · io::Result · explicit cwd |
| Make relative | relative(), try_relative(), relative_with() |
— | receiver is the target; returns Cow<Path> |
| Convert separators | to_slash(), try_to_slash(), to_slash_lossy() |
into_slash(), try_into_slash(), into_slash_lossy() |
strict · recoverable · lossy Unicode |
| View text as a path | as_path() |
— | borrowed &Path |
PathBuf and String reach SugarPath methods through normal deref method lookup. Both traits are sealed extension-method namespaces; they are not intended for downstream implementations.
For the full contract of each method — including panic conditions, Windows edge cases, and ownership — see the crate documentation.
SugarPath rewrites path components only. It does not touch the filesystem, check existence, or resolve symbolic links. Removing .. lexically is not a security boundary and does not prove filesystem containment. Use std::fs::canonicalize when you need physical filesystem identity.
normalize() removes . components and redundant native separators, resolves .. against preceding normal components, and preserves one trailing separator on a non-root path. An empty path normalizes to ..
Parsing always follows the compilation target's std::path rules. There is no caller-selected POSIX/Windows mode, and Windows syntax is not parsed on Unix. On Unix, \ is an ordinary path byte. On Windows, / is normally a separator, but / inside a verbatim path component is literal and is left unchanged.
Call relative() as target.relative(base). Equal paths return an empty path. A target's non-root trailing separator is removed. A clean descendant may borrow its suffix from the target; upward, rewritten, or differently rooted results are owned.
Ambient cwd is used only when the inputs do not determine the answer themselves. On Windows, paths on different drives, UNC shares, or namespace roots cannot be connected by a native relative path, so the normalized target is returned instead. Drive-relative, root-relative, verbatim, and unrepresentable-component cases are documented on the method.
absolutize() and relative() are convenient ambient methods. They panic only when the calculation needs ambient path resolution and that resolution fails. try_absolutize() and try_relative() return the underlying io::Error instead.
absolutize_with() and relative_with() take an explicit absolute cwd and never read process cwd. They accept a borrowed path or an owned PathBuf; an owned value may supply reusable result storage. An explicit cwd is validated only when the operation actually needs it.
Slash conversion changes only the target platform's main separator. It does not normalize components or interpret foreign-platform syntax.
| Policy | Borrowed | Consuming | Invalid native encoding |
|---|---|---|---|
| Strict | to_slash() |
into_slash() |
panics |
| Recoverable | try_to_slash() |
try_into_slash() |
None or original PathBuf |
| Lossy | to_slash_lossy() |
into_slash_lossy() |
inserts U+FFFD; may not round-trip |
Use strict conversion when valid UTF-8 is an invariant, recoverable conversion when the native path must be kept, and lossy conversion only when replacement is intentional.
Borrowed Cow results never borrow from a base or cwd argument. They normally borrow from the receiver; normalization may also return the static current-directory path .. Already-normalized paths and clean relative descendants can avoid a result allocation. Call .into_owned() only when an owned PathBuf is required.
SugarPathBuf consumes PathBuf where ownership can avoid a copy. Storage reuse is an optimization: do not rely on the result keeping the same address or capacity. For a final slash-separated String, the ordinary composition is:
use std::path::Path;
use sugar_path::{SugarPath, SugarPathBuf};
let target = Path::new("workspace/src/lib.rs");
let base = Path::new("workspace");
let output = target.relative(base).into_owned().into_slash();
assert_eq!(output, "src/lib.rs");Allocation-sensitive behavior is gated in CI on Linux and Windows via cargo allocs (always under cached_current_dir). See the benchmark and allocation methodology.
Default features are empty.
| Feature | Purpose |
|---|---|
cached_current_dir |
Lazily cache the first successful process cwd lookup for apps that treat cwd as process-lifetime state |
codspeed |
Maintainer-only benchmark instrumentation; do not enable in applications |
sugar_path = { version = "3", features = ["cached_current_dir"] }With cached_current_dir, later std::env::set_current_dir calls are not observed. Absolute and other cwd-independent operations do not initialize the cache. Failed lookups are not cached. Explicit-cwd methods remain independent. Windows drive-relative paths still use authoritative per-drive cwd resolution.
CI tests Ubuntu, macOS, and Windows with default features and with all features. Semantics stay host-native:
- Unix paths keep arbitrary native bytes outside Unicode conversion.
- Windows normalization emits native separators and preserves drive-letter spelling.
- Windows drive, root, and normal-component comparison is ASCII case-insensitive, not general Unicode case folding.
- Windows drive, UNC, verbatim, and device namespaces remain distinct roots.
Version 3 is a breaking API revision:
relative()returnsCow<Path>; call.into_owned()where aPathBufis required.to_slash()is the strict direct-returning conversion; usetry_to_slash()to preserve failure.SugarPathBufprovides consuming normalization and slash conversion.- Explicit-cwd methods accept borrowed or owned cwd values directly.
See the changelog migration section for the full checklist.
Licensed under the MIT License.