feat: add package identifiers

This commit is contained in:
Strix 2023-10-14 22:39:40 +02:00
parent 6e30a8bed7
commit a0416cf6a9
No known key found for this signature in database
GPG key ID: 49B2E37B8915B774
5 changed files with 185 additions and 1 deletions

View file

@ -6,4 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fern = "0.6.2"
getopts = "0.2.21"
log = "0.4.19"
regex = "1.9.1"
manifest = { path = "../manifest" }

8
pkgr/src/commands/mod.rs Normal file
View file

@ -0,0 +1,8 @@
use crate::package::identifier::PackageIdentifier;
pub enum Command {
Grab(PackageIdentifier), // grab a package from a remote source
Remove(PackageIdentifier), // remove a package from the local source
List, // list all packages in the local source
Update,
}

View file

@ -1,2 +1,59 @@
use std::env;
use std::process::exit;
use std::str::FromStr;
use getopts::Options;
use log::{info, SetLoggerError};
mod commands;
mod package;
fn main() {
setup_logger()
.expect("Unable to setup logger.");
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
let mut opts = Options::new();
opts.optflag("h", "help", "Show help message.");
let matches = opts.parse(&args[1..]).unwrap_or_else(|e| panic!("{}", e.to_string()));
if matches.opt_present("h") {
print_usage(&program, opts);
exit(1);
}
let command = if !matches.free.is_empty() {
matches.free[0].clone()
} else {
print_usage(&program, opts);
exit(1);
};
info!("Identifier: {}", package::identifier::PackageIdentifier::from_str(&command).unwrap());
}
fn print_usage(program: &str, opts: Options) {
let mut brief = format!("Usage: {} <COMMAND> [options]\n\n", program);
brief += &*format!("Commands: grab \n");
brief += &*format!(" remove\n");
print!("{}", opts.usage(&brief.trim_end()));
}
fn setup_logger() -> Result<(), SetLoggerError> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{} {}",
match record.level().to_string().chars().nth(0).unwrap_or('I') {
'E' | 'W' => "!!",
_ => "**",
},
message
))
})
.level(log::LevelFilter::Info)
.chain(std::io::stdout())
.apply()
}

View file

@ -0,0 +1,114 @@
use std::str::FromStr;
use regex::Regex;
#[derive(Debug)]
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),
}
}
}
#[derive(Debug)]
pub enum PackageIdentifier {
PackageLocator(PackageLocator),
URI(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),
}
}
}
#[derive(Debug)]
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::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 {
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,
})
}
}

1
pkgr/src/package/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod identifier;