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:
KtorZ 2023-01-14 23:00:44 +01:00
parent d4f905b1db
commit 38df9a586e
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
8 changed files with 135 additions and 20 deletions

View File

@ -2,17 +2,13 @@ use crate::{package_name::PackageName, Error};
use aiken_lang::ast::Span;
use miette::NamedSource;
use serde::{Deserialize, Serialize};
use std::{
fmt::Display,
fs, io,
path::{Path, PathBuf},
};
use std::{fmt::Display, fs, io, path::Path};
#[derive(Deserialize, Serialize)]
pub struct Config {
pub name: PackageName,
pub version: String,
pub license: String,
pub license: Option<String>,
#[serde(default)]
pub description: String,
pub repository: Option<Repository>,
@ -57,7 +53,7 @@ impl Config {
Config {
name: name.clone(),
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}'"),
repository: Some(Repository {
user: name.owner.clone(),
@ -81,10 +77,11 @@ impl Config {
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 raw_config = fs::read_to_string(&config_path)
.map_err(|_| Error::MissingManifest { path: dir.clone() })?;
let raw_config = fs::read_to_string(&config_path).map_err(|_| Error::MissingManifest {
path: dir.to_path_buf(),
})?;
let result: Self = toml::from_str(&raw_config).map_err(|e| Error::TomlLoading {
path: config_path.clone(),
@ -100,4 +97,19 @@ impl Config {
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)
}
}

View File

@ -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::{
ast::{BinOp, Span},
parser::error::ParseError,
@ -418,6 +418,8 @@ pub enum Warning {
#[source]
warning: tipo::error::Warning,
},
#[error("{name} is already a dependency.")]
DependencyAlreadyExists { name: PackageName },
}
impl Diagnostic for Warning {
@ -429,6 +431,7 @@ impl Diagnostic for Warning {
match self {
Warning::Type { named, .. } => Some(named),
Warning::NoValidators => None,
Warning::DependencyAlreadyExists { .. } => None,
}
}
@ -436,6 +439,7 @@ impl Diagnostic for Warning {
match self {
Warning::Type { warning, .. } => warning.labels(),
Warning::NoValidators => None,
Warning::DependencyAlreadyExists { .. } => None,
}
}
@ -443,6 +447,19 @@ impl Diagnostic for Warning {
match self {
Warning::Type { .. } => 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.",
)),
}
}
}

View File

@ -86,7 +86,7 @@ where
module_types.insert("aiken".to_string(), builtins::prelude(&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 {
config,

View File

@ -13,22 +13,24 @@ pub struct PackageName {
}
impl PackageName {
fn validate(&self) -> Result<(), Error> {
let name = format!("{}/{}", self.owner, self.repo);
pub fn restrict(&self) -> Result<(), Error> {
if self.owner.starts_with("aiken") {
return Err(Error::InvalidProjectName {
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");
if !(r.is_match(&self.owner) && r.is_match(&self.repo)) {
return Err(Error::InvalidProjectName {
reason: InvalidProjectNameReason::Format,
name,
name: self.to_string(),
});
}

View File

@ -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)
}
}
}

View File

@ -1,3 +1,4 @@
pub mod add;
pub mod clear_cache;
use clap::Subcommand;
@ -6,12 +7,16 @@ use clap::Subcommand;
#[derive(Subcommand)]
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
pub enum Cmd {
/// Add a new dependency
Add(add::Args),
/// Clear the system-wide dependencies cache
ClearCache,
}
pub fn exec(cmd: Cmd) -> miette::Result<()> {
match cmd {
Cmd::Add(args) => add::exec(args),
Cmd::ClearCache => clear_cache::exec(),
}
}

View File

@ -29,6 +29,8 @@ pub fn exec(args: Args) -> miette::Result<()> {
}
fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()> {
package_name.restrict().into_diagnostic()?;
let root = PathBuf::from(&package_name.repo);
if root.exists() {

View File

@ -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;
/// Aiken: a smart-contract language and toolchain for Cardano
@ -12,6 +16,7 @@ pub enum Cmd {
Build(build::Args),
Check(check::Args),
Docs(docs::Args),
Add(add::Args),
#[clap(subcommand)]
Deps(deps::Cmd),
@ -38,9 +43,10 @@ fn main() -> miette::Result<()> {
Cmd::New(args) => new::exec(args),
Cmd::Fmt(args) => fmt::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::Docs(args) => docs::exec(args),
Cmd::Add(args) => add::exec(args),
Cmd::Deps(args) => deps::exec(args),
Cmd::Lsp(args) => lsp::exec(args),
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),