feat: add package identifiers
This commit is contained in:
parent
6e30a8bed7
commit
a0416cf6a9
5 changed files with 185 additions and 1 deletions
|
@ -6,4 +6,8 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
manifest = { path = "../manifest" }
|
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
8
pkgr/src/commands/mod.rs
Normal 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,
|
||||||
|
}
|
|
@ -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() {
|
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()
|
||||||
|
}
|
114
pkgr/src/package/identifier.rs
Normal file
114
pkgr/src/package/identifier.rs
Normal 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
1
pkgr/src/package/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod identifier;
|
Loading…
Reference in a new issue