Move 'PackageName' and associated methods in its own module.
This is a bit cleaner, as the 'cmd/new' had many on-the-fly functions which are better scoped inside this module. Plus, it plays nicely with the std::str::FromStr trait definition.
This commit is contained in:
parent
bfeb26c9a9
commit
0771ab24bd
|
@ -119,6 +119,7 @@ dependencies = [
|
|||
"ignore",
|
||||
"itertools",
|
||||
"miette",
|
||||
"owo-colors",
|
||||
"pallas",
|
||||
"pallas-traverse",
|
||||
"petgraph",
|
||||
|
|
|
@ -18,6 +18,7 @@ hex = "0.4.3"
|
|||
ignore = "0.4.18"
|
||||
itertools = "0.10.1"
|
||||
miette = { version = "5.3.0", features = ["fancy"] }
|
||||
owo-colors = "3.5.0"
|
||||
pallas = "0.16.0"
|
||||
pallas-traverse = "0.16.0"
|
||||
petgraph = "0.6.2"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::error::Error;
|
||||
use crate::{package_name::PackageName, Error};
|
||||
use aiken_lang::ast::Span;
|
||||
use miette::NamedSource;
|
||||
use serde::{de::Visitor, Deserialize, Serialize};
|
||||
|
@ -37,67 +37,6 @@ pub struct Dependency {
|
|||
pub source: Platform,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub struct PackageName {
|
||||
pub owner: String,
|
||||
pub repo: String,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Platform {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> {
|
||||
match *self {
|
||||
|
|
|
@ -6,8 +6,9 @@ use serde::{Deserialize, Serialize};
|
|||
use tokio::time::Instant;
|
||||
|
||||
use crate::{
|
||||
config::{Config, Dependency, PackageName},
|
||||
config::{Config, Dependency},
|
||||
error::Error,
|
||||
package_name::PackageName,
|
||||
paths,
|
||||
telemetry::{Event, EventListener},
|
||||
};
|
||||
|
|
|
@ -4,8 +4,8 @@ use futures::future;
|
|||
use reqwest::Client;
|
||||
|
||||
use crate::{
|
||||
config::PackageName,
|
||||
error::Error,
|
||||
package_name::PackageName,
|
||||
paths::{self, CacheKey},
|
||||
};
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ use miette::NamedSource;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
config::{Config, Dependency, PackageName, Platform},
|
||||
config::{Config, Dependency, Platform},
|
||||
error::Error,
|
||||
package_name::PackageName,
|
||||
paths,
|
||||
telemetry::{Event, EventListener},
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ pub mod error;
|
|||
pub mod format;
|
||||
pub mod module;
|
||||
pub mod options;
|
||||
pub mod package_name;
|
||||
pub mod paths;
|
||||
pub mod pretty;
|
||||
pub mod script;
|
||||
|
@ -18,10 +19,10 @@ use aiken_lang::{
|
|||
uplc::CodeGenerator,
|
||||
IdGenerator,
|
||||
};
|
||||
use config::PackageName;
|
||||
use deps::UseManifest;
|
||||
use miette::NamedSource;
|
||||
use options::{CodeGenMode, Options};
|
||||
use package_name::PackageName;
|
||||
use pallas::{
|
||||
codec::minicbor,
|
||||
ledger::{addresses::Address, primitives::babbage},
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
use owo_colors::OwoColorize;
|
||||
use serde::{de::Visitor, Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub struct PackageName {
|
||||
pub owner: String,
|
||||
pub repo: String,
|
||||
}
|
||||
|
||||
impl PackageName {
|
||||
fn validate(&self) -> Result<(), Error> {
|
||||
let name = format!("{}/{}", self.owner, self.repo);
|
||||
|
||||
if self.owner.starts_with("aiken") {
|
||||
return Err(Error::InvalidProjectName {
|
||||
reason: InvalidProjectNameReason::Reserved,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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.red(), reason.to_string())]
|
||||
InvalidProjectName {
|
||||
name: String,
|
||||
reason: InvalidProjectNameReason,
|
||||
},
|
||||
#[error("A project named {} already exists.", name.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}".bright_blue(),
|
||||
"{project}".bright_blue(),
|
||||
"aiken-lang/stdlib".bright_blue(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use crate::deps::manifest::Package;
|
||||
use crate::{config::PackageName, error::Error};
|
||||
use crate::{error::Error, package_name::PackageName};
|
||||
use reqwest::Client;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
use std::fmt;
|
||||
|
||||
use owo_colors::OwoColorize;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, miette::Diagnostic)]
|
||||
pub enum Error {
|
||||
#[error("{} is not a valid project name: {}", name.red(), reason.to_string())]
|
||||
InvalidProjectName {
|
||||
name: String,
|
||||
reason: InvalidProjectNameReason,
|
||||
},
|
||||
#[error("A project named {} already exists.", name.red())]
|
||||
ProjectExists { name: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum InvalidProjectNameReason {
|
||||
AikenPrefix,
|
||||
AikenReservedModule,
|
||||
Format,
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidProjectNameReason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
InvalidProjectNameReason::AikenPrefix => write!(f, "it's a reserved word in Aiken."),
|
||||
InvalidProjectNameReason::AikenReservedModule => {
|
||||
write!(f, "it's a reserved module name 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}".bright_blue(),
|
||||
"{project}".bright_blue(),
|
||||
"aiken-lang/stdlib".bright_blue(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ pub mod build;
|
|||
pub mod check;
|
||||
pub mod deps;
|
||||
pub mod docs;
|
||||
pub mod error;
|
||||
pub mod fmt;
|
||||
pub mod lsp;
|
||||
pub mod new;
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use aiken_project::config::PackageName;
|
||||
use aiken_project::{
|
||||
config::Config,
|
||||
package_name::{self, PackageName},
|
||||
};
|
||||
use indoc::{formatdoc, indoc};
|
||||
use miette::IntoDiagnostic;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use super::error::{Error, InvalidProjectNameReason};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
/// Create a new Aiken project
|
||||
|
@ -18,14 +22,9 @@ pub struct Args {
|
|||
}
|
||||
|
||||
pub fn exec(args: Args) -> miette::Result<()> {
|
||||
validate_name(&args.name)?;
|
||||
|
||||
let package_name = package_name_from_str(&args.name)?;
|
||||
|
||||
let package_name = PackageName::from_str(&args.name).into_diagnostic()?;
|
||||
create_project(args, &package_name)?;
|
||||
|
||||
print_success_message(&package_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -33,7 +32,7 @@ fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()>
|
|||
let root = PathBuf::from(&package_name.repo);
|
||||
|
||||
if root.exists() {
|
||||
Err(Error::ProjectExists {
|
||||
Err(package_name::Error::ProjectExists {
|
||||
name: package_name.repo.clone(),
|
||||
})?;
|
||||
}
|
||||
|
@ -198,49 +197,3 @@ fn gitignore(root: &Path) -> miette::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_name(name: &str) -> Result<(), Error> {
|
||||
if name.starts_with("aiken_") {
|
||||
Err(Error::InvalidProjectName {
|
||||
name: name.to_string(),
|
||||
reason: InvalidProjectNameReason::AikenPrefix,
|
||||
})
|
||||
} else if name == "aiken" {
|
||||
Err(Error::InvalidProjectName {
|
||||
name: name.to_string(),
|
||||
reason: InvalidProjectNameReason::AikenReservedModule,
|
||||
})
|
||||
} else if !regex::Regex::new("^[a-z0-9_-]+/[a-z0-9_-]+$")
|
||||
.expect("regex could not be compiled")
|
||||
.is_match(name)
|
||||
{
|
||||
Err(Error::InvalidProjectName {
|
||||
name: name.to_string(),
|
||||
reason: InvalidProjectNameReason::Format,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn package_name_from_str(name: &str) -> Result<PackageName, 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();
|
||||
|
||||
Ok(PackageName { owner, repo })
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue