feat: redo the new command
This commit is contained in:
parent
a129a8a0d3
commit
c723f4f796
|
@ -1,14 +1,17 @@
|
|||
use std::fmt;
|
||||
|
||||
use owo_colors::OwoColorize;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, miette::Diagnostic)]
|
||||
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 {
|
||||
name: String,
|
||||
reason: InvalidProjectNameReason,
|
||||
},
|
||||
#[error("A project named {} already exists.", name.red())]
|
||||
ProjectExists { name: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -28,9 +31,12 @@ impl fmt::Display for InvalidProjectNameReason {
|
|||
InvalidProjectNameReason::Format => write!(
|
||||
f,
|
||||
"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.\
|
||||
\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 std::fs;
|
||||
use std::io::Write;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use super::error::{Error, InvalidProjectNameReason};
|
||||
|
||||
|
@ -10,131 +11,251 @@ use super::error::{Error, InvalidProjectNameReason};
|
|||
/// Create a new Aiken project
|
||||
pub struct Args {
|
||||
/// Project name
|
||||
name: PathBuf,
|
||||
name: String,
|
||||
/// Library only
|
||||
lib: bool,
|
||||
}
|
||||
|
||||
pub struct Creator {
|
||||
root: PathBuf,
|
||||
lib: PathBuf,
|
||||
validators: PathBuf,
|
||||
project_lib: PathBuf,
|
||||
project_name: String,
|
||||
pub fn exec(args: Args) -> miette::Result<()> {
|
||||
validate_name(&args.name)?;
|
||||
|
||||
let package_name = package_name_from_str(&args.name)?;
|
||||
|
||||
create_project(args, &package_name)?;
|
||||
|
||||
print_success_message(&package_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Creator {
|
||||
fn new(args: Args, project_name: String) -> Self {
|
||||
let root = PathBuf::from(args.name.file_name().unwrap());
|
||||
let lib = root.join("lib");
|
||||
let validators = root.join("validators");
|
||||
let project_name = project_name;
|
||||
let project_lib = lib.join(&project_name);
|
||||
Self {
|
||||
root,
|
||||
lib,
|
||||
validators,
|
||||
project_lib,
|
||||
project_name,
|
||||
fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()> {
|
||||
let root = PathBuf::from(&package_name.repo);
|
||||
|
||||
if root.exists() {
|
||||
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 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(())
|
||||
}
|
||||
fn create_lib(root: &Path, package_name: &PackageName) -> miette::Result<()> {
|
||||
let lib = root.join("lib");
|
||||
|
||||
fn readme(&self) -> miette::Result<()> {
|
||||
write(
|
||||
self.root.join("README.md"),
|
||||
&format! {
|
||||
r#"# {name}
|
||||
fs::create_dir_all(&lib).into_diagnostic()?;
|
||||
|
||||
Write validators in the `validators` folder, and supporting functions in the `lib` folder using `.ak` as a file extension.
|
||||
let lib_module_path = lib.join(format!("{}.ak", package_name.repo));
|
||||
|
||||
For example, as `validators/always_true.ak`
|
||||
fs::write(
|
||||
lib_module_path,
|
||||
indoc! {
|
||||
r#"
|
||||
pub fn hello(_name: String) -> Bool {
|
||||
trace("hello")
|
||||
|
||||
```gleam
|
||||
pub fn spend(_datum: Data, _redeemer: Data, _context: Data) -> Bool {{
|
||||
True
|
||||
}}
|
||||
```
|
||||
True
|
||||
}
|
||||
|
||||
Validators are named after their purpose, so one of:
|
||||
test hello_1() {
|
||||
hello("Human")
|
||||
}
|
||||
"#
|
||||
},
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
- `script`
|
||||
- `mint`
|
||||
- `withdraw`
|
||||
- `certify`
|
||||
let nested_path = lib.join(&package_name.repo);
|
||||
|
||||
## Building
|
||||
fs::create_dir_all(&nested_path).into_diagnostic()?;
|
||||
|
||||
```console
|
||||
aiken build
|
||||
```
|
||||
let nested_module_path = nested_path.join("util.ak");
|
||||
|
||||
## Testing
|
||||
fs::write(
|
||||
nested_module_path,
|
||||
indoc! {
|
||||
r#"
|
||||
pub fn is_one(a: Int) -> Bool {
|
||||
trace("hi")
|
||||
|
||||
You can write tests in any module using the `test` keyword. For example:
|
||||
a == 1
|
||||
}
|
||||
|
||||
```gleam
|
||||
test foo() {{
|
||||
1 + 1 == 2
|
||||
}}
|
||||
```
|
||||
test is_one_1() {
|
||||
is_one(1)
|
||||
}
|
||||
"#
|
||||
},
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
To run all tests, simply do:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
```console
|
||||
aiken check
|
||||
```
|
||||
fn create_validators(root: &Path, package_name: &PackageName) -> miette::Result<()> {
|
||||
let validators = root.join("validators");
|
||||
|
||||
To run only tests matching the string `foo`, do:
|
||||
fs::create_dir_all(&validators).into_diagnostic()?;
|
||||
|
||||
```console
|
||||
aiken check -m foo
|
||||
```
|
||||
let always_true_path = validators.join("always_true.ak");
|
||||
|
||||
## Documentation
|
||||
fs::write(
|
||||
always_true_path,
|
||||
formatdoc! {
|
||||
r#"
|
||||
use aiken/transaction.{{ScriptContext}}
|
||||
use {name}
|
||||
use {name}/util
|
||||
|
||||
If you're writing a library, you might want to generate an HTML documentation for it.
|
||||
pub fn spend(_datum: Data, _redeemer: Data, _context: ScriptContext) -> Bool {{
|
||||
{name}.hello("World") && util.is_one(1)
|
||||
}}
|
||||
"#,
|
||||
name = package_name.repo
|
||||
},
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
Use:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
```
|
||||
aiken docs
|
||||
```
|
||||
fn aiken_toml(root: &Path, package_name: &PackageName) -> miette::Result<()> {
|
||||
let aiken_toml_path = root.join("aiken.toml");
|
||||
|
||||
## Resources
|
||||
|
||||
Find more on the [Aiken's user manual](https://aiken-lang.org).
|
||||
"#,
|
||||
name = self.project_name
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn aiken_toml(&self) -> miette::Result<()> {
|
||||
write(
|
||||
self.root.join("aiken.toml"),
|
||||
&formatdoc! {
|
||||
r#"name = "{name}"
|
||||
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 = self.project_name,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
dependencies = [
|
||||
{{ name = "aiken-lang/stdlib", version = "main", source = "github" }},
|
||||
]
|
||||
"#,
|
||||
name = package_name.to_string(),
|
||||
},
|
||||
)
|
||||
.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()?;
|
||||
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.
|
||||
|
||||
For example, as `validators/always_true.ak`
|
||||
|
||||
```gleam
|
||||
pub fn spend(_datum: Data, _redeemer: Data, _context: Data) -> Bool {{
|
||||
True
|
||||
}}
|
||||
```
|
||||
|
||||
Validators are named after their purpose, so one of:
|
||||
|
||||
- `script`
|
||||
- `mint`
|
||||
- `withdraw`
|
||||
- `certify`
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
aiken build
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
You can write tests in any module using the `test` keyword. For example:
|
||||
|
||||
```gleam
|
||||
test foo() {{
|
||||
1 + 1 == 2
|
||||
}}
|
||||
```
|
||||
|
||||
To run all tests, simply do:
|
||||
|
||||
```sh
|
||||
aiken check
|
||||
```
|
||||
|
||||
To run only tests matching the string `foo`, do:
|
||||
|
||||
```sh
|
||||
aiken check -m foo
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
If you're writing a library, you might want to generate an HTML documentation for it.
|
||||
|
||||
Use:
|
||||
|
||||
```sh
|
||||
aiken docs
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
Find more on the [Aiken's user manual](https://aiken-lang.org).
|
||||
"#,
|
||||
name = project_name
|
||||
},
|
||||
).into_diagnostic()
|
||||
}
|
||||
|
||||
fn gitignore(root: &Path) -> miette::Result<()> {
|
||||
let gitignore_path = root.join(".gitignore");
|
||||
|
||||
fs::write(
|
||||
gitignore_path,
|
||||
indoc! {
|
||||
r#"
|
||||
build/
|
||||
"#
|
||||
},
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -162,14 +283,24 @@ fn validate_name(name: &str) -> Result<(), Error> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn exec(args: Args) -> miette::Result<()> {
|
||||
if !args.name.exists() {
|
||||
let project_name = args.name.clone().into_os_string().into_string().unwrap();
|
||||
fn package_name_from_str(name: &str) -> Result<PackageName, Error> {
|
||||
let mut name_split = name.split('/');
|
||||
|
||||
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 {
|
||||
name: name.to_string(),
|
||||
reason: InvalidProjectNameReason::Format,
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
Ok(())
|
||||
Ok(PackageName { owner, repo })
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue