packager/pkgr/src/package/identifier.rs

194 lines
6 KiB
Rust

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<u32>,
pub tags: Option<Vec<String>>,
}
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<Self, Self::Err> {
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::<Vec<&str>>();
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<Self, Self::Err> {
#[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::<u32>().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::<u32>().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,
}
}
}