use crate::enums::FsoType;
use crate::env_ext::get_home_dir;
use std::ffi::OsString;
use std::path::{Path, PathBuf, MAIN_SEPARATOR_STR};
#[cfg(test)]
mod tests;
pub(crate) trait PathExt {
fn join_raw(&self, path: &Path) -> PathBuf;
fn get_type(&self) -> Option<FsoType>;
#[cfg(windows)]
fn replace_dir_seps(&self) -> PathBuf;
fn resolve_tilde(&self) -> PathBuf;
fn resolve_env_variables(&self) -> PathBuf;
fn root(&self, root: &Path) -> PathBuf;
}
impl PathExt for Path {
fn join_raw(&self, path: &Path) -> PathBuf {
let parent_bytes = self.as_os_str().as_encoded_bytes();
let child_bytes = path.as_os_str().as_encoded_bytes();
let mut raw_str = std::ffi::OsString::from(&self);
#[cfg(not(windows))]
if parent_bytes.ends_with(&['/' as u8])
|| child_bytes.starts_with(&['/' as u8]) {
raw_str.push(path);
}
else {
raw_str.push(std::path::MAIN_SEPARATOR_STR);
raw_str.push(path);
}
#[cfg(windows)]
if parent_bytes.ends_with(&['/' as u8])
|| parent_bytes.ends_with(&['\\' as u8])
|| child_bytes.starts_with(&['/' as u8])
|| child_bytes.starts_with(&['\\' as u8]) {
raw_str.push(path);
}
else {
raw_str.push(std::path::MAIN_SEPARATOR_STR);
raw_str.push(path);
}
PathBuf::from(raw_str)
}
fn get_type(&self) -> Option<FsoType> {
if self.is_file() {
if self.is_symlink() {
Some(FsoType::SymFile)
}
else {
Some(FsoType::File)
}
}
else if self.is_dir() {
if self.is_symlink() {
Some(FsoType::SymDir)
}
else {
Some(FsoType::Dir)
}
}
else if self.is_symlink() {
Some(FsoType::BrokenSym)
}
else {
None
}
}
#[cfg(windows)]
fn replace_dir_seps(&self) -> PathBuf {
let mut bytes = Vec::from(self.as_os_str().as_encoded_bytes());
for b in bytes.iter_mut() {
if *b == '/' as u8 {
*b = std::path::MAIN_SEPARATOR as u8;
}
}
unsafe {
OsString::from_encoded_bytes_unchecked(bytes)
}.into()
}
fn resolve_tilde(&self) -> PathBuf {
let path_bytes = self.as_os_str().as_encoded_bytes();
if path_bytes == &['~' as u8]
|| path_bytes.starts_with(&['~' as u8, '/' as u8])
|| path_bytes.starts_with(&['~' as u8, '\\' as u8]) {
}
else {
return PathBuf::from(self);
}
match get_home_dir() {
Some(mut v) => {
let trimmed_str;
unsafe {
trimmed_str = OsString::from_encoded_bytes_unchecked(
path_bytes[1..].to_vec()
);
}
v.push(trimmed_str);
PathBuf::from(v)
}
None => PathBuf::from(self),
}
}
fn resolve_env_variables(&self) -> PathBuf {
let given_bytes = self.as_os_str().as_encoded_bytes();
let mut search_start_idx = 0;
let mut resolved_bytes: Vec<u8> = vec![];
while let Some(next) = next_env_var(given_bytes, search_start_idx) {
let var_no_brkts = &given_bytes[next.0 + 1..next.1];
let var_name_no_brkts = unsafe {
OsString::from_encoded_bytes_unchecked(var_no_brkts.to_vec())
};
if let Some(v) = std::env::var_os(var_name_no_brkts) {
resolved_bytes.extend(&given_bytes[search_start_idx..next.0]);
resolved_bytes.extend(v.as_encoded_bytes());
}
else {
resolved_bytes.extend(&given_bytes[search_start_idx..next.1 + 1]);
}
search_start_idx = next.1 + 1;
}
if search_start_idx == 0 {
return PathBuf::from(self);
}
else {
resolved_bytes.extend(&given_bytes[search_start_idx..]);
let resolved_str = unsafe {
OsString::from_encoded_bytes_unchecked(resolved_bytes)
};
PathBuf::from(resolved_str)
}
}
fn root(&self, root: &Path) -> PathBuf {
if self.has_root() {
PathBuf::from(self)
}
else if root.has_root() {
root.join_raw(self)
}
else {
Path::new(MAIN_SEPARATOR_STR).join_raw(self)
}
}
}
fn next_env_var(bytes: &[u8], search_start_idx: usize) -> Option<(usize, usize)> {
let mut var_start = 0;
let mut has_start = false;
for (i, b) in bytes[search_start_idx..].iter().enumerate() {
if *b == '<' as u8 {
var_start = i;
has_start = true;
}
else if *b == '>' as u8 && has_start {
return Some((search_start_idx + var_start, search_start_idx + i))
}
}
None
}
#[cfg(test)]
pub fn contains_env_var(input: &Path) -> bool {
next_env_var(input.as_os_str().as_encoded_bytes(), 0).is_some()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UniPath(PathBuf);
impl UniPath {
pub fn new_with_root(path: &Path, root: &Path) -> Self {
#[cfg(windows)]
return UniPath(
path
.resolve_env_variables()
.resolve_tilde()
.root(root)
.replace_dir_seps()
);
#[cfg(not(windows))]
return UniPath(
path
.resolve_env_variables()
.resolve_tilde()
.root(root)
);
}
pub fn inner(&self) -> &Path {
&self.0
}
}
impl From<&Path> for UniPath {
fn from(value: &Path) -> Self {
Self::new_with_root(value, &Path::new(MAIN_SEPARATOR_STR))
}
}
impl From<&PathBuf> for UniPath {
fn from(value: &PathBuf) -> Self {
Self::from(value.as_path())
}
}
impl From<PathBuf> for UniPath {
fn from(value: PathBuf) -> Self {
Self::from(value.as_path())
}
}
impl AsRef<UniPath> for UniPath {
fn as_ref(&self) -> &UniPath {
&self
}
}