use std::error::Error; use std::str::FromStr; use regex::Regex; #[derive(Debug, Clone)] pub enum PackageIdentifierError { InvalidPackageLocator(String), InvalidURI(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), } } } impl Error for PackageIdentifierError {} #[derive(Debug, Clone)] pub enum PackageIdentifier { PackageLocator(PackageLocator), URI(String), Path(String), } 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 std::path::Path::new(s).exists() { 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, }) } }