feat: redo the new command
This commit is contained in:
parent
a129a8a0d3
commit
c723f4f796
|
@ -1,14 +1,17 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, miette::Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("'{}' is not a valid project name: {}", name, reason.to_string())]
|
#[error("{} is not a valid project name: {}", name.red(), reason.to_string())]
|
||||||
InvalidProjectName {
|
InvalidProjectName {
|
||||||
name: String,
|
name: String,
|
||||||
reason: InvalidProjectNameReason,
|
reason: InvalidProjectNameReason,
|
||||||
},
|
},
|
||||||
|
#[error("A project named {} already exists.", name.red())]
|
||||||
|
ProjectExists { name: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -28,9 +31,12 @@ impl fmt::Display for InvalidProjectNameReason {
|
||||||
InvalidProjectNameReason::Format => write!(
|
InvalidProjectNameReason::Format => write!(
|
||||||
f,
|
f,
|
||||||
"it is malformed.\n\nProjects must be named as:\n\n\t\
|
"it is malformed.\n\nProjects must be named as:\n\n\t\
|
||||||
{{repository}}/{{project}}\n\nEach part must start with a lowercase letter \
|
{}/{}\n\nEach part must start with a lowercase letter \
|
||||||
and may only contain lowercase letters, numbers, hyphens or underscores.\
|
and may only contain lowercase letters, numbers, hyphens or underscores.\
|
||||||
\nFor example,\n\n\taiken-lang/stdlib"
|
\nFor example,\n\n\t{}",
|
||||||
|
"{owner}".bright_blue(),
|
||||||
|
"{project}".bright_blue(),
|
||||||
|
"aiken-lang/stdlib".bright_blue(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use indoc::formatdoc;
|
use aiken_project::config::PackageName;
|
||||||
|
use indoc::{formatdoc, indoc};
|
||||||
use miette::IntoDiagnostic;
|
use miette::IntoDiagnostic;
|
||||||
use std::fs;
|
use owo_colors::OwoColorize;
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
use super::error::{Error, InvalidProjectNameReason};
|
use super::error::{Error, InvalidProjectNameReason};
|
||||||
|
|
||||||
|
@ -10,47 +11,173 @@ use super::error::{Error, InvalidProjectNameReason};
|
||||||
/// Create a new Aiken project
|
/// Create a new Aiken project
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Project name
|
/// Project name
|
||||||
name: PathBuf,
|
name: String,
|
||||||
|
/// Library only
|
||||||
|
lib: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Creator {
|
pub fn exec(args: Args) -> miette::Result<()> {
|
||||||
root: PathBuf,
|
validate_name(&args.name)?;
|
||||||
lib: PathBuf,
|
|
||||||
validators: PathBuf,
|
|
||||||
project_lib: PathBuf,
|
|
||||||
project_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Creator {
|
let package_name = package_name_from_str(&args.name)?;
|
||||||
fn new(args: Args, project_name: String) -> Self {
|
|
||||||
let root = PathBuf::from(args.name.file_name().unwrap());
|
create_project(args, &package_name)?;
|
||||||
let lib = root.join("lib");
|
|
||||||
let validators = root.join("validators");
|
print_success_message(&package_name);
|
||||||
let project_name = project_name;
|
|
||||||
let project_lib = lib.join(&project_name);
|
|
||||||
Self {
|
|
||||||
root,
|
|
||||||
lib,
|
|
||||||
validators,
|
|
||||||
project_lib,
|
|
||||||
project_name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&self) -> miette::Result<()> {
|
|
||||||
fs::create_dir_all(&self.lib).into_diagnostic()?;
|
|
||||||
fs::create_dir_all(&self.project_lib).into_diagnostic()?;
|
|
||||||
fs::create_dir_all(&self.validators).into_diagnostic()?;
|
|
||||||
self.aiken_toml()?;
|
|
||||||
self.readme()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readme(&self) -> miette::Result<()> {
|
fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()> {
|
||||||
write(
|
let root = PathBuf::from(&package_name.repo);
|
||||||
self.root.join("README.md"),
|
|
||||||
&format! {
|
if root.exists() {
|
||||||
r#"# {name}
|
Err(Error::ProjectExists {
|
||||||
|
name: package_name.repo.clone(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_lib(&root, package_name)?;
|
||||||
|
|
||||||
|
if !args.lib {
|
||||||
|
create_validators(&root, package_name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
aiken_toml(&root, package_name)?;
|
||||||
|
|
||||||
|
readme(&root, &package_name.repo)?;
|
||||||
|
|
||||||
|
gitignore(&root)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_success_message(package_name: &PackageName) {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
formatdoc! {
|
||||||
|
r#"
|
||||||
|
Your Aiken project {name} has been {s} created.
|
||||||
|
The project can be compiled and tested by running these commands:
|
||||||
|
|
||||||
|
{cd} {name}
|
||||||
|
{aiken} check
|
||||||
|
"#,
|
||||||
|
s = "successfully".bold().bright_green(),
|
||||||
|
cd = "cd".bold().purple(),
|
||||||
|
name = package_name.repo.bright_blue(),
|
||||||
|
aiken = "aiken".bold().purple(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_lib(root: &Path, package_name: &PackageName) -> miette::Result<()> {
|
||||||
|
let lib = root.join("lib");
|
||||||
|
|
||||||
|
fs::create_dir_all(&lib).into_diagnostic()?;
|
||||||
|
|
||||||
|
let lib_module_path = lib.join(format!("{}.ak", package_name.repo));
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
lib_module_path,
|
||||||
|
indoc! {
|
||||||
|
r#"
|
||||||
|
pub fn hello(_name: String) -> Bool {
|
||||||
|
trace("hello")
|
||||||
|
|
||||||
|
True
|
||||||
|
}
|
||||||
|
|
||||||
|
test hello_1() {
|
||||||
|
hello("Human")
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_diagnostic()?;
|
||||||
|
|
||||||
|
let nested_path = lib.join(&package_name.repo);
|
||||||
|
|
||||||
|
fs::create_dir_all(&nested_path).into_diagnostic()?;
|
||||||
|
|
||||||
|
let nested_module_path = nested_path.join("util.ak");
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
nested_module_path,
|
||||||
|
indoc! {
|
||||||
|
r#"
|
||||||
|
pub fn is_one(a: Int) -> Bool {
|
||||||
|
trace("hi")
|
||||||
|
|
||||||
|
a == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test is_one_1() {
|
||||||
|
is_one(1)
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_diagnostic()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_validators(root: &Path, package_name: &PackageName) -> miette::Result<()> {
|
||||||
|
let validators = root.join("validators");
|
||||||
|
|
||||||
|
fs::create_dir_all(&validators).into_diagnostic()?;
|
||||||
|
|
||||||
|
let always_true_path = validators.join("always_true.ak");
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
always_true_path,
|
||||||
|
formatdoc! {
|
||||||
|
r#"
|
||||||
|
use aiken/transaction.{{ScriptContext}}
|
||||||
|
use {name}
|
||||||
|
use {name}/util
|
||||||
|
|
||||||
|
pub fn spend(_datum: Data, _redeemer: Data, _context: ScriptContext) -> Bool {{
|
||||||
|
{name}.hello("World") && util.is_one(1)
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
name = package_name.repo
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_diagnostic()?;
|
||||||
|
|
||||||
|
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<()> {
|
||||||
|
fs::write(
|
||||||
|
root.join("README.md"),
|
||||||
|
formatdoc! {
|
||||||
|
r#"
|
||||||
|
# {name}
|
||||||
|
|
||||||
Write validators in the `validators` folder, and supporting functions in the `lib` folder using `.ak` as a file extension.
|
Write validators in the `validators` folder, and supporting functions in the `lib` folder using `.ak` as a file extension.
|
||||||
|
|
||||||
|
@ -71,7 +198,7 @@ Validators are named after their purpose, so one of:
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
```console
|
```sh
|
||||||
aiken build
|
aiken build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -87,13 +214,13 @@ test foo() {{
|
||||||
|
|
||||||
To run all tests, simply do:
|
To run all tests, simply do:
|
||||||
|
|
||||||
```console
|
```sh
|
||||||
aiken check
|
aiken check
|
||||||
```
|
```
|
||||||
|
|
||||||
To run only tests matching the string `foo`, do:
|
To run only tests matching the string `foo`, do:
|
||||||
|
|
||||||
```console
|
```sh
|
||||||
aiken check -m foo
|
aiken check -m foo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -103,7 +230,7 @@ If you're writing a library, you might want to generate an HTML documentation fo
|
||||||
|
|
||||||
Use:
|
Use:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
aiken docs
|
aiken docs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -111,30 +238,24 @@ aiken docs
|
||||||
|
|
||||||
Find more on the [Aiken's user manual](https://aiken-lang.org).
|
Find more on the [Aiken's user manual](https://aiken-lang.org).
|
||||||
"#,
|
"#,
|
||||||
name = self.project_name
|
name = project_name
|
||||||
},
|
},
|
||||||
)
|
).into_diagnostic()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn aiken_toml(&self) -> miette::Result<()> {
|
fn gitignore(root: &Path) -> miette::Result<()> {
|
||||||
write(
|
let gitignore_path = root.join(".gitignore");
|
||||||
self.root.join("aiken.toml"),
|
|
||||||
&formatdoc! {
|
fs::write(
|
||||||
r#"name = "{name}"
|
gitignore_path,
|
||||||
version = "0.0.0"
|
indoc! {
|
||||||
licences = ["Apache-2.0"]
|
r#"
|
||||||
description = "Aiken contracts for project '{name}'"
|
build/
|
||||||
dependencies = []
|
"#
|
||||||
"#,
|
|
||||||
name = self.project_name,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
.into_diagnostic()?;
|
||||||
}
|
|
||||||
|
|
||||||
fn write(path: PathBuf, contents: &str) -> miette::Result<()> {
|
|
||||||
let mut f = fs::File::create(path).into_diagnostic()?;
|
|
||||||
f.write_all(contents.as_bytes()).into_diagnostic()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,14 +283,24 @@ fn validate_name(name: &str) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(args: Args) -> miette::Result<()> {
|
fn package_name_from_str(name: &str) -> Result<PackageName, Error> {
|
||||||
if !args.name.exists() {
|
let mut name_split = name.split('/');
|
||||||
let project_name = args.name.clone().into_os_string().into_string().unwrap();
|
|
||||||
|
|
||||||
validate_name(&project_name).into_diagnostic()?;
|
let owner = name_split
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| Error::InvalidProjectName {
|
||||||
|
name: name.to_string(),
|
||||||
|
reason: InvalidProjectNameReason::Format,
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
Creator::new(args, project_name).run()?;
|
let repo = name_split
|
||||||
}
|
.next()
|
||||||
|
.ok_or_else(|| Error::InvalidProjectName {
|
||||||
Ok(())
|
name: name.to_string(),
|
||||||
|
reason: InvalidProjectNameReason::Format,
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(PackageName { owner, repo })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue