Reputation: 35186
On Windows, Path::canonicalize()
returns the path in the format:
\\\\?\\C:\\projects\\3rdparty\\rust...
This is because it is the correct canonical path, and allows 'long' paths on Windows (see Why does my canonicalized path get prefixed with \\?\).
However, this is not a user-friendly path, and people do not understand it.
For display and logging purposes how can I easily remove this prefix in a generic platform independent way?
Path::components
will return a component \\?\C:
as the first component...
Should I convert this to a &str
and use a regex? Is there some other more ergonomic method for removing the prefix, e.g. some type with a Display
implementation that automatically does the right thing?
My requirements specifically are:
X:\\...
for a canonical path on Windows.Example:
use std::path::{Path, PathBuf};
fn simple_path<P: AsRef<Path>>(p: P) -> String {
String::from(p.as_ref().to_str().unwrap()) // <-- ?? What to do here?
}
pub fn main() {
let path = PathBuf::from("C:\temp").canonicalize().unwrap();
let display_path = simple_path(path);
println!("Output: {}", display_path);
}
Upvotes: 14
Views: 3863
Reputation: 100170
Use the dunce
crate:
let compatible_path = dunce::canonicalize(&any_path);
Just stripping \\?\
may give wrong/invalid paths. The dunce crate checks whether the UNC path is compatible and converts the path accurately whenever possible. It passes through all other paths. It compiles to plain fs::canonicalize()
on non-Windows.
Upvotes: 13
Reputation: 195
You can implement a StripCanonicalization
trait for PathBuf, which helps you keep a chainable calling.
pub trait StripCanonicalization where Self: AsRef<Path> {
#[cfg(not(target_os = "windows"))]
fn strip_canonicalization(&self) -> PathBuf {
self.as_ref().to_path_buf()
}
#[cfg(target_os = "windows")]
fn strip_canonicalization(&self) -> PathBuf {
const VERBATIM_PREFIX: &str = r#"\\?\"#;
let p = self.as_ref().display().to_string();
if p.starts_with(VERBATIM_PREFIX) {
PathBuf::from(&p[VERBATIM_PREFIX.len()..])
} else {
self.as_ref().to_path_buf()
}
}
}
impl StripCanonicalization for PathBuf {}
Calling like
let p = Path::new(s).canonicalize()?;
let p = p.strip_canonicalization();
Upvotes: -1
Reputation: 12547
Here's a version that reconstructs the path from the components.
It helps with std::fs::canonicalize
on Windows, but a naive Path::new(r"\\?\C:\projects\3rdparty\rust")
at play.rust-lang.org will produce a single-component Path
.
use std::path::{Component, Path, PathBuf, Prefix};
// Should remove the “\\?” prefix from the canonical path
// in order to avoid CMD bailing with “UNC paths are not supported”.
let head = path.components().next().ok_or("empty path?")?;
let diskˢ;
let head = if let Component::Prefix(prefix) = head {
if let Prefix::VerbatimDisk(disk) = prefix.kind() {
diskˢ = format!("{}:", disk as char);
Path::new(&diskˢ).components().next().ok_or("empty path?")?
} else {
head
}
} else {
head
};
println!("{:?}", head);
let path = std::iter::once(head)
.chain(path.components().skip(1))
.collect::<PathBuf>();
Upvotes: 1
Reputation: 431529
The straightforward answer is to do platform-specific string munging:
use std::path::{Path, PathBuf};
#[cfg(not(target_os = "windows"))]
fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
p.as_ref().display().to_string()
}
#[cfg(target_os = "windows")]
fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
const VERBATIM_PREFIX: &str = r#"\\?\"#;
let p = p.as_ref().display().to_string();
if p.starts_with(VERBATIM_PREFIX) {
p[VERBATIM_PREFIX.len()..].to_string()
} else {
p
}
}
pub fn main() {
let path = PathBuf::from(r#"C:\Windows\System32"#)
.canonicalize()
.unwrap();
let display_path = adjust_canonicalization(path);
println!("Output: {}", display_path);
}
For the record, I don't agree that your premise is a good idea. Windows Explorer handles these verbatim paths just fine, and I think users are capable of handling it as well.
For [...] logging purposes
This sounds like a terrible idea. If you are logging something, you want to know the exact path, not some potentially incorrect path.
Upvotes: 8