use std::error::Error; use std::fmt::Formatter; use std::str::FromStr; use regex::Regex; use serde::Serializer; #[derive(Debug, Clone)] pub enum PackageIdentifierError { InvalidPackageLocator(String), InvalidURI(String), InvalidPath(String), } impl std::fmt::Display for PackageIdentifierError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { PackageIdentifierError::InvalidPackageLocator(s) => write!(f, "Invalid package locator: {}", s), PackageIdentifierError::InvalidURI(s) => write!(f, "Invalid URI: {}", s), PackageIdentifierError::InvalidPath(s) => write!(f, "Invalid path: {}", s), } } } impl Error for PackageIdentifierError {} #[derive(Clone)] pub enum PackageIdentifier { PackageLocator(PackageLocator), URI(String), Path(String), } impl std::fmt::Debug for PackageIdentifier { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { match self { PackageIdentifier::PackageLocator(pl) => write!(f, "PackageLocator({:#?})", pl), PackageIdentifier::URI(uri) => write!(f, "URI({:?})", uri), PackageIdentifier::Path(path) => write!(f, "Path({:?})", path), } } else { match self { PackageIdentifier::PackageLocator(pl) => write!(f, "PL: {:?}", pl), PackageIdentifier::URI(uri) => write!(f, "URI: {:?}", uri), PackageIdentifier::Path(path) => write!(f, "Path: {:?}", path), } } } } impl std::fmt::Display for PackageIdentifier { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { PackageIdentifier::PackageLocator(pl) => write!(f, "{}", pl), PackageIdentifier::URI(uri) => write!(f, "{}", uri), PackageIdentifier::Path(path) => write!(f, "{}", path), } } } #[derive(Debug, Clone)] pub struct PackageLocator { pub name: String, pub version: Option, pub tags: Option>, } impl std::fmt::Display for PackageLocator { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let mut s = self.name.clone(); if let Some(version) = self.version { s += &*format!("@{}", version); } if let Some(tags) = &self.tags { s += &*format!(":{}", tags.join(",")); } write!(f, "{}", s) } } impl FromStr for PackageIdentifier { type Err = PackageIdentifierError; fn from_str(s: &str) -> Result { let uri_re = Regex::new(r"^[a-zA-Z0-9]+://").unwrap(); if uri_re.is_match(s) { // there needs to be stuff after the protocol let split = s.split("://").collect::>(); if split.len() != 2 || split[1].is_empty() { return Err(PackageIdentifierError::InvalidURI(s.to_string())); } Ok(PackageIdentifier::URI(s.to_string())) } else if s.starts_with("/") || s.starts_with("./") || s.starts_with("../") || s.starts_with("~/") { let path = std::path::Path::new(s); if s.ends_with("/") || !path.exists() || path.is_dir() { return Err(PackageIdentifierError::InvalidPath(s.to_string())); } return Ok(PackageIdentifier::Path(s.to_string())); } else { let pl = match PackageLocator::from_str(s) { Ok(pl) => pl, Err(e) => return Err(PackageIdentifierError::InvalidPackageLocator(e.to_string())), }; Ok(PackageIdentifier::PackageLocator(pl)) } } } impl FromStr for PackageLocator { type Err = PackageIdentifierError; fn from_str(s: &str) -> Result { #[allow(unused_assignments)] // false positive let mut name = None; let mut version = None; let mut tags = None; if s.is_empty() { return Err(PackageIdentifierError::InvalidPackageLocator(s.to_string())); } let name_re = Regex::new("^([A-Za-z][A-Za-z0-9_.]+)").unwrap(); let version_re = Regex::new("@([0-9]+)").unwrap(); let tags_re = Regex::new(":([a-zA-Z0-9,._]+)").unwrap(); if let Some(caps) = name_re.captures(s) { name = Some(caps.get(1).unwrap().as_str().to_string()); } else { return Err(PackageIdentifierError::InvalidPackageLocator(s.to_string())); } if let Some(caps) = version_re.captures(s) { version = Some(caps.get(1).unwrap().as_str().parse::().unwrap()); } if let Some(caps) = tags_re.captures(s) { tags = Some( caps.get(1) .unwrap() .as_str() .split(",") .map(|s| s.to_string()) .collect(), ); } Ok(PackageLocator { name: name.unwrap(), version, tags, }) } } impl From<(String, String)> for PackageLocator { fn from((name, locate_str): (String, String)) -> Self { // name = "pkg" // locate_str = "1.0.0:tag1,tag2" or "1.0.0" or "tag1,tag2" let mut version = None; let mut tags = None; let version_re = Regex::new("^([0-9]+)").unwrap(); let tags_re = Regex::new("^:([a-zA-Z0-9,._]+)").unwrap(); if let Some(caps) = version_re.captures(locate_str.as_str()) { version = Some(caps.get(1).unwrap().as_str().parse::().unwrap()); } if let Some(caps) = tags_re.captures(locate_str.as_str()) { tags = Some( caps.get(1) .unwrap() .as_str() .split(",") .map(|s| s.to_string()) .collect(), ); } PackageLocator { name, version, tags, } } }