paved 0.6.0

A simple platform agnostic path representation
Documentation
use std::{
    convert::Infallible,
    ffi::{OsStr, OsString},
    path::{Component, Path, PathBuf},
    str::FromStr,
};

use crate::{PathPrefix, PathType, PavedPath, UNIX_PATH_SEPARATOR, WINDOWS_PATH_SEPARATOR};

impl AsRef<PathBuf> for PavedPath {
    fn as_ref(&self) -> &PathBuf {
        &self.inner
    }
}

impl AsRef<Path> for PavedPath {
    fn as_ref(&self) -> &Path {
        self.inner.as_path()
    }
}

impl From<PavedPath> for PathBuf {
    fn from(value: PavedPath) -> Self {
        value.inner
    }
}

impl From<PavedPath> for OsString {
    fn from(value: PavedPath) -> Self {
        value.inner.into_os_string()
    }
}

impl From<PavedPath> for String {
    fn from(value: PavedPath) -> Self {
        value.inner.to_string_lossy().to_string()
    }
}

impl IntoIterator for PavedPath {
    type Item = OsString;

    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        [self.directories, self.file.as_slice().to_vec()]
            .concat()
            .into_iter()
    }
}

impl From<&Path> for PavedPath {
    fn from(path: &Path) -> Self {
        let path_type = if path.is_absolute() || path.starts_with(UNIX_PATH_SEPARATOR.to_string()) {
            PathType::Absolute
        } else {
            PathType::Relative
        };

        let mut new_paved_path = PavedPath::new(path_type);

        let drive;
        {
            use std::path::Prefix::*;
            if let Some(Component::Prefix(prefix)) = path.components().next() {
                drive = match prefix.kind() {
                    Disk(d) | VerbatimDisk(d) => {
                        Some(PathPrefix::Drive((d as char).to_ascii_uppercase()))
                    }
                    UNC(s1, s2) | VerbatimUNC(s1, s2) => {
                        Some(PathPrefix::UNC(s1.to_os_string(), s2.to_os_string()))
                    }
                    _ => None,
                }
            } else {
                drive = None;
            }
        }

        if let Some(drive) = drive {
            new_paved_path.set_prefix(drive);
        }

        for c in path.components() {
            match c {
                Component::ParentDir => new_paved_path.push_dir_raw(OsString::from("..")),
                Component::Normal(os_str) => new_paved_path.push_dir_raw(os_str.to_os_string()),
                _ => {}
            }
        }

        if new_paved_path.last().is_some() {
            let raw = path.as_os_str().to_string_lossy();
            if !raw.ends_with(UNIX_PATH_SEPARATOR) && !raw.ends_with(WINDOWS_PATH_SEPARATOR) {
                let new_file = new_paved_path.pop_dir().unwrap(); // Infallible, we checked that a last element exists
                new_paved_path.set_file(new_file);
            }
        }

        new_paved_path.rebuild_inner();
        new_paved_path
    }
}

impl From<PathBuf> for PavedPath {
    fn from(value: PathBuf) -> Self {
        value.as_path().into()
    }
}

impl From<String> for PavedPath {
    fn from(value: String) -> Self {
        Path::new(&value).into()
    }
}

impl From<&str> for PavedPath {
    fn from(value: &str) -> Self {
        Path::new(value).into()
    }
}

impl FromStr for PavedPath {
    type Err = Infallible;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Path::new(s).into())
    }
}

impl From<&OsStr> for PavedPath {
    fn from(value: &OsStr) -> Self {
        Path::new(value).into()
    }
}

impl From<OsString> for PavedPath {
    fn from(value: OsString) -> Self {
        value.as_os_str().into()
    }
}

impl<P: Into<OsString>> Extend<P> for PavedPath {
    fn extend<T: IntoIterator<Item = P>>(&mut self, iter: T) {
        for v in iter {
            self.push_dir_raw(v.into());
        }

        self.rebuild_inner();
    }
}

impl std::ops::Add for PavedPath {
    type Output = Self;

    /// Wrapper around [`impl std::ops::AddAssign`](#impl-AddAssign-for-PavedPath)
    ///
    /// See [`impl std::ops::AddAssign`](#impl-AddAssign-for-PavedPath) for more information
    fn add(self, rhs: Self) -> Self::Output {
        let mut paved = self;
        paved += rhs;

        paved
    }
}

impl std::ops::AddAssign for PavedPath {
    /// Extends [`Self`] with the directories of the rhs
    ///
    /// If [Self] does not have a file, it will add the file of rhs, otherwise it will preserve the file of [Self]
    fn add_assign(&mut self, rhs: Self) {
        self.extend(rhs.get_dirs());

        if !self.has_file()
            && let Some(file) = rhs.get_file()
        {
            self.file = Some(file.to_os_string());
            self.rebuild_inner();
        }
    }
}