151 lines
4.4 KiB
Rust
151 lines
4.4 KiB
Rust
use owo_colors::{OwoColorize, Stream::Stdout};
|
|
use serde::{de::Visitor, Deserialize, Serialize};
|
|
use std::{
|
|
fmt::{self, Display},
|
|
str::FromStr,
|
|
};
|
|
use thiserror::Error;
|
|
|
|
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
|
pub struct PackageName {
|
|
pub owner: String,
|
|
pub repo: String,
|
|
}
|
|
|
|
impl PackageName {
|
|
fn validate(&self) -> Result<(), Error> {
|
|
let r = regex::Regex::new("^[a-z0-9_-]+$").expect("regex could not be compiled");
|
|
|
|
if !(r.is_match(&self.owner) && r.is_match(&self.repo)) {
|
|
return Err(Error::InvalidProjectName {
|
|
reason: InvalidProjectNameReason::Format,
|
|
name: self.to_string(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl FromStr for PackageName {
|
|
type Err = Error;
|
|
|
|
fn from_str(name: &str) -> Result<Self, Error> {
|
|
let mut name_split = name.split('/');
|
|
let owner = name_split
|
|
.next()
|
|
.ok_or_else(|| Error::InvalidProjectName {
|
|
name: name.to_string(),
|
|
reason: InvalidProjectNameReason::Format,
|
|
})?
|
|
.to_string();
|
|
let repo = name_split
|
|
.next()
|
|
.ok_or_else(|| Error::InvalidProjectName {
|
|
name: name.to_string(),
|
|
reason: InvalidProjectNameReason::Format,
|
|
})?
|
|
.to_string();
|
|
let package_name = PackageName { owner, repo };
|
|
package_name.validate()?;
|
|
Ok(package_name)
|
|
}
|
|
}
|
|
|
|
impl Display for PackageName {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}/{}", self.owner, self.repo)
|
|
}
|
|
}
|
|
|
|
impl Serialize for PackageName {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&self.to_string())
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for PackageName {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
struct PackageNameVisitor;
|
|
|
|
impl<'de> Visitor<'de> for PackageNameVisitor {
|
|
type Value = PackageName;
|
|
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
formatter
|
|
.write_str("a string representing an owner and repo, ex: aiken-lang/stdlib")
|
|
}
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: serde::de::Error,
|
|
{
|
|
let mut name = v.split('/');
|
|
|
|
let owner = name.next().ok_or_else(|| {
|
|
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self)
|
|
})?;
|
|
|
|
let repo = name.next().ok_or_else(|| {
|
|
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self)
|
|
})?;
|
|
|
|
Ok(PackageName {
|
|
owner: owner.to_string(),
|
|
repo: repo.to_string(),
|
|
})
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_str(PackageNameVisitor)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error, miette::Diagnostic)]
|
|
pub enum Error {
|
|
#[error(
|
|
"{} is not a valid project name: {}",
|
|
name.if_supports_color(Stdout, |s| s.red()),
|
|
reason.to_string()
|
|
)]
|
|
InvalidProjectName {
|
|
name: String,
|
|
reason: InvalidProjectNameReason,
|
|
},
|
|
#[error(
|
|
"A project named {} already exists.",
|
|
name.if_supports_color(Stdout, |s| s.red())
|
|
)]
|
|
ProjectExists { name: String },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum InvalidProjectNameReason {
|
|
Reserved,
|
|
Format,
|
|
}
|
|
|
|
impl fmt::Display for InvalidProjectNameReason {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
InvalidProjectNameReason::Reserved => write!(f, "It's a reserved word in Aiken."),
|
|
InvalidProjectNameReason::Format => write!(
|
|
f,
|
|
"It is malformed.\n\nProjects must be named as:\n\n\t\
|
|
{}/{}\n\nEach part must start with a lowercase letter \
|
|
and may only contain lowercase letters, numbers, hyphens or underscores.\
|
|
\nFor example,\n\n\t{}",
|
|
"{owner}".if_supports_color(Stdout, |s| s.bright_blue()),
|
|
"{project}".if_supports_color(Stdout, |s| s.bright_blue()),
|
|
"aiken-lang/stdlib".if_supports_color(Stdout, |s| s.bright_blue()),
|
|
),
|
|
}
|
|
}
|
|
}
|