use std::borrow::Borrow;
use std::path::{Component, Path, PathBuf};
use std::str::FromStr;
use thiserror::Error;
const PREFIX: &str = "../../";
#[cfg(feature = "serde")]
use serde_with::{DeserializeFromStr, SerializeDisplay};
#[derive(Debug, Eq, Error, Ord, PartialEq, PartialOrd)]
pub enum PkgPathError {
#[error("Invalid path specified")]
InvalidPath,
}
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
pub struct PkgPath {
full: String,
}
impl PkgPath {
pub fn new(path: &str) -> Result<Self, PkgPathError> {
let p = PathBuf::from(path);
let c: Vec<_> = p.components().collect();
let (cat, pkg) = match c.len() {
2 => match (c[0], c[1]) {
(Component::Normal(cat), Component::Normal(pkg)) => (cat, pkg),
_ => return Err(PkgPathError::InvalidPath),
},
4 => match (c[0], c[1], c[2], c[3]) {
(
Component::ParentDir,
Component::ParentDir,
Component::Normal(cat),
Component::Normal(pkg),
) => (cat, pkg),
_ => return Err(PkgPathError::InvalidPath),
},
_ => return Err(PkgPathError::InvalidPath),
};
let cat = cat.to_str().ok_or(PkgPathError::InvalidPath)?;
let pkg = pkg.to_str().ok_or(PkgPathError::InvalidPath)?;
Ok(PkgPath {
full: format!("{PREFIX}{cat}/{pkg}"),
})
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.full[PREFIX.len()..]
}
pub fn as_path(&self) -> &Path {
Path::new(self.as_str())
}
#[must_use]
pub fn as_full_str(&self) -> &str {
&self.full
}
pub fn as_full_path(&self) -> &Path {
Path::new(&self.full)
}
}
impl std::fmt::Display for PkgPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for PkgPath {
type Err = PkgPathError;
fn from_str(s: &str) -> Result<Self, PkgPathError> {
PkgPath::new(s)
}
}
impl AsRef<Path> for PkgPath {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl crate::kv::FromKv for PkgPath {
fn from_kv(value: &str, span: crate::kv::Span) -> crate::kv::Result<Self> {
Self::new(value).map_err(|e| crate::kv::KvError::Parse {
message: e.to_string(),
span,
})
}
}
impl Borrow<Path> for PkgPath {
fn borrow(&self) -> &Path {
self.as_path()
}
}
impl TryFrom<&str> for PkgPath {
type Error = PkgPathError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::new(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsStr;
fn assert_valid_foobar(s: &str) -> Result<(), PkgPathError> {
let p = PkgPath::new(s)?;
assert_eq!(p.as_path(), OsStr::new("foo/bar"));
assert_eq!(p.as_full_path(), OsStr::new("../../foo/bar"));
Ok(())
}
#[test]
fn pkgpath_test_good_input() -> Result<(), PkgPathError> {
assert_valid_foobar("foo/bar")?;
assert_valid_foobar("foo//bar")?;
assert_valid_foobar("foo//bar//")?;
assert_valid_foobar("../../foo/bar")?;
assert_valid_foobar("../../foo/bar/")?;
assert_valid_foobar("..//..//foo//bar//")?;
Ok(())
}
#[test]
fn pkgpath_test_bad_input() {
let err = Err(PkgPathError::InvalidPath);
assert_eq!(PkgPath::new(""), err);
assert_eq!(PkgPath::new("\0"), err);
assert_eq!(PkgPath::new("foo"), err);
assert_eq!(PkgPath::new("foo/"), err);
assert_eq!(PkgPath::new("./foo"), err);
assert_eq!(PkgPath::new("./foo/"), err);
assert_eq!(PkgPath::new("../foo"), err);
assert_eq!(PkgPath::new("../foo/"), err);
assert_eq!(PkgPath::new("../foo/bar"), err);
assert_eq!(PkgPath::new("../foo/bar/"), err);
assert_eq!(PkgPath::new("../foo/bar/ojnk"), err);
assert_eq!(PkgPath::new("../foo/bar/ojnk/"), err);
assert_eq!(PkgPath::new("../.."), err);
assert_eq!(PkgPath::new("../../"), err);
assert_eq!(PkgPath::new("../../foo"), err);
assert_eq!(PkgPath::new("../../foo/"), err);
assert_eq!(PkgPath::new("../../foo/bar/ojnk"), err);
assert_eq!(PkgPath::new("../../foo/bar/ojnk/"), err);
assert_eq!(PkgPath::new(".. /../foo/bar"), err);
}
#[test]
fn pkgpath_as_ref() -> Result<(), PkgPathError> {
let p = PkgPath::new("pkgtools/pkg_install")?;
let path: &Path = p.as_ref();
assert_eq!(path, Path::new("pkgtools/pkg_install"));
fn takes_asref(p: impl AsRef<Path>) -> bool {
p.as_ref().starts_with("pkgtools")
}
assert!(takes_asref(&p));
use std::borrow::Borrow;
let path: &Path = p.borrow();
assert_eq!(path, Path::new("pkgtools/pkg_install"));
let p: PkgPath = "devel/gmake".try_into()?;
assert_eq!(p.as_path(), OsStr::new("devel/gmake"));
Ok(())
}
}