Add new 'deps add' command
This makes it easier to add new dependencies, without having to manually edit the `aiken.toml` file. The command is accessible via two different paths: - aiken deps add or simply - aiken add for this is quite common to find at the top-level of the command-line, and, we still want to keep commands for managing dependencies grouped under a command sub-group and not all at the top-level. So we're merely promoting that one for visibility.
This commit is contained in:
parent
d4f905b1db
commit
38df9a586e
|
@ -2,17 +2,13 @@ use crate::{package_name::PackageName, Error};
|
||||||
use aiken_lang::ast::Span;
|
use aiken_lang::ast::Span;
|
||||||
use miette::NamedSource;
|
use miette::NamedSource;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{fmt::Display, fs, io, path::Path};
|
||||||
fmt::Display,
|
|
||||||
fs, io,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub name: PackageName,
|
pub name: PackageName,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub license: String,
|
pub license: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub repository: Option<Repository>,
|
pub repository: Option<Repository>,
|
||||||
|
@ -57,7 +53,7 @@ impl Config {
|
||||||
Config {
|
Config {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
version: "0.0.0".to_string(),
|
version: "0.0.0".to_string(),
|
||||||
license: "Apache-2.0".to_string(),
|
license: Some("Apache-2.0".to_string()),
|
||||||
description: format!("Aiken contracts for project '{name}'"),
|
description: format!("Aiken contracts for project '{name}'"),
|
||||||
repository: Some(Repository {
|
repository: Some(Repository {
|
||||||
user: name.owner.clone(),
|
user: name.owner.clone(),
|
||||||
|
@ -81,10 +77,11 @@ impl Config {
|
||||||
fs::write(aiken_toml_path, aiken_toml)
|
fs::write(aiken_toml_path, aiken_toml)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(dir: PathBuf) -> Result<Config, Error> {
|
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(),
|
||||||
|
@ -100,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -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::deps::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.",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,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,
|
||||||
|
|
|
@ -13,22 +13,24 @@ pub struct PackageName {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackageName {
|
impl PackageName {
|
||||||
fn validate(&self) -> Result<(), Error> {
|
pub fn restrict(&self) -> Result<(), Error> {
|
||||||
let name = format!("{}/{}", self.owner, self.repo);
|
|
||||||
|
|
||||||
if self.owner.starts_with("aiken") {
|
if self.owner.starts_with("aiken") {
|
||||||
return Err(Error::InvalidProjectName {
|
return Err(Error::InvalidProjectName {
|
||||||
reason: InvalidProjectNameReason::Reserved,
|
reason: InvalidProjectNameReason::Reserved,
|
||||||
name,
|
name: self.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self) -> Result<(), Error> {
|
||||||
let r = regex::Regex::new("^[a-z0-9_-]+$").expect("regex could not be compiled");
|
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)) {
|
if !(r.is_match(&self.owner) && r.is_match(&self.repo)) {
|
||||||
return Err(Error::InvalidProjectName {
|
return Err(Error::InvalidProjectName {
|
||||||
reason: InvalidProjectNameReason::Format,
|
reason: InvalidProjectNameReason::Format,
|
||||||
name,
|
name: self.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
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.
|
||||||
|
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<()> {
|
||||||
|
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("Adding".to_string(), 13, " ")
|
||||||
|
.bold()
|
||||||
|
.purple(),
|
||||||
|
dependency.name.bright_blue(),
|
||||||
|
);
|
||||||
|
|
||||||
|
match config.insert(&dependency, false) {
|
||||||
|
Some(config) => {
|
||||||
|
config.save(&root).into_diagnostic()?;
|
||||||
|
println!(
|
||||||
|
"{} version = {}",
|
||||||
|
pretty::pad_left("Added".to_string(), 13, " ")
|
||||||
|
.bold()
|
||||||
|
.purple(),
|
||||||
|
dependency.version.yellow()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let warning = Warning::DependencyAlreadyExists {
|
||||||
|
name: dependency.name,
|
||||||
|
};
|
||||||
|
warning.report();
|
||||||
|
process::exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod add;
|
||||||
pub mod clear_cache;
|
pub mod clear_cache;
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
@ -6,12 +7,16 @@ 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 dependency
|
||||||
|
Add(add::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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ pub fn exec(args: Args) -> miette::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()> {
|
fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()> {
|
||||||
|
package_name.restrict().into_diagnostic()?;
|
||||||
|
|
||||||
let root = PathBuf::from(&package_name.repo);
|
let root = PathBuf::from(&package_name.repo);
|
||||||
|
|
||||||
if root.exists() {
|
if root.exists() {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use aiken::cmd::{build, check, deps, docs, fmt, lsp, new, tx, uplc};
|
use aiken::cmd::{
|
||||||
|
build, check,
|
||||||
|
deps::{self, add},
|
||||||
|
docs, fmt, lsp, new, 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,6 +16,7 @@ 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),
|
Deps(deps::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::Deps(args) => deps::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