Merge pull request #259 from aiken-lang/basic-add-upgrade-packages
Basic commands for manipulating the aiken.toml
This commit is contained in:
commit
8ac2d75ef1
|
@ -119,6 +119,7 @@ dependencies = [
|
||||||
"ignore",
|
"ignore",
|
||||||
"itertools",
|
"itertools",
|
||||||
"miette",
|
"miette",
|
||||||
|
"owo-colors",
|
||||||
"pallas",
|
"pallas",
|
||||||
"pallas-traverse",
|
"pallas-traverse",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
|
|
|
@ -18,6 +18,7 @@ hex = "0.4.3"
|
||||||
ignore = "0.4.18"
|
ignore = "0.4.18"
|
||||||
itertools = "0.10.1"
|
itertools = "0.10.1"
|
||||||
miette = { version = "5.3.0", features = ["fancy"] }
|
miette = { version = "5.3.0", features = ["fancy"] }
|
||||||
|
owo-colors = "3.5.0"
|
||||||
pallas = "0.16.0"
|
pallas = "0.16.0"
|
||||||
pallas-traverse = "0.16.0"
|
pallas-traverse = "0.16.0"
|
||||||
petgraph = "0.6.2"
|
petgraph = "0.6.2"
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::error::Error;
|
use crate::{package_name::PackageName, Error};
|
||||||
use aiken_lang::ast::Span;
|
use aiken_lang::ast::Span;
|
||||||
use miette::NamedSource;
|
use miette::NamedSource;
|
||||||
use serde::{de::Visitor, Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt::Display, fs, path::PathBuf};
|
use std::{fmt::Display, fs, io, path::Path};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub name: PackageName,
|
pub name: PackageName,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
pub license: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub repository: Option<Repository>,
|
pub repository: Option<Repository>,
|
||||||
|
@ -15,7 +16,7 @@ pub struct Config {
|
||||||
pub dependencies: Vec<Dependency>,
|
pub dependencies: Vec<Dependency>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Repository {
|
pub struct Repository {
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub project: String,
|
pub project: String,
|
||||||
|
@ -37,67 +38,6 @@ pub struct Dependency {
|
||||||
pub source: Platform,
|
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 {
|
impl Display for Platform {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -109,10 +49,39 @@ impl Display for Platform {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load(dir: PathBuf) -> Result<Config, Error> {
|
pub fn default(name: &PackageName) -> Self {
|
||||||
|
Config {
|
||||||
|
name: name.clone(),
|
||||||
|
version: "0.0.0".to_string(),
|
||||||
|
license: Some("Apache-2.0".to_string()),
|
||||||
|
description: format!("Aiken contracts for project '{name}'"),
|
||||||
|
repository: Some(Repository {
|
||||||
|
user: name.owner.clone(),
|
||||||
|
project: name.repo.clone(),
|
||||||
|
platform: Platform::Github,
|
||||||
|
}),
|
||||||
|
dependencies: vec![Dependency {
|
||||||
|
name: PackageName {
|
||||||
|
owner: "aiken-lang".to_string(),
|
||||||
|
repo: "stdlib".to_string(),
|
||||||
|
},
|
||||||
|
version: "main".to_string(),
|
||||||
|
source: Platform::Github,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self, dir: &Path) -> Result<(), io::Error> {
|
||||||
|
let aiken_toml_path = dir.join("aiken.toml");
|
||||||
|
let aiken_toml = toml::to_string_pretty(self).unwrap();
|
||||||
|
fs::write(aiken_toml_path, aiken_toml)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(dir: &Path) -> Result<Config, Error> {
|
||||||
let config_path = dir.join("aiken.toml");
|
let config_path = dir.join("aiken.toml");
|
||||||
let raw_config = fs::read_to_string(&config_path)
|
let raw_config = fs::read_to_string(&config_path).map_err(|_| Error::MissingManifest {
|
||||||
.map_err(|_| Error::MissingManifest { path: dir.clone() })?;
|
path: dir.to_path_buf(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let result: Self = toml::from_str(&raw_config).map_err(|e| Error::TomlLoading {
|
let result: Self = toml::from_str(&raw_config).map_err(|e| Error::TomlLoading {
|
||||||
path: config_path.clone(),
|
path: config_path.clone(),
|
||||||
|
@ -128,4 +97,19 @@ impl Config {
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert(mut self, dependency: &Dependency, and_replace: bool) -> Option<Self> {
|
||||||
|
for mut existing in self.dependencies.iter_mut() {
|
||||||
|
if existing.name == dependency.name {
|
||||||
|
return if and_replace {
|
||||||
|
existing.version = dependency.version.clone();
|
||||||
|
Some(self)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.dependencies.push(dependency.clone());
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, Dependency, PackageName},
|
config::{Config, Dependency},
|
||||||
error::Error,
|
error::Error,
|
||||||
|
package_name::PackageName,
|
||||||
paths,
|
paths,
|
||||||
telemetry::{Event, EventListener},
|
telemetry::{Event, EventListener},
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,8 +4,8 @@ use futures::future;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::PackageName,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
|
package_name::PackageName,
|
||||||
paths::{self, CacheKey},
|
paths::{self, CacheKey},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@ use miette::NamedSource;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, Dependency, PackageName, Platform},
|
config::{Config, Dependency, Platform},
|
||||||
error::Error,
|
error::Error,
|
||||||
|
package_name::PackageName,
|
||||||
paths,
|
paths,
|
||||||
telemetry::{Event, EventListener},
|
telemetry::{Event, EventListener},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{deps::manifest::Package, pretty, script::EvalHint};
|
use crate::{deps::manifest::Package, package_name::PackageName, pretty, script::EvalHint};
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::{BinOp, Span},
|
ast::{BinOp, Span},
|
||||||
parser::error::ParseError,
|
parser::error::ParseError,
|
||||||
|
@ -264,10 +264,10 @@ impl Diagnostic for Error {
|
||||||
Error::ValidatorMustReturnBool { .. } => Some(Box::new("aiken::scripts")),
|
Error::ValidatorMustReturnBool { .. } => Some(Box::new("aiken::scripts")),
|
||||||
Error::WrongValidatorArity { .. } => Some(Box::new("aiken::validators")),
|
Error::WrongValidatorArity { .. } => Some(Box::new("aiken::validators")),
|
||||||
Error::TestFailure { path, .. } => Some(Box::new(path.to_str().unwrap_or(""))),
|
Error::TestFailure { path, .. } => Some(Box::new(path.to_str().unwrap_or(""))),
|
||||||
Error::Http(_) => Some(Box::new("aiken::deps")),
|
Error::Http(_) => Some(Box::new("aiken::packages::download")),
|
||||||
Error::ZipExtract(_) => None,
|
Error::ZipExtract(_) => None,
|
||||||
Error::JoinError(_) => None,
|
Error::JoinError(_) => None,
|
||||||
Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::deps")),
|
Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::packages::resolve")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,6 +418,8 @@ pub enum Warning {
|
||||||
#[source]
|
#[source]
|
||||||
warning: tipo::error::Warning,
|
warning: tipo::error::Warning,
|
||||||
},
|
},
|
||||||
|
#[error("{name} is already a dependency.")]
|
||||||
|
DependencyAlreadyExists { name: PackageName },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for Warning {
|
impl Diagnostic for Warning {
|
||||||
|
@ -429,6 +431,7 @@ impl Diagnostic for Warning {
|
||||||
match self {
|
match self {
|
||||||
Warning::Type { named, .. } => Some(named),
|
Warning::Type { named, .. } => Some(named),
|
||||||
Warning::NoValidators => None,
|
Warning::NoValidators => None,
|
||||||
|
Warning::DependencyAlreadyExists { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,6 +439,7 @@ impl Diagnostic for Warning {
|
||||||
match self {
|
match self {
|
||||||
Warning::Type { warning, .. } => warning.labels(),
|
Warning::Type { warning, .. } => warning.labels(),
|
||||||
Warning::NoValidators => None,
|
Warning::NoValidators => None,
|
||||||
|
Warning::DependencyAlreadyExists { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,6 +447,19 @@ impl Diagnostic for Warning {
|
||||||
match self {
|
match self {
|
||||||
Warning::Type { .. } => Some(Box::new("aiken::check")),
|
Warning::Type { .. } => Some(Box::new("aiken::check")),
|
||||||
Warning::NoValidators => Some(Box::new("aiken::check")),
|
Warning::NoValidators => Some(Box::new("aiken::check")),
|
||||||
|
Warning::DependencyAlreadyExists { .. } => {
|
||||||
|
Some(Box::new("aiken::packages::already_exists"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
match self {
|
||||||
|
Warning::Type { .. } => None,
|
||||||
|
Warning::NoValidators => None,
|
||||||
|
Warning::DependencyAlreadyExists { .. } => Some(Box::new(
|
||||||
|
"If you need to change the version, try 'aiken packages upgrade' instead.",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod error;
|
||||||
pub mod format;
|
pub mod format;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
pub mod options;
|
pub mod options;
|
||||||
|
pub mod package_name;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
pub mod pretty;
|
pub mod pretty;
|
||||||
pub mod script;
|
pub mod script;
|
||||||
|
@ -18,10 +19,10 @@ use aiken_lang::{
|
||||||
uplc::CodeGenerator,
|
uplc::CodeGenerator,
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
use config::PackageName;
|
|
||||||
use deps::UseManifest;
|
use deps::UseManifest;
|
||||||
use miette::NamedSource;
|
use miette::NamedSource;
|
||||||
use options::{CodeGenMode, Options};
|
use options::{CodeGenMode, Options};
|
||||||
|
use package_name::PackageName;
|
||||||
use pallas::{
|
use pallas::{
|
||||||
codec::minicbor,
|
codec::minicbor,
|
||||||
ledger::{addresses::Address, primitives::babbage},
|
ledger::{addresses::Address, primitives::babbage},
|
||||||
|
@ -85,7 +86,7 @@ where
|
||||||
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
|
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
|
||||||
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
|
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
|
||||||
|
|
||||||
let config = Config::load(root.clone())?;
|
let config = Config::load(&root)?;
|
||||||
|
|
||||||
Ok(Project {
|
Ok(Project {
|
||||||
config,
|
config,
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
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 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.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::deps::manifest::Package;
|
||||||
use crate::{config::PackageName, error::Error};
|
use crate::{error::Error, package_name::PackageName};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use std::path::PathBuf;
|
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(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,9 @@
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod check;
|
pub mod check;
|
||||||
pub mod deps;
|
|
||||||
pub mod docs;
|
pub mod docs;
|
||||||
pub mod error;
|
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
pub mod lsp;
|
pub mod lsp;
|
||||||
pub mod new;
|
pub mod new;
|
||||||
|
pub mod packages;
|
||||||
pub mod tx;
|
pub mod tx;
|
||||||
pub mod uplc;
|
pub mod uplc;
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
use aiken_project::config::PackageName;
|
use aiken_project::{
|
||||||
|
config::Config,
|
||||||
|
package_name::{self, PackageName},
|
||||||
|
};
|
||||||
use indoc::{formatdoc, indoc};
|
use indoc::{formatdoc, indoc};
|
||||||
use miette::IntoDiagnostic;
|
use miette::IntoDiagnostic;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use std::path::PathBuf;
|
use std::{
|
||||||
use std::{fs, path::Path};
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
use super::error::{Error, InvalidProjectNameReason};
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
/// Create a new Aiken project
|
/// Create a new Aiken project
|
||||||
|
@ -18,14 +22,9 @@ pub struct Args {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(args: Args) -> miette::Result<()> {
|
pub fn exec(args: Args) -> miette::Result<()> {
|
||||||
validate_name(&args.name)?;
|
let package_name = PackageName::from_str(&args.name).into_diagnostic()?;
|
||||||
|
|
||||||
let package_name = package_name_from_str(&args.name)?;
|
|
||||||
|
|
||||||
create_project(args, &package_name)?;
|
create_project(args, &package_name)?;
|
||||||
|
|
||||||
print_success_message(&package_name);
|
print_success_message(&package_name);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +32,7 @@ fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()>
|
||||||
let root = PathBuf::from(&package_name.repo);
|
let root = PathBuf::from(&package_name.repo);
|
||||||
|
|
||||||
if root.exists() {
|
if root.exists() {
|
||||||
Err(Error::ProjectExists {
|
Err(package_name::Error::ProjectExists {
|
||||||
name: package_name.repo.clone(),
|
name: package_name.repo.clone(),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -44,10 +43,12 @@ fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()>
|
||||||
create_validators_folder(&root)?;
|
create_validators_folder(&root)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
aiken_toml(&root, package_name)?;
|
|
||||||
|
|
||||||
readme(&root, &package_name.repo)?;
|
readme(&root, &package_name.repo)?;
|
||||||
|
|
||||||
|
Config::default(package_name)
|
||||||
|
.save(&root)
|
||||||
|
.into_diagnostic()?;
|
||||||
|
|
||||||
gitignore(&root)?;
|
gitignore(&root)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -85,28 +86,6 @@ fn create_validators_folder(root: &Path) -> miette::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn aiken_toml(root: &Path, package_name: &PackageName) -> miette::Result<()> {
|
|
||||||
let aiken_toml_path = root.join("aiken.toml");
|
|
||||||
|
|
||||||
fs::write(
|
|
||||||
aiken_toml_path,
|
|
||||||
formatdoc! {
|
|
||||||
r#"
|
|
||||||
name = "{name}"
|
|
||||||
version = "0.0.0"
|
|
||||||
licences = ["Apache-2.0"]
|
|
||||||
description = "Aiken contracts for project '{name}'"
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
{{ name = "aiken-lang/stdlib", version = "main", source = "github" }},
|
|
||||||
]
|
|
||||||
"#,
|
|
||||||
name = package_name.to_string(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.into_diagnostic()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readme(root: &Path, project_name: &str) -> miette::Result<()> {
|
fn readme(root: &Path, project_name: &str) -> miette::Result<()> {
|
||||||
fs::write(
|
fs::write(
|
||||||
root.join("README.md"),
|
root.join("README.md"),
|
||||||
|
@ -198,49 +177,3 @@ fn gitignore(root: &Path) -> miette::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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 })
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
use aiken_project::{
|
||||||
|
config::{Config, Dependency, Platform},
|
||||||
|
error::Warning,
|
||||||
|
package_name::PackageName,
|
||||||
|
pretty,
|
||||||
|
};
|
||||||
|
use miette::IntoDiagnostic;
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
|
use std::{path::PathBuf, process, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
/// Add a new project package as dependency
|
||||||
|
pub struct Args {
|
||||||
|
/// Package name, in the form of {owner}/{repository}.
|
||||||
|
///
|
||||||
|
/// For example → 'add aiken-lang/stdlib'
|
||||||
|
///
|
||||||
|
/// Note that by default, this assumes the package is located
|
||||||
|
/// on Github.
|
||||||
|
pub package: String,
|
||||||
|
/// The package version, as a git commit hash, a tag or a branch name.
|
||||||
|
#[clap(long)]
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
#[clap(hide = true)]
|
||||||
|
pub overwrite: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(args: Args) -> miette::Result<()> {
|
||||||
|
let root = PathBuf::from(".");
|
||||||
|
|
||||||
|
let dependency = Dependency {
|
||||||
|
name: PackageName::from_str(&args.package)?,
|
||||||
|
version: args.version,
|
||||||
|
source: Platform::Github,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = match Config::load(&root) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
e.report();
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{} {}",
|
||||||
|
pretty::pad_left("Package".to_string(), 13, " ")
|
||||||
|
.bold()
|
||||||
|
.purple(),
|
||||||
|
dependency.name.bright_blue(),
|
||||||
|
);
|
||||||
|
|
||||||
|
match config.insert(&dependency, args.overwrite) {
|
||||||
|
Some(config) => {
|
||||||
|
config.save(&root).into_diagnostic()?;
|
||||||
|
println!(
|
||||||
|
"{} version → {}",
|
||||||
|
pretty::pad_left(
|
||||||
|
if args.overwrite { "Changed" } else { "Added" }.to_string(),
|
||||||
|
13,
|
||||||
|
" "
|
||||||
|
)
|
||||||
|
.bold()
|
||||||
|
.purple(),
|
||||||
|
dependency.version.yellow()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let warning = Warning::DependencyAlreadyExists {
|
||||||
|
name: dependency.name,
|
||||||
|
};
|
||||||
|
warning.report();
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
|
pub mod add;
|
||||||
pub mod clear_cache;
|
pub mod clear_cache;
|
||||||
|
pub mod upgrade;
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
@ -6,12 +8,20 @@ use clap::Subcommand;
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
|
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
|
||||||
pub enum Cmd {
|
pub enum Cmd {
|
||||||
|
/// Add a new package dependency
|
||||||
|
Add(add::Args),
|
||||||
|
|
||||||
|
/// Change the version of an installed dependency
|
||||||
|
Upgrade(upgrade::Args),
|
||||||
|
|
||||||
/// Clear the system-wide dependencies cache
|
/// Clear the system-wide dependencies cache
|
||||||
ClearCache,
|
ClearCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(cmd: Cmd) -> miette::Result<()> {
|
pub fn exec(cmd: Cmd) -> miette::Result<()> {
|
||||||
match cmd {
|
match cmd {
|
||||||
|
Cmd::Add(args) => add::exec(args),
|
||||||
Cmd::ClearCache => clear_cache::exec(),
|
Cmd::ClearCache => clear_cache::exec(),
|
||||||
|
Cmd::Upgrade(args) => upgrade::exec(args),
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
use super::add;
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
/// Change the version of an installed dependency
|
||||||
|
pub struct Args {
|
||||||
|
/// Package name, in the form of {owner}/{repository}.
|
||||||
|
///
|
||||||
|
/// For example → 'add aiken-lang/stdlib'
|
||||||
|
///
|
||||||
|
/// Note that by default, this assumes the package is located
|
||||||
|
/// on Github.
|
||||||
|
package: String,
|
||||||
|
/// The package version, as a git commit hash, a tag or a branch name.
|
||||||
|
#[clap(long)]
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(args: Args) -> miette::Result<()> {
|
||||||
|
add::exec(add::Args {
|
||||||
|
package: args.package,
|
||||||
|
version: args.version,
|
||||||
|
overwrite: true,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
use aiken::cmd::{build, check, deps, docs, fmt, lsp, new, tx, uplc};
|
use aiken::cmd::{
|
||||||
|
build, check, docs, fmt, lsp, new,
|
||||||
|
packages::{self, add},
|
||||||
|
tx, uplc,
|
||||||
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
/// Aiken: a smart-contract language and toolchain for Cardano
|
/// Aiken: a smart-contract language and toolchain for Cardano
|
||||||
|
@ -12,9 +16,10 @@ pub enum Cmd {
|
||||||
Build(build::Args),
|
Build(build::Args),
|
||||||
Check(check::Args),
|
Check(check::Args),
|
||||||
Docs(docs::Args),
|
Docs(docs::Args),
|
||||||
|
Add(add::Args),
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
Deps(deps::Cmd),
|
Packages(packages::Cmd),
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
Tx(tx::Cmd),
|
Tx(tx::Cmd),
|
||||||
|
@ -38,9 +43,10 @@ fn main() -> miette::Result<()> {
|
||||||
Cmd::New(args) => new::exec(args),
|
Cmd::New(args) => new::exec(args),
|
||||||
Cmd::Fmt(args) => fmt::exec(args),
|
Cmd::Fmt(args) => fmt::exec(args),
|
||||||
Cmd::Build(args) => build::exec(args),
|
Cmd::Build(args) => build::exec(args),
|
||||||
Cmd::Docs(args) => docs::exec(args),
|
|
||||||
Cmd::Deps(args) => deps::exec(args),
|
|
||||||
Cmd::Check(args) => check::exec(args),
|
Cmd::Check(args) => check::exec(args),
|
||||||
|
Cmd::Docs(args) => docs::exec(args),
|
||||||
|
Cmd::Add(args) => add::exec(args),
|
||||||
|
Cmd::Packages(args) => packages::exec(args),
|
||||||
Cmd::Lsp(args) => lsp::exec(args),
|
Cmd::Lsp(args) => lsp::exec(args),
|
||||||
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
|
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
|
||||||
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),
|
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),
|
||||||
|
|
Loading…
Reference in New Issue