194 lines
6 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|