Summary
Add optional shellexpand feature for automatic Unix shell-style path expansion in soft_canonicalize().
Motivation
Users frequently pass shell-style paths from configs, CLI args, and user input. Currently requires manual expansion before calling soft_canonicalize.
Proposed API
[dependencies]
soft-canonicalize = { version = "0.4", features = ["shellexpand"] }
// With feature enabled, automatic expansion:
let path = soft_canonicalize("~/Documents/$PROJECT/file.txt")?;
// → /home/user/Documents/my-project/file.txt
// Without feature: tilde treated as literal path component
Supported Expansions (Unix only)
~ → /home/user
~/path → /home/user/path
~user/path → /home/user/path
$VAR, ${VAR} → environment variables
~+ → $PWD
~- → $OLDPWD
Critical Design Question: Non-Existing Users
Our library works with non-existing filesystem paths. Should it work with non-existing users?
soft_canonicalize("~alice/file.txt")
// What if user "alice" doesn't exist in /etc/passwd?
Options
Option 1: Error on unknown users ❌
- Contradicts "works with non-existing paths" philosophy
- Less useful for planning future multi-user systems
Option 2: Silent literal fallback ⚠️
~alice → ./~alice/file.txt if user doesn't exist
- Too magical, hard to debug
Option 3: Current user only 😐
- Expand
~ but not ~alice
- Inconsistent, feels incomplete
Option 4: Best-effort with heuristics ✅ Recommended
- Existing user → authoritative lookup
- Non-existing user → platform convention (
/home/alice or /Users/alice)
- Aligns with library philosophy: path may not exist, that's OK
- Clear documented behavior
Option 5: Configurable policy 🔧
- Add
ExpansionPolicy enum
- Maximum flexibility, more complex API
Option 6: Don't implement 🚫
- Users compose with
shellexpand crate themselves
- Maintains clear separation of concerns
Implementation Notes (Option 4)
fn expand_tilde_user(username: &str, rest: &str) -> io::Result<PathBuf> {
// Try system lookup first
if let Some(home) = lookup_user_home(username) {
return Ok(home.join(rest)); // Authoritative
}
// Fallback: platform conventions
#[cfg(target_os = "macos")]
let home = PathBuf::from("/Users").join(username);
#[cfg(not(target_os = "macos"))]
let home = PathBuf::from("/home").join(username);
Ok(home.join(rest))
}
Dependencies
With feature enabled:
shellexpand (~20KB) - may need fork/patch for best-effort behavior
dirs - home directory detection
bstr - byte string utilities
Total: ~3 small, well-maintained crates
Questions for Discussion
- Which option for non-existing users? (Recommendation: Option 4)
- Is best-effort expansion acceptable? Will
/home/alice assumption work?
- Implementation approach: DIY, fork shellexpand, or use as-is?
- Environment variables: Same policy as users or stricter?
- Windows: Feature doesn't compile on Windows (cfg-gated)?
- Should this be in the library at all? Or keep as separate concern?
Feedback needed: The non-existing user dilemma is critical. Which approach makes most sense for this library's philosophy?
Summary
Add optional
shellexpandfeature for automatic Unix shell-style path expansion insoft_canonicalize().Motivation
Users frequently pass shell-style paths from configs, CLI args, and user input. Currently requires manual expansion before calling
soft_canonicalize.Proposed API
Supported Expansions (Unix only)
~→/home/user~/path→/home/user/path~user/path→/home/user/path$VAR,${VAR}→ environment variables~+→$PWD~-→$OLDPWDCritical Design Question: Non-Existing Users
Our library works with non-existing filesystem paths. Should it work with non-existing users?
Options
Option 1: Error on unknown users ❌
Option 2: Silent literal fallback⚠️
~alice→./~alice/file.txtif user doesn't existOption 3: Current user only 😐
~but not~aliceOption 4: Best-effort with heuristics ✅ Recommended
/home/aliceor/Users/alice)Option 5: Configurable policy 🔧
ExpansionPolicyenumOption 6: Don't implement 🚫
shellexpandcrate themselvesImplementation Notes (Option 4)
Dependencies
With feature enabled:
shellexpand(~20KB) - may need fork/patch for best-effort behaviordirs- home directory detectionbstr- byte string utilitiesTotal: ~3 small, well-maintained crates
Questions for Discussion
/home/aliceassumption work?Feedback needed: The non-existing user dilemma is critical. Which approach makes most sense for this library's philosophy?