use std::{borrow::Cow, ops::Add, path::PathBuf};
use crate::Parsed;
use super::shared::{
format_inner, is_posix_path_separator, normalize_string, CHAR_DOT, CHAR_FORWARD_SLASH,
};
#[allow(non_upper_case_globals)]
pub const sep: char = '/';
#[allow(non_upper_case_globals)]
pub const delimiter: char = ':';
#[inline]
pub fn basename_impl(path: &str) -> String {
basename_impl_without_ext(path, "")
}
pub fn basename_impl_without_ext(path: &str, ext: &str) -> String {
let mut start = 0;
let mut end = -1;
let mut matched_slash = true;
let path = path.chars().collect::<Vec<char>>();
let ext = ext.chars().collect::<Vec<char>>();
if ext.len() > 0 && ext.len() <= path.len() {
if ext == path {
return "".to_owned();
}
let mut ext_idx = ext.len() as i32 - 1;
let mut first_non_slash_end = -1;
let mut i = path.len() as i32 - 1;
while i >= 0 {
let code = path.get(i as usize).unwrap();
if code == &CHAR_FORWARD_SLASH {
if !matched_slash {
start = i + 1;
break;
}
} else {
if first_non_slash_end == -1 {
matched_slash = false;
first_non_slash_end = i + 1;
}
if ext_idx >= 0 {
if code == ext.get(ext_idx as usize).unwrap() {
ext_idx -= 1;
if ext_idx == -1 {
end = i;
}
} else {
ext_idx = -1;
end = first_non_slash_end;
}
}
}
i -= 1;
}
if start == end {
end = first_non_slash_end
} else if end == -1 {
end = path.len() as i32
}
return path[start as usize..end as usize].iter().collect();
}
let mut i = path.len() as i32 - 1;
while i >= 0 {
if path.get(i as usize).unwrap() == &CHAR_FORWARD_SLASH {
if !matched_slash {
start = i + 1;
break;
}
} else if end == -1 {
matched_slash = false;
end = i + 1;
}
i -= 1;
}
if end == -1 {
return "".to_owned();
}
return path[start as usize..end as usize].iter().collect();
}
#[macro_export]
macro_rules! basename {
( $x:expr ) => {{
$crate::posix::basename_impl($x)
}};
( $x:expr, $y:expr ) => {{
$crate::posix::basename_impl_without_ext($x, $y)
}};
}
pub use basename;
pub fn dirname(path: &str) -> String {
if path.len() == 0 {
".".to_owned()
} else {
let path = path.chars().collect::<Vec<char>>();
let has_root = path
.iter()
.next()
.map(|c| c == &CHAR_FORWARD_SLASH)
.unwrap_or(false);
let mut end = -1;
let mut matched_slash = true;
let mut i = path.len() as i32 - 1;
while i >= 1 {
if path
.get(i as usize)
.map(|c| c == &CHAR_FORWARD_SLASH)
.unwrap_or(false)
{
if !matched_slash {
end = i;
break;
}
} else {
matched_slash = false;
}
i -= 1;
}
if end == -1 {
if has_root {
"/".to_owned()
} else {
".".to_owned()
}
} else if has_root && end == 1 {
"//".to_owned()
} else {
path[0..end as usize].iter().collect()
}
}
}
pub fn extname(path: &str) -> String {
parse(path).ext
}
pub fn format(path_object: Parsed) -> String {
format_inner("/", path_object)
}
pub fn is_absolute(path: &str) -> bool {
path.bytes().next().map(|c| c == b'/').unwrap_or(false)
}
#[macro_export]
macro_rules! join {
( $( $x:expr ),* ) => {
{
$crate::posix::join_impl(&[
$(
$x,
)*
])
}
};
}
pub use join;
pub fn join_impl(args: &[&str]) -> String {
if args.len() == 0 {
".".to_owned()
} else {
let joined = args
.iter()
.filter_map(|&arg| {
if arg.is_empty() {
None
} else {
Some(Cow::Borrowed(arg))
}
})
.reduce(|mut pre, cur| {
pre = pre.add("/");
pre = pre.add(cur);
pre
});
match joined {
Some(joined) => normalize(&joined),
None => ".".to_string(),
}
}
}
pub fn normalize(path: &str) -> String {
if path.len() == 0 {
return ".".to_owned();
} else {
let is_absolute = is_absolute(path);
let trailing_separator = path
.chars()
.last()
.map(|c| c == CHAR_FORWARD_SLASH)
.unwrap_or(false);
let mut consecutive_dd = 0;
let mut path_stack = vec![];
path.split("/")
.filter(|seg| !seg.is_empty())
.for_each(|seg| {
match seg {
"." => {}
".." => {
if consecutive_dd == path_stack.len() {
path_stack.push(seg);
consecutive_dd += 1;
} else {
path_stack.pop();
}
}
other => {
path_stack.push(other);
}
}
});
let mut normalized_path = if is_absolute {
path_stack
.iter()
.position(|&str| str != "..")
.map(|item| path_stack[item..].join("/"))
.unwrap_or("".to_string())
} else {
path_stack.join("/")
};
if is_absolute {
normalized_path = "/".to_string() + &normalized_path;
}
if normalized_path.is_empty() {
normalized_path.push('.');
}
if trailing_separator && normalized_path != "/" {
normalized_path.push('/');
}
normalized_path
}
}
pub fn parse(path: &str) -> Parsed {
let path = path.chars().collect::<Vec<char>>();
let mut ret = Parsed::default();
if path.len() == 0 {
ret
} else {
let is_absolute = path.get(0).map(|c| c == &CHAR_FORWARD_SLASH).unwrap();
let start;
if is_absolute {
ret.root = "/".to_owned();
start = 1;
} else {
start = 0;
}
let mut start_dot = -1;
let mut start_part = 0;
let mut end = -1;
let mut matched_slash = true;
let mut i = (path.len() - 1) as i32;
let mut pre_dot_state = 0;
while i >= start {
let code = *path.get(i as usize).unwrap();
if code == CHAR_FORWARD_SLASH {
if !matched_slash {
start_part = i + 1;
break;
}
i -= 1;
continue;
}
if end == -1 {
matched_slash = false;
end = i + 1;
}
if code == CHAR_DOT {
if start_dot == -1 {
start_dot = i;
} else if pre_dot_state != 1 {
pre_dot_state = 1;
}
} else if start_dot != -1 {
pre_dot_state = -1;
}
i -= 1;
}
if end != -1 {
let start = if start_part == 0 && is_absolute {
1
} else {
start_part
};
if start_dot == -1 ||
pre_dot_state == 0 ||
(pre_dot_state == 1 &&
start_dot == end - 1 &&
start_dot == start_part + 1)
{
ret.base = path[start as usize..end as usize].iter().collect();
ret.name = ret.base.clone();
} else {
ret.name = path[start as usize..start_dot as usize].iter().collect();
ret.base = path[start as usize..end as usize].iter().collect();
ret.ext = path[start_dot as usize..end as usize].iter().collect();
}
}
if start_part > 0 {
ret.dir = path[0..(start_part - 1) as usize].iter().collect();
} else if is_absolute {
ret.dir = "/".to_owned();
}
ret
}
}
pub fn relative(from: &str, to: &str) -> String {
if from == to {
"".to_owned()
} else {
let from = resolve!(&from).chars().collect::<Vec<char>>();
let to = resolve!(&to).chars().collect::<Vec<char>>();
if from == to {
"".to_owned()
} else {
let from_start = 1;
let from_end = from.len() as i32;
let from_len = from_end - from_start;
let to_start = 1;
let to_len = to.len() as i32 - to_start;
let length = if from_len < to_len { from_len } else { to_len };
let mut last_common_sep = -1;
let mut i = 0;
while i < length {
let from_code = from.get((from_start + i) as usize).unwrap();
if from_code != to.get((to_start + i) as usize).unwrap() {
break;
} else if from_code == &CHAR_FORWARD_SLASH {
last_common_sep = i;
}
i += 1;
}
if i == length {
if to_len > length {
if to.get((to_start + i) as usize).unwrap() == &CHAR_FORWARD_SLASH {
return to[(to_start + i + 1) as usize..to.len()].iter().collect();
}
if i == 0 {
return to[(to_start + i) as usize..to.len()].iter().collect();
}
} else if from_len > length {
if from.get((from_start + i) as usize).unwrap() == &CHAR_FORWARD_SLASH {
last_common_sep = i;
} else if i == 0 {
last_common_sep = 0;
}
}
}
let mut out = "".to_owned();
let mut i = from_start + last_common_sep + 1;
while i <= from_end {
if i == from_end || from.get(i as usize).unwrap() == &CHAR_FORWARD_SLASH {
if out.len() == 0 {
out.push_str("..")
} else {
out.push_str("/..")
}
}
i += 1;
}
format!(
"{}{}",
&out,
&to[(to_start + last_common_sep) as usize..to.len()]
.iter()
.collect::<String>()
)
}
}
}
pub fn resolve_impl(args: &[&str]) -> String {
let mut resolved_path = "".to_owned();
let mut resolved_absolute = false;
let mut i = args.len() as i32 - 1;
while i >= -1 && !resolved_absolute {
let path = if i >= 0 {
args.get(i.clone() as usize).unwrap().to_string()
} else {
cwd().to_owned()
};
if path.len() == 0 {
i -= 1;
continue;
}
resolved_path = format!("{}/{}", path, resolved_path);
resolved_absolute = path
.chars()
.next()
.map(|c| c == CHAR_FORWARD_SLASH)
.unwrap_or(false);
i -= 1;
}
resolved_path = normalize_string(
&resolved_path,
!resolved_absolute,
&sep,
&is_posix_path_separator,
);
if resolved_absolute {
"/".to_owned() + &resolved_path
} else {
if !resolved_path.is_empty() {
resolved_path
} else {
".".to_owned()
}
}
}
#[macro_export]
macro_rules! resolve {
( $( $x:expr ),* ) => {
{
$crate::posix::resolve_impl(&[
$(
$x,
)*
])
}
};
}
pub use resolve;
pub fn to_namespaced_path() {}
use once_cell::sync::Lazy;
pub(crate) static POSIX_CWD: Lazy<String> = Lazy::new(|| {
let mut cwd = std::env::current_dir()
.unwrap_or(PathBuf::from(""))
.to_string_lossy()
.to_string();
if cfg!(target_os = "windows") {
cwd = cwd
.chars()
.map(|c| if c == '\\' { '/' } else { c })
.take_while(|c| c != &'/')
.collect()
};
cwd
});
pub fn cwd() -> &'static str {
&POSIX_CWD
}