use std::path::{Component, Path, PathBuf};
pub fn normalize<P: AsRef<Path>>(path: P) -> PathBuf {
let mut components = path.as_ref().components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(Component::RootDir);
}
Component::CurDir => {}
Component::ParentDir => {
if ret.ends_with(Component::ParentDir) {
ret.push(Component::ParentDir);
} else {
let popped = ret.pop();
if !popped && !ret.has_root() {
ret.push(Component::ParentDir);
}
}
}
Component::Normal(c) => {
ret.push(c);
}
}
}
ret
}
pub fn try_normalize<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
let mut ret = PathBuf::new();
for component in path.as_ref().components() {
match component {
Component::Prefix(..) | Component::RootDir => return None,
Component::CurDir => {}
Component::ParentDir => {
if !ret.pop() {
return None;
}
}
Component::Normal(c) => {
ret.push(c);
}
}
}
Some(ret)
}
pub fn normalize_sep<P: AsRef<Path>>(path: P, seq: &str) -> PathBuf {
let path = normalize(path);
let s = path
.into_os_string()
.into_string()
.expect("Failed to convert Path to String");
if s.contains("\\") {
return PathBuf::from(s.replace("\\", seq));
} else if s.contains("/") {
return PathBuf::from(s.replace("/", seq));
}
PathBuf::from(s).to_path_buf()
}
pub fn to_unix<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
#[cfg(unix)]
{
use std::os::unix::ffi::{OsStrExt, OsStringExt};
let bytes = path
.as_os_str()
.as_bytes()
.iter()
.map(|byte| if *byte == b'\\' { b'/' } else { *byte })
.collect();
return PathBuf::from(std::ffi::OsString::from_vec(bytes));
}
#[cfg(windows)]
{
use std::os::windows::ffi::{OsStrExt, OsStringExt};
let wide = path
.as_os_str()
.encode_wide()
.map(|unit| {
if unit == b'\\' as u16 {
b'/' as u16
} else {
unit
}
})
.collect::<Vec<_>>();
return PathBuf::from(std::ffi::OsString::from_wide(&wide));
}
#[cfg(not(any(unix, windows)))]
{
PathBuf::from(path.to_string_lossy().replace('\\', "/"))
}
}
pub fn normalize_unix<P: AsRef<Path>>(path: P) -> PathBuf {
let unix = to_unix(path);
to_unix(normalize(unix))
}
fn is_normalized(path: &Path) -> bool {
for component in path.components() {
match component {
Component::CurDir | Component::ParentDir => {
return false;
}
_ => continue,
}
}
true
}
pub trait PathExt: AsRef<Path> {
fn normalize(&self) -> PathBuf {
normalize(self.as_ref())
}
fn normalize_string(&self) -> Option<String> {
normalize(self.as_ref()).into_os_string().into_string().ok()
}
fn try_normalize(&self) -> Option<PathBuf> {
try_normalize(self.as_ref())
}
fn normalize_sep(&self, seq: &str) -> PathBuf {
normalize_sep(self.as_ref(), seq)
}
fn to_unix(&self) -> PathBuf {
to_unix(self.as_ref())
}
fn to_unix_string(&self) -> Option<String> {
Some(self.as_ref().to_str()?.replace('\\', "/"))
}
fn normalize_unix(&self) -> PathBuf {
normalize_unix(self.as_ref())
}
fn normalize_unix_string(&self) -> Option<String> {
normalize_unix(self.as_ref())
.into_os_string()
.into_string()
.ok()
}
fn is_normalized(&self) -> bool {
is_normalized(self.as_ref())
}
}
#[deprecated(since = "0.7.0", note = "use `PathExt` instead")]
pub use PathExt as NormalizePath;
impl PathExt for Path {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn converts_separators_without_normalizing_components() {
let path = Path::new(r"Music\Artist\.\Album\..\Track.mp3");
assert_eq!(
path.to_unix(),
PathBuf::from("Music/Artist/./Album/../Track.mp3")
);
}
#[test]
fn normalizes_windows_style_input_to_a_rekordbox_path() {
let path = Path::new(r"Music\Artist\.\Album\..\Track.mp3");
assert_eq!(
path.normalize_unix(),
PathBuf::from("Music/Artist/Track.mp3")
);
}
#[test]
fn converts_paths_to_strings() {
let path = Path::new(r"Music\Artist\.\Album\..\Track.mp3");
assert_eq!(
path.normalize_string(),
path.normalize().to_str().map(str::to_owned)
);
assert_eq!(
path.to_unix_string().as_deref(),
Some("Music/Artist/./Album/../Track.mp3")
);
assert_eq!(
path.normalize_unix_string().as_deref(),
Some("Music/Artist/Track.mp3")
);
}
#[cfg(unix)]
#[test]
fn string_conversions_reject_non_unicode_paths() {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
let path = PathBuf::from(OsString::from_vec(vec![b'M', 0xff]));
assert_eq!(path.normalize_string(), None);
assert_eq!(path.to_unix_string(), None);
assert_eq!(path.normalize_unix_string(), None);
}
#[test]
fn preserves_unix_roots_and_forward_slashes() {
let path = Path::new("/Contents/Artist/../Track.flac");
assert_eq!(path.to_unix(), path);
assert_eq!(path.normalize_unix(), PathBuf::from("/Contents/Track.flac"));
}
}