#![forbid(unsafe_code)]
#![forbid(missing_docs)]
#![forbid(clippy::missing_errors_doc)]
use std::{ops::Index, path::{Path, PathBuf}};
#[path = "pathconv.rs"]
mod pathconv;
pub fn canonicalize(path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
let path = path.as_ref();
match path.is_relative() {
true => match std::env::current_dir() {
Ok(current_dir) => Ok(current_dir.join(path)),
Err(e) => Err(e),
},
false => Ok(path.to_path_buf()),
}
}
macro_rules! make_pub_posix2 {
($(#[$meta:meta])* $vis:vis fn $name:ident $args:tt -> $ret:ty $body:block) => {
#[cfg(feature = "posix")]
$(#[$meta])*
pub fn $name $args -> $ret $body
#[cfg(any(test,all(target_family = "unix", not(feature = "posix"))))]
fn $name $args -> $ret $body
};
}
macro_rules! make_pub_win2 {
($(#[$meta:meta])* $vis:vis fn $name:ident $args:tt -> $ret:ty $body:block) => {
#[cfg(feature = "windows")]
$(#[$meta])*
pub fn $name $args -> $ret $body
#[cfg(any(test,all(target_os = "windows", not(feature = "windows"))))]
fn $name $args -> $ret $body
};
}
pub fn normalize(path: impl AsRef<Path>) -> PathBuf {
#[cfg(target_family = "windows")]
{ normalize_windows(path) }
#[cfg(target_family = "unix")]
{ normalize_posix(path) }
}
make_pub_posix2!{
pub fn normalize_posix(path: impl AsRef<Path>) -> PathBuf {
let mut p = pathconv::path_to_vector(path);
remove_double_separators(&mut p, '/');
remove_start_dot_slash(&mut p, '/');
remove_slash_dot_slash(&mut p, '/');
remove_end_dot(&mut p, '/');
resolve_end_updir(&mut p, '/');
resolve_updirs(&mut p, '/');
pathconv::vector_to_pathbuf(p)
}
}
make_pub_win2!{
fn normalize_windows(path: impl AsRef<Path>) -> PathBuf {
let mut p: Vec<char> = pathconv::path_to_vector(path);
replace_slashes(&mut p, '/', '\\');
unc_remove_extra_backslashes(&mut p);
normalize_unc(&mut p);
normalize_drive(&mut p);
let prefix_len = windows_prefix_len(&p);
let prefix: Vec<char> =
if prefix_len > 0 {
p.drain(..prefix_len).collect()
} else {
Vec::<char>::new()
};
remove_start_dot_slash(&mut p, '\\');
remove_slash_dot_slash(&mut p, '\\');
remove_end_dot(&mut p, '\\');
resolve_end_updir(&mut p, '\\');
resolve_updirs(&mut p, '\\');
p.splice(..0, prefix);
pathconv::vector_to_pathbuf(p)
}
}
pub fn is_absolute(path: impl AsRef<Path>) -> bool {
#[cfg(target_family = "windows")]
{ is_absolute_windows(path) }
#[cfg(target_family = "unix")]
{ is_absolute_posix(path) }
}
make_pub_posix2!{
fn is_absolute_posix(path: impl AsRef<Path>) -> bool {
if let Some(first_char) = path.as_ref().to_string_lossy().chars().next() {
if first_char == '/' {
true
} else {
false
}
} else {
false
}
}
}
make_pub_win2!{
fn is_absolute_windows(path: impl AsRef<Path>) -> bool {
fn any_slash(c: char) -> bool {
c == '/' || c == '\\'
}
#[allow(non_snake_case)]
fn charAt(s: impl AsRef<str>, pos: usize) -> char {
s.as_ref().chars().nth(pos).unwrap()
}
let str = path.as_ref().to_string_lossy();
if str.len() >=3 {
if any_slash(charAt(&str, 0)) && any_slash(charAt(&str, 1)) &&
( charAt(&str,2).is_ascii_alphabetic() || any_slash(charAt(&str, 2)) ) {
true
} else if charAt(&str,0).is_ascii_alphabetic() && charAt(&str,1) == ':' && any_slash(charAt(&str, 2)) {
true
} else {
false
}
} else {
false
}
}
}
pub fn join(base: impl AsRef<Path>, append: impl AsRef<Path>) -> PathBuf {
#[cfg(target_family = "windows")]
{ join_windows(base, append) }
#[cfg(target_family = "unix")]
{ join_posix(base, append) }
}
make_pub_posix2!{
fn join_posix(base: impl AsRef<Path>, append: impl AsRef<Path>) -> PathBuf {
if is_absolute_posix(&append) {
normalize_posix(append)
} else if append.as_ref().as_os_str().is_empty() {
normalize_posix(base)
} else
{
let mut joined = pathconv::path_to_vector(base);
joined.push('/');
joined.append(&mut pathconv::path_to_vector(append));
normalize_posix(pathconv::vector_to_pathbuf(joined))
}
}
}
make_pub_win2!{
fn join_windows(base: impl AsRef<Path>, append: impl AsRef<Path>) -> PathBuf {
if is_absolute_windows(&append) {
normalize_windows(append)
} else if append.as_ref().as_os_str().is_empty() {
normalize_windows(base)
} else
{
let mut base1 = pathconv::path_to_vector(&base);
let mut append2 = pathconv::path_to_vector(&append);
let havedrive2 = have_drive(&append2);
if havedrive2 == false {
base1.push('\\');
base1.append(&mut append2);
normalize_windows(pathconv::vector_to_pathbuf(base1))
} else {
let havedrive1 = have_drive(&base1);
if havedrive1 == true {
normalize_drive(&mut base1);
normalize_drive(&mut append2);
if base1[0] == append2[0] {
append2.drain(0..2);
base1.push('\\');
base1.append(&mut append2);
} else {
base1.clear();
base1.append(&mut append2);
}
normalize_windows(pathconv::vector_to_pathbuf(base1))
} else {
append2.drain(0..2);
base1.push('\\');
base1.append(&mut append2);
normalize_windows(pathconv::vector_to_pathbuf(base1))
}
}
}
}
}
fn unc_remove_extra_backslashes(path: &mut Vec<char>) {
if path.len() >= 1 {
if let Some(&first) = path.first() {
if first == '\\' {
path.remove(0);
remove_double_separators(path, '\\');
path.insert(0, '\\');
} else {
remove_double_separators(path, '\\');
}
}
}
}
fn normalize_unc(path: &mut Vec<char>) {
if path.len() >= 3 {
if path[0] == '\\' && path[1] == '\\' {
let mut slash_count = 0;
for ch in path.iter_mut().skip(2) {
if *ch == '\\' {
slash_count += 1;
} else if ch.is_ascii_uppercase() {
*ch = ch.to_ascii_lowercase();
};
if slash_count == 2 {
return;
};
}
}
}
}
fn normalize_drive(path: &mut Vec<char>) {
if path.len() >= 2 {
if path[1] == ':' {
if path[0].is_ascii_lowercase() {
path[0] = path[0].to_ascii_uppercase();
}
}
}
}
fn have_drive(path: & Vec<char>) -> bool {
if path.len()>= 2 {
if path[0].is_ascii_alphabetic() && path[1] == ':' {
true
} else {
false
}
} else {
false
}
}
fn remove_double_separators(path: &mut Vec<char>, separator: char) {
let mut i = 0;
let mut previous = false;
while i < path.len() {
if *path.index(i) == separator {
if previous {
path.remove(i);
i -= 1;
} else {
previous = true;
}
} else {
previous = false;
}
i += 1;
}
}
fn replace_slashes(path: &mut Vec<char>, from: char, to: char) {
for c in path.iter_mut() {
if *c == from {
*c = to;
}
}
}
fn remove_start_dot_slash(path: &mut Vec<char>, separator: char) {
while path.len() >= 2 {
if path[0] == '.' && path[1] == separator {
path.drain(0..2);
} else {
return
}
}
}
fn remove_slash_dot_slash(path: &mut Vec<char>, separator: char) {
while let Some(pos) = path.windows(3).position(|f| f[0] == separator && f[1] == '.' && f[2] == separator ) {
path.drain(pos..pos+2);
}
}
fn remove_end_dot(path: &mut Vec<char>, separator: char) {
if path.len() >= 2 {
if path[path.len() - 2] == separator && path[path.len() - 1] == '.' {
path.pop();
}
}
}
fn resolve_updirs(path: &mut Vec<char>, separator: char) {
while let Some(pos) = path.windows(4).position(
|f| f[0] == separator && f[1] == '.' && f[2] == '.' && f[3] == separator ) {
let frontal = &path[0..pos];
if let Some(index) = frontal.iter().rposition(|&x| x == separator) {
path.drain(index + 1..pos+4);
} else {
if pos == 0 {
path.drain(1..pos+4);
} else {
path.drain(0..pos+4);
}
}
}
}
fn resolve_end_updir(path: &mut Vec<char>, separator: char) {
if path.len() >= 3 {
if path[path.len() -3] == separator && path[path.len() -2] == '.' && path[path.len() -1] == '.' {
if path.len() == 3 {
path.drain(1..3);
} else {
let truncated_path = &path[..path.len() - 3];
if let Some(index) = truncated_path.iter().rposition(|&x| x == separator) {
path.drain(index + 1..);
} else {
path.clear();
}
}
}
}
}
fn windows_prefix_len(path: &Vec<char>) -> usize {
if path.len() >=2 {
if path[1] == ':' {
2
} else if path[0] == '\\' && path[1] == '\\' {
1
} else {
0
}
} else {
0
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod test_normalizefs;
#[cfg(test)]
#[path = "normalize_posix_tests.rs"]
mod normalize_posix_tests;
#[cfg(test)]
#[path = "normalize_windows_tests.rs"]
mod normalize_windows_tests;
#[cfg(test)]
#[path = "absolute_posix_tests.rs"]
mod absolute_posix_tests;
#[cfg(test)]
#[path = "absolute_windows_tests.rs"]
mod absolute_windows_tests;
#[cfg(test)]
#[path = "join_posix_tests.rs"]
mod join_posix_tests;
#[cfg(test)]
#[path = "join_windows_tests.rs"]
mod join_windows_tests;
#[cfg(test)]
#[path = "double_separator_tests.rs"]
mod double_separator_tests;
#[cfg(test)]
#[path = "start_dot_slash_tests.rs"]
mod start_dot_slash_tests;
#[cfg(test)]
#[path = "end_dot_tests.rs"]
mod end_dot_tests;
#[cfg(test)]
#[path = "slash_dot_slash_tests.rs"]
mod slash_dot_slash_tests;
#[cfg(test)]
#[path = "end_updir_tests.rs"]
mod end_updir_tests;
#[cfg(test)]
#[path = "updirs_tests.rs"]
mod resolve_updirs_tests;
#[cfg(test)]
#[path = "replace_slashes_tests.rs"]
mod replace_slashes_tests;
#[cfg(test)]
#[path = "unc_extra_backslashes_tests.rs"]
mod unc_extra_backslashes_tests;
#[cfg(test)]
#[path = "normalize_unc_tests.rs"]
mod normalize_unc_tests;
#[cfg(test)]
#[path = "normalize_drive_tests.rs"]
mod normalize_drive_tests;
#[cfg(test)]
#[path = "windows_prefix_tests.rs"]
mod windows_prefix_tests;
#[cfg(test)]
#[path = "have_drive_tests.rs"]
mod have_drive_tests;