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 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue