use std::path::{self, Component, Path, PathBuf};
use crate::{core::*, errors::*};
pub fn base<T: AsRef<Path>>(path: T) -> RvResult<String> {
path.as_ref().components().last_result()?.to_string()
}
pub fn clean<T: AsRef<Path>>(path: T) -> PathBuf {
let mut cnt = 0;
let mut prev = None;
let mut path_buf = PathBuf::new();
for component in path.as_ref().components() {
match component {
x if x == Component::CurDir && cnt == 0 => continue,
x if x == Component::ParentDir && cnt > 0 && !prev.has(Component::ParentDir) => {
match prev.unwrap() {
Component::RootDir => {},
Component::Normal(_) => {
cnt -= 1;
path_buf.pop();
prev = path_buf.components().last();
},
_ => {},
}
continue;
},
_ => {
cnt += 1;
path_buf.push(component);
prev = Some(component);
},
};
}
if is_empty(&path_buf) {
path_buf.push(".");
}
path_buf
}
pub fn concat<T: AsRef<Path>, U: AsRef<str>>(path: T, val: U) -> RvResult<PathBuf> {
Ok(PathBuf::from(format!("{}{}", path.as_ref().to_string()?, val.as_ref())))
}
pub fn dir<T: AsRef<Path>>(path: T) -> RvResult<PathBuf> {
let path = path.as_ref();
let dir = path.parent().ok_or_else(|| PathError::parent_not_found(path))?;
Ok(dir.to_path_buf())
}
pub fn expand<T: AsRef<Path>>(path: T) -> RvResult<PathBuf> {
let path = path.as_ref();
let pathstr = path.to_string()?;
let path = match pathstr.matches('~').count() {
cnt if cnt > 1 => return Err(PathError::multiple_home_symbols(path).into()),
cnt if cnt == 1 && !has_prefix(path, "~/") && pathstr != "~" => {
return Err(PathError::invalid_expansion(path).into())
},
cnt if cnt == 1 && pathstr == "~" => home_dir()?,
1 => mash(home_dir()?, &pathstr[2..]),
_ => path.to_path_buf(),
};
let pathstr = path.to_string()?;
let path = if pathstr.matches('$').some() {
let mut path_buf = PathBuf::new();
for x in path.components() {
match x {
Component::Normal(y) => {
let mut str = String::new();
let seg = y.to_string()?;
let mut chars = seg.chars().peekable();
while chars.peek().is_some() {
str += &chars.by_ref().take_while(|&x| x != '$').collect::<String>();
if chars.peek().is_some() {
chars.next_if_eq(&'{'); let var = &chars.take_while_p(|&x| x != '$' && x != '}').collect::<String>();
chars.next_if_eq(&'}'); if var.is_empty() {
return Err(PathError::invalid_expansion(seg).into());
}
str += &std::env::var(var)?;
}
}
path_buf.push(str);
},
_ => path_buf.push(x),
};
}
path_buf
} else {
path
};
Ok(path)
}
pub fn ext<T: AsRef<Path>>(path: T) -> RvResult<String> {
match path.as_ref().extension() {
Some(val) => val.to_string(),
None => Err(PathError::extension_not_found(path).into()),
}
}
pub fn first<T: AsRef<Path>>(path: T) -> RvResult<String> {
path.as_ref().components().first_result()?.to_string()
}
pub fn name<T: AsRef<Path>>(path: T) -> RvResult<String> {
base(trim_ext(path)?)
}
pub fn has<T: AsRef<Path>, U: AsRef<Path>>(path: T, val: U) -> bool {
match (path.as_ref().to_string(), val.as_ref().to_string()) {
(Ok(base), Ok(path)) => base.contains(&path),
_ => false,
}
}
pub fn has_prefix<T: AsRef<Path>, U: AsRef<Path>>(path: T, prefix: U) -> bool {
match (path.as_ref().to_string(), prefix.as_ref().to_string()) {
(Ok(base), Ok(prefix)) => base.starts_with(&prefix),
_ => false,
}
}
pub fn has_suffix<T: AsRef<Path>, U: AsRef<Path>>(path: T, suffix: U) -> bool {
match (path.as_ref().to_string(), suffix.as_ref().to_string()) {
(Ok(base), Ok(suffix)) => base.ends_with(&suffix),
_ => false,
}
}
pub fn home_dir() -> RvResult<PathBuf> {
let home = std::env::var("HOME")?;
let dir = PathBuf::from(home);
Ok(dir)
}
pub fn is_empty<T: Into<PathBuf>>(path: T) -> bool {
path.into() == PathBuf::new()
}
pub fn last<T: AsRef<Path>>(path: T) -> RvResult<String> {
base(path)
}
pub fn mash<T: AsRef<Path>, U: AsRef<Path>>(dir: T, base: U) -> PathBuf {
let base = trim_prefix(base, path::MAIN_SEPARATOR.to_string());
let path = dir.as_ref().join(base);
path.components().collect::<PathBuf>()
}
pub fn parse_paths<T: AsRef<str>>(value: T) -> RvResult<Vec<PathBuf>> {
let mut paths: Vec<PathBuf> = vec![];
for dir in value.as_ref().split(':') {
if !dir.is_empty() {
paths.push(PathBuf::from(dir));
}
}
Ok(paths)
}
pub fn relative<T: AsRef<Path>, U: AsRef<Path>>(path: T, base: U) -> RvResult<PathBuf> {
let path = path.as_ref();
let base = base.as_ref();
if path != base {
let mut x = path.components();
let mut y = base.components();
let mut comps: Vec<Component> = vec![];
loop {
match (x.next(), y.next()) {
(None, None) => break,
(None, _) => comps.push(Component::ParentDir),
(Some(a), None) => {
comps.push(a);
comps.extend(x.by_ref());
break;
},
(Some(a), Some(b)) if comps.is_empty() && a == b => continue,
(Some(a), Some(_)) => {
comps.push(Component::ParentDir);
for _ in y {
comps.push(Component::ParentDir);
}
comps.push(a);
comps.extend(x.by_ref());
break;
},
}
}
return Ok(comps.iter().collect::<PathBuf>());
}
Ok(path.to_owned())
}
pub fn trim_ext<T: AsRef<Path>>(path: T) -> RvResult<PathBuf> {
let path = path.as_ref();
Ok(match path.extension() {
Some(val) => trim_suffix(path, format!(".{}", val.to_string()?)),
None => path.to_path_buf(),
})
}
pub fn trim_first<T: AsRef<Path>>(path: T) -> PathBuf {
path.as_ref().components().drop(1).as_path().to_path_buf()
}
pub fn trim_last<T: AsRef<Path>>(path: T) -> PathBuf {
path.as_ref().components().drop(-1).as_path().to_path_buf()
}
pub fn trim_prefix<T: AsRef<Path>, U: AsRef<Path>>(path: T, prefix: U) -> PathBuf {
let path = path.as_ref();
match (path.to_string(), prefix.as_ref().to_string()) {
(Ok(base), Ok(prefix)) if base.starts_with(&prefix) => PathBuf::from(&base[prefix.size()..]),
_ => path.to_path_buf(),
}
}
pub fn trim_protocol<T: AsRef<Path>>(path: T) -> PathBuf {
let path = path.as_ref();
match path.to_string() {
Ok(base) => match base.find("//") {
Some(i) => {
let (prefix, suffix) = base.split_at(i + 2);
let lower = prefix.to_lowercase();
let lower = lower.trim_start_matches("file://");
let lower = lower.trim_start_matches("ftp://");
let lower = lower.trim_start_matches("http://");
let lower = lower.trim_start_matches("https://");
if !lower.is_empty() {
PathBuf::from(format!("{}{}", prefix, suffix))
} else {
PathBuf::from(suffix)
}
},
_ => PathBuf::from(base),
},
_ => path.to_path_buf(),
}
}
pub fn trim_suffix<T: AsRef<Path>, U: AsRef<Path>>(path: T, suffix: U) -> PathBuf {
let path = path.as_ref();
match (path.to_string(), suffix.as_ref().to_string()) {
(Ok(base), Ok(suffix)) if base.ends_with(&suffix) => PathBuf::from(&base[..base.size() - suffix.size()]),
_ => path.to_path_buf(),
}
}
pub trait PathExt {
fn base(&self) -> RvResult<String>;
fn clean(&self) -> PathBuf;
fn concat<T: AsRef<str>>(&self, val: T) -> RvResult<PathBuf>;
fn dir(&self) -> RvResult<PathBuf>;
fn expand(&self) -> RvResult<PathBuf>;
fn ext(&self) -> RvResult<String>;
fn first(&self) -> RvResult<String>;
fn has<T: AsRef<Path>>(&self, path: T) -> bool;
fn has_prefix<T: AsRef<Path>>(&self, prefix: T) -> bool;
fn has_suffix<T: AsRef<Path>>(&self, suffix: T) -> bool;
fn is_empty(&self) -> bool;
fn last(&self) -> RvResult<String>;
fn mash<T: AsRef<Path>>(&self, path: T) -> PathBuf;
fn name(&self) -> RvResult<String>;
fn relative<T: AsRef<Path>>(&self, path: T) -> RvResult<PathBuf>;
fn trim_ext(&self) -> RvResult<PathBuf>;
fn trim_first(&self) -> PathBuf;
fn trim_last(&self) -> PathBuf;
fn trim_prefix<T: AsRef<Path>>(&self, prefix: T) -> PathBuf;
fn trim_protocol(&self) -> PathBuf;
fn trim_suffix<T: AsRef<Path>>(&self, suffix: T) -> PathBuf;
}
impl PathExt for Path {
fn base(&self) -> RvResult<String> {
base(self)
}
fn clean(&self) -> PathBuf {
clean(self)
}
fn concat<T: AsRef<str>>(&self, val: T) -> RvResult<PathBuf> {
concat(self, val)
}
fn dir(&self) -> RvResult<PathBuf> {
dir(self)
}
fn expand(&self) -> RvResult<PathBuf> {
expand(self)
}
fn ext(&self) -> RvResult<String> {
ext(self)
}
fn first(&self) -> RvResult<String> {
first(self)
}
fn is_empty(&self) -> bool {
is_empty(self)
}
fn has<T: AsRef<Path>>(&self, val: T) -> bool {
has(self, val)
}
fn has_prefix<T: AsRef<Path>>(&self, prefix: T) -> bool {
has_prefix(self, prefix)
}
fn has_suffix<T: AsRef<Path>>(&self, suffix: T) -> bool {
has_suffix(self, suffix)
}
fn last(&self) -> RvResult<String> {
last(self)
}
fn mash<T: AsRef<Path>>(&self, path: T) -> PathBuf {
mash(self, path)
}
fn name(&self) -> RvResult<String> {
name(self)
}
fn relative<T: AsRef<Path>>(&self, path: T) -> RvResult<PathBuf> {
relative(self, path)
}
fn trim_ext(&self) -> RvResult<PathBuf> {
trim_ext(self)
}
fn trim_first(&self) -> PathBuf {
trim_first(self)
}
fn trim_last(&self) -> PathBuf {
trim_last(self)
}
fn trim_prefix<T: AsRef<Path>>(&self, prefix: T) -> PathBuf {
trim_prefix(self, prefix)
}
fn trim_protocol(&self) -> PathBuf {
trim_protocol(self)
}
fn trim_suffix<T: AsRef<Path>>(&self, suffix: T) -> PathBuf {
trim_suffix(self, suffix)
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
#[test]
fn test_pathext_base() {
assert_eq!(Path::new("").base().unwrap_err().to_string(), IterError::item_not_found().to_string());
assert_eq!(Path::new("bar").base().unwrap(), "bar".to_string());
assert_eq!(Path::new("/foo/bar").base().unwrap(), "bar".to_string());
assert_eq!(Path::new("/foo/bar.bin").base().unwrap(), "bar.bin".to_string());
}
#[test]
fn test_pathext_clean() {
let tests = vec![
("/", "/"),
("/", "//"),
("/", "///"),
(".", ".//"),
("/", "//.."),
("..", "..//"),
("/", "/..//"),
("foo/bar/blah", "foo//bar///blah"),
("/foo/bar/blah", "/foo//bar///blah"),
("/", "/.//./"),
(".", "././/./"),
(".", "./"),
("/", "/./"),
("foo", "./foo"),
("foo/bar", "./foo/./bar"),
("/foo/bar", "/foo/./bar"),
("foo/bar", "foo/bar/."),
("/", "/.."),
("/foo", "/../foo"),
(".", "foo/.."),
("../foo", "../foo"),
("/bar", "/foo/../bar"),
("foo", "foo/bar/.."),
("bar", "foo/../bar"),
("/bar", "/foo/../bar"),
(".", "foo/bar/../../"),
("..", "foo/bar/../../.."),
("/", "/foo/bar/../../.."),
("/", "/foo/bar/../../../.."),
("../..", "foo/bar/../../../.."),
("blah/bar", "foo/bar/../../blah/bar"),
("blah", "foo/bar/../../blah/bar/.."),
("../foo", "../foo"),
("../foo", "../foo/"),
("../foo/bar", "../foo/bar"),
("..", "../foo/.."),
("~/foo", "~/foo"),
];
for test in tests {
assert_eq!(Path::new(test.1).clean(), PathBuf::from(test.0));
}
}
#[test]
fn test_pathext_concat() {
assert_eq!(Path::new("/foo/bar").concat(".rs").unwrap(), PathBuf::from("/foo/bar.rs"));
assert_eq!(PathBuf::from("bar").concat(".rs").unwrap(), PathBuf::from("bar.rs"));
}
#[test]
fn test_pathext_dir() {
assert_eq!(Path::new("/foo/").dir().unwrap(), PathBuf::from("/").as_path(),);
assert_eq!(Path::new("/foo/bar/").dir().unwrap(), PathBuf::from("/foo").as_path(),);
assert_eq!(Path::new("/foo/bar").dir().unwrap(), PathBuf::from("/foo").as_path());
}
#[test]
fn test_pathext_expand() -> RvResult<()> {
let home = sys::home_dir()?;
assert_eq!(
Path::new("~/~").expand().unwrap_err().to_string(),
PathError::multiple_home_symbols("~/~").to_string()
);
assert_eq!(
Path::new("foo/~").expand().unwrap_err().to_string(),
PathError::invalid_expansion("foo/~").to_string()
);
assert_eq!(Path::new("~").expand()?, PathBuf::from(&home));
assert_eq!(Path::new("~/foo").expand()?, PathBuf::from(&home).join("foo"));
assert_eq!(Path::new("${HOME}").expand()?, PathBuf::from(&home));
assert_eq!(Path::new("${HOME}/foo").expand()?, PathBuf::from(&home).join("foo"));
assert_eq!(Path::new("/foo/${HOME}").expand()?, PathBuf::from("/foo").join(&home));
assert_eq!(Path::new("/foo/${HOME}/bar").expand()?, PathBuf::from("/foo").join(&home).join("bar"));
assert_eq!(
Path::new("/foo${HOME}/bar").expand()?,
PathBuf::from("/foo".to_string() + &home.to_string()? + &"/bar".to_string())
);
assert_eq!(
Path::new("/foo${HOME}${HOME}").expand()?,
PathBuf::from("/foo".to_string() + &home.to_string()? + &home.to_string()?)
);
assert_eq!(
Path::new("/foo$HOME$HOME").expand()?,
PathBuf::from("/foo".to_string() + &home.to_string()? + &home.to_string()?)
);
Ok(())
}
#[test]
fn test_pathext_ext() {
assert_eq!(
Path::new("base").ext().unwrap_err().to_string(),
PathError::extension_not_found("base").to_string()
);
assert_eq!(Path::new("base.bin").ext().unwrap(), "bin".to_string());
assert_eq!(Path::new("/foo/bar/base.blah").ext().unwrap(), "blah".to_string());
}
#[test]
fn test_pathext_first() {
assert_eq!(Path::new("").first().unwrap_err().to_string(), IterError::item_not_found().to_string());
assert_eq!(Path::new("foo").first().unwrap(), "foo".to_string());
assert_eq!(Path::new("/foo").first().unwrap(), "/".to_string());
}
#[test]
fn test_pathext_has() {
assert_eq!(Path::new("").has(""), true);
assert_eq!(Path::new("/foo").has("fo"), true);
assert_eq!(Path::new("/foo/bar").has("bar"), true);
assert_eq!(Path::new("/foo/bar").has("bar/"), false);
}
#[test]
fn test_pathext_has_prefix() {
assert_eq!(Path::new("").has_prefix(""), true);
assert_eq!(Path::new("/foo").has_prefix("/fo"), true);
assert_eq!(Path::new("/foo/bar").has_prefix("bar/"), false);
}
#[test]
fn test_pathext_has_suffix() {
assert_eq!(Path::new("").has_suffix(""), true);
assert_eq!(Path::new("/foo").has_suffix("/fo"), false);
assert_eq!(Path::new("/foo/bar").has_suffix("bar"), true);
}
#[test]
fn test_pathext_last() {
assert_eq!(Path::new("").last().unwrap_err().to_string(), IterError::item_not_found().to_string());
assert_eq!(Path::new("foo").last().unwrap(), "foo".to_string());
assert_eq!(Path::new("/foo").last().unwrap(), "foo".to_string());
}
#[test]
fn test_sys_home_dir() {
let home = sys::home_dir().unwrap();
assert!(home != PathBuf::new());
assert!(home.starts_with("/"));
assert_eq!(home.join("foo"), PathBuf::from(&home).join("foo"));
}
#[test]
fn test_pathext_is_empty() {
assert_eq!(Path::new("/").is_empty(), false);
assert_eq!(Path::new("").is_empty(), true);
assert_eq!(PathBuf::from("").is_empty(), true);
}
#[test]
fn test_pathext_mash() {
assert_eq!(Path::new("").mash(""), PathBuf::from(""));
assert_eq!(Path::new("/foo").mash(""), PathBuf::from("/foo"));
assert_eq!(Path::new("/foo").mash("/bar"), PathBuf::from("/foo/bar"));
assert_eq!(Path::new("/foo").mash("bar/"), PathBuf::from("/foo/bar"));
}
#[test]
fn test_pathext_name() {
assert_eq!(Path::new("").name().unwrap_err().to_string(), IterError::item_not_found().to_string());
assert_eq!(Path::new("bar").name().unwrap(), "bar".to_string());
assert_eq!(Path::new("/foo/bar").name().unwrap(), "bar".to_string());
assert_eq!(Path::new("/foo/bar.bin").name().unwrap(), "bar".to_string());
}
#[test]
fn test_pathext_relative() {
assert_eq!(Path::new("bar1").relative("bar2").unwrap(), PathBuf::from("../bar1"));
assert_eq!(Path::new("foo/bar1").relative("foo/bar2").unwrap(), PathBuf::from("../bar1"));
assert_eq!(Path::new("~/foo/bar1").relative("~/foo/bar2").unwrap(), PathBuf::from("../bar1"));
assert_eq!(Path::new("../foo/bar1").relative("../foo/bar2").unwrap(), PathBuf::from("../bar1"));
assert_eq!(Path::new("foo1/bar1").relative("foo2/bar2").unwrap(), PathBuf::from("../../foo1/bar1"));
assert_eq!(Path::new("/foo1/bar1").relative("/foo2/bar2").unwrap(), PathBuf::from("../../foo1/bar1"));
assert_eq!(
Path::new("blah1/foo1/bar1").relative("blah2/foo2/bar2").unwrap(),
PathBuf::from("../../../blah1/foo1/bar1")
);
assert_eq!(
Path::new("/blah1/foo1/bar1").relative("/blah2/foo2/bar2").unwrap(),
PathBuf::from("../../../blah1/foo1/bar1")
);
assert_eq!(Path::new("/dir1").relative("/dir1/dir2").unwrap(), PathBuf::from(".."));
}
#[test]
fn test_pathext_trim_ext() {
assert_eq!(Path::new("/").trim_ext().unwrap(), PathBuf::from("/"));
assert_eq!(Path::new("/foo").trim_ext().unwrap(), PathBuf::from("/foo"));
assert_eq!(Path::new("/foo.bar").trim_ext().unwrap(), PathBuf::from("/foo"));
assert_eq!(Path::new("/foo.bar.bar").trim_ext().unwrap(), PathBuf::from("/foo.bar"));
}
#[test]
fn test_pathext_trim_first() {
assert_eq!(Path::new("/").trim_first(), PathBuf::from(""),);
assert_eq!(Path::new("foo/bar").trim_first(), PathBuf::from("bar"),);
assert_eq!(Path::new("/foo/bar").trim_first(), PathBuf::from("foo/bar"),);
}
#[test]
fn test_pathext_trim_last() {
assert_eq!(Path::new("/").trim_last(), PathBuf::from(""),);
assert_eq!(Path::new("foo/bar").trim_last(), PathBuf::from("foo"),);
assert_eq!(Path::new("/foo/bar").trim_last(), PathBuf::from("/foo"),);
}
#[test]
fn test_pathext_trim_prefix() {
assert_eq!(Path::new("/").trim_prefix("/"), PathBuf::new());
assert_eq!(Path::new("/foo/bar").trim_prefix("/foo"), PathBuf::from("/bar"));
assert_eq!(Path::new("/").trim_prefix(""), PathBuf::from("/"));
assert_eq!(Path::new("/foo").trim_prefix("blah"), PathBuf::from("/foo"));
}
#[test]
fn test_pathext_trim_protocol() {
assert_eq!(Path::new("/foo").trim_protocol(), PathBuf::from("/foo"));
assert_eq!(Path::new("file:///foo").trim_protocol(), PathBuf::from("/foo"));
assert_eq!(Path::new("ftp://foo").trim_protocol(), PathBuf::from("foo"));
assert_eq!(Path::new("http://foo").trim_protocol(), PathBuf::from("foo"));
assert_eq!(Path::new("https://foo").trim_protocol(), PathBuf::from("foo"));
assert_eq!(Path::new("HTTPS://Foo").trim_protocol(), PathBuf::from("Foo"));
assert_eq!(Path::new("Https://Foo").trim_protocol(), PathBuf::from("Foo"));
assert_eq!(Path::new("HttpS://FoO").trim_protocol(), PathBuf::from("FoO"));
assert_eq!(Path::new("foo").trim_protocol(), PathBuf::from("foo"));
assert_eq!(Path::new("foo/bar").trim_protocol(), PathBuf::from("foo/bar"));
assert_eq!(Path::new("foo//bar").trim_protocol(), PathBuf::from("foo//bar"));
assert_eq!(Path::new("ntp:://foo").trim_protocol(), PathBuf::from("ntp:://foo"));
}
#[test]
fn test_pathext_trim_suffix() {
assert_eq!(Path::new("/").trim_suffix("/"), PathBuf::new());
assert_eq!(Path::new("/foo/bar").trim_suffix("/bar"), PathBuf::from("/foo"));
assert_eq!(Path::new("/").trim_suffix(""), PathBuf::from("/"));
assert_eq!(Path::new("/foo").trim_suffix("blah"), PathBuf::from("/foo"));
}
}