#[cfg(windows)]
use regex::Regex;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::ffi::OsStr;
use std::fmt;
use std::path::{Path, PathBuf};
#[cfg(unix)]
mod localization {
pub const ROOT: &str = "/";
pub const SLASH: char = '/';
pub const SLASH_STR: &str = ROOT;
}
#[cfg(windows)]
mod localization {
pub const SLASH: char = '\\';
pub const SLASH_STR: &str = "\\";
}
#[cfg(unix)]
use localization::{ROOT, SLASH, SLASH_STR};
#[cfg(windows)]
use localization::{SLASH, SLASH_STR};
const RC: char = char::REPLACEMENT_CHARACTER; const BS: char = '\\';
const FS: char = '/';
const UP: &str = "..";
#[derive(Clone, PartialEq, Debug, Default)]
pub struct OsPath {
components: Vec<String>,
absolute: bool,
directory: bool,
path: PathBuf,
}
impl OsPath {
pub fn new() -> Self {
Self::default()
}
pub fn join<P: AsRef<Path>>(&self, path: P) -> Self {
let mut new_self = self.clone();
let path = Self::build_self(path);
Self::merge_paths(&mut new_self, path);
new_self.path = Self::build_pathbuf(&new_self.components, new_self.absolute);
new_self
}
pub fn push<P: AsRef<Path>>(&mut self, path: P) {
let path = Self::build_self(path);
Self::merge_paths(self, path);
self.path = Self::build_pathbuf(&self.components, self.absolute);
}
pub fn resolve(&mut self) {
let mut new_vec: Vec<String> = Vec::new();
for c in &self.components {
if c != UP {
new_vec.push(c.clone());
} else {
new_vec.pop();
}
}
self.components = new_vec;
self.path = Self::build_pathbuf(&self.components, self.absolute);
}
pub fn is_absolute(&self) -> bool {
self.absolute
}
pub fn exists(&self) -> bool {
self.path.exists()
}
pub fn is_file(&self) -> bool {
!self.directory
}
pub fn is_dir(&self) -> bool {
self.directory
}
pub fn name(&self) -> Option<&String> {
if !self.components.is_empty() {
return self.components.last();
}
None
}
pub fn extension(&self) -> Option<String> {
if self.is_file() {
return Some(self.name()?.split('.').last()?.to_string());
}
None
}
pub fn parent(&self) -> Option<Self> {
if self.components.len() < 2 && !self.absolute {
return None;
}
let i = self.components.len() - 1;
let mut new_self = self.clone();
new_self.components.truncate(i);
new_self.path = Self::build_pathbuf(&new_self.components, new_self.absolute);
new_self.directory = true;
Some(new_self)
}
pub fn root(&self) -> Option<String> {
if !self.components.is_empty() {
return Some(self.components[0].clone());
}
None
}
pub fn force_dir(&mut self) {
self.directory = true;
}
pub fn to_pathbuf(&self) -> PathBuf {
self.path.clone()
}
pub fn to_path(&self) -> &Path {
self.path.as_path()
}
}
impl OsPath {
fn build_self<P: AsRef<Path>>(path: P) -> Self {
let path = path.as_ref().to_string_lossy().to_string();
#[cfg(unix)]
let absolute = path.starts_with(ROOT) || path.starts_with(BS) || path.starts_with(FS);
#[cfg(windows)]
let absolute = match Regex::new(r"^[a-zA-Z]:") {
Ok(re) => re.is_match(&path),
Err(_) => false,
};
let directory = path.ends_with(SLASH) || path.ends_with(UP);
let clean: String = path
.chars()
.map(|c| if c == BS || c == FS { RC } else { c })
.collect();
let components: Vec<String> = clean
.split(RC)
.filter_map(|s| {
if s.is_empty() {
None
} else {
Some(s.to_string())
}
})
.collect();
let path = Self::build_pathbuf(&components, absolute);
Self {
components,
absolute,
directory,
path,
}
}
fn build_string(&self) -> String {
match (self.absolute, self.directory) {
#[cfg(unix)]
(true, true) => ROOT.to_string() + &self.components.join(SLASH_STR) + SLASH_STR,
#[cfg(unix)]
(true, false) => ROOT.to_string() + &self.components.join(SLASH_STR),
#[cfg(windows)]
(true, true) => self.components.join(SLASH_STR) + SLASH_STR,
#[cfg(windows)]
(true, false) => self.components.join(SLASH_STR),
(false, false) => self.components.join(SLASH_STR),
(false, true) => self.components.join(SLASH_STR) + SLASH_STR,
}
}
fn build_pathbuf(components: &Vec<String>, absolute: bool) -> PathBuf {
let mut path = PathBuf::new();
if absolute {
#[cfg(unix)]
path.push(ROOT);
#[cfg(windows)]
if components.len() == 1 {
path.push(format!("{}{}", &components[0], SLASH_STR));
return path; }
}
#[cfg(windows)]
if let Ok(re) = Regex::new(r"^[a-zA-Z]:$") {
for c in components {
#[cfg(windows)]
if re.is_match(&c) {
path.push(format!("{}{}", &c, SLASH_STR));
continue;
}
path.push(c);
}
} else {
for c in components {
path.push(c);
}
}
#[cfg(unix)]
for c in components {
path.push(c);
}
path
}
fn merge_paths(first: &mut Self, mut second: Self) {
if second.components.is_empty() {
return;
}
if first.components.is_empty() && !first.absolute {
*first = second;
return;
}
if !first.directory && second.components.first().unwrap() == UP {
first.components.pop();
first.components.pop();
second.components.remove(0);
}
for c in second.components {
if c == UP {
first.components.pop();
continue;
}
first.components.push(c);
}
first.directory = second.directory;
}
}
impl fmt::Display for OsPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.build_string())
}
}
impl Serialize for OsPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.build_string())
}
}
struct OsPathVisitor;
impl<'de> Visitor<'de> for OsPathVisitor {
type Value = OsPath;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a str or String")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(OsPath::from(value))
}
}
impl<'de> Deserialize<'de> for OsPath {
fn deserialize<D>(deserializer: D) -> Result<OsPath, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(OsPathVisitor)
}
}
impl From<&OsPath> for OsPath {
fn from(p: &OsPath) -> Self {
p.clone()
}
}
impl From<&str> for OsPath {
fn from(s: &str) -> Self {
Self::build_self(s)
}
}
impl From<String> for OsPath {
fn from(s: String) -> Self {
Self::build_self(s)
}
}
impl From<OsPath> for String {
fn from(p: OsPath) -> Self {
p.build_string()
}
}
impl From<&OsPath> for String {
fn from(p: &OsPath) -> Self {
p.build_string()
}
}
impl From<&String> for OsPath {
fn from(s: &String) -> Self {
Self::build_self(s)
}
}
impl From<PathBuf> for OsPath {
fn from(p: PathBuf) -> Self {
Self::build_self(p)
}
}
impl From<OsPath> for PathBuf {
fn from(p: OsPath) -> Self {
p.path
}
}
impl From<&PathBuf> for OsPath {
fn from(p: &PathBuf) -> Self {
Self::build_self(p)
}
}
impl From<&Path> for OsPath {
fn from(p: &Path) -> Self {
Self::build_self(p)
}
}
impl FromIterator<OsPath> for OsPath {
fn from_iter<I: IntoIterator<Item = OsPath>>(iter: I) -> Self {
let mut path = Self::new();
for i in iter {
path.push(i);
}
path
}
}
impl FromIterator<String> for OsPath {
fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
let mut path = Self::new();
for i in iter {
path.push(i);
}
path
}
}
impl AsRef<OsPath> for OsPath {
fn as_ref(&self) -> &OsPath {
self
}
}
impl AsRef<Path> for OsPath {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl AsRef<OsStr> for OsPath {
fn as_ref(&self) -> &OsStr {
self.path.as_os_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let path = OsPath::new();
assert_eq!(path.components.len(), 0);
assert_eq!(path.absolute, false);
assert_eq!(path.directory, false);
assert_eq!(path.path, PathBuf::new());
}
#[test]
fn test_build_self() {
#[cfg(unix)]
{
let path = OsPath::build_self("/");
assert_eq!(path.components.len(), 0);
assert_eq!(path.absolute, true);
assert_eq!(path.directory, true);
assert_eq!(path.path, PathBuf::from("/"));
let path = OsPath::build_self("/a/b/c");
assert_eq!(path.components.len(), 3);
assert_eq!(path.absolute, true);
assert_eq!(path.directory, false);
assert_eq!(path.path, PathBuf::from("/a/b/c"));
let path = OsPath::build_self("/a/b/c/");
assert_eq!(path.components.len(), 3);
assert_eq!(path.absolute, true);
assert_eq!(path.directory, true);
assert_eq!(path.path, PathBuf::from("/a/b/c/"));
let path = OsPath::build_self("a/b/c");
assert_eq!(path.components.len(), 3);
assert_eq!(path.absolute, false);
assert_eq!(path.directory, false);
assert_eq!(path.path, PathBuf::from("a/b/c"));
let path = OsPath::build_self("a/b/c/../../../d");
println!("{:?}", path);
assert_eq!(path.components.len(), 7);
assert_eq!(path.absolute, false);
assert_eq!(path.directory, false);
assert_eq!(path.path, PathBuf::from("a/b/c/../../../d"));
}
#[cfg(windows)]
{
let path = OsPath::build_self("C:\\");
assert_eq!(path.components.len(), 1);
assert_eq!(path.absolute, true);
assert_eq!(path.directory, true);
assert_eq!(path.path, PathBuf::from("C:\\"));
let path = OsPath::build_self("A:\\a\\b\\c");
print!("{:?}", path);
assert_eq!(path.components.len(), 4);
assert_eq!(path.absolute, true);
assert_eq!(path.directory, false);
assert_eq!(path.path, PathBuf::from("A:\\a\\b\\c"));
let path = OsPath::build_self("D:\\a\\b\\c\\");
assert_eq!(path.components.len(), 4);
assert_eq!(path.absolute, true);
assert_eq!(path.directory, true);
assert_eq!(path.path, PathBuf::from("D:\\a\\b\\c\\"));
let path = OsPath::build_self("O:\\a\\b\\c\\..\\..\\..\\d");
assert_eq!(path.components.len(), 8);
assert_eq!(path.absolute, true);
assert_eq!(path.directory, false);
assert_eq!(path.path, PathBuf::from("O:\\a\\b\\c\\..\\..\\..\\d"));
assert_eq!(path.root().unwrap(), "O:".to_string());
}
}
}