chore: make folder names match crate name

This commit is contained in:
rvcas
2022-12-21 17:42:53 -05:00
committed by Lucas
parent 5694cac1a5
commit 42204d2d71
93 changed files with 7 additions and 7 deletions

30
crates/aiken/Cargo.toml Normal file
View File

@@ -0,0 +1,30 @@
[package]
name = "aiken"
description = "Cardano smart contract language and toolchain"
version = "0.0.26"
edition = "2021"
repository = "https://github.com/aiken-lang/aiken"
homepage = "https://github.com/aiken-lang/aiken"
license = "Apache-2.0"
authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
[dependencies]
anyhow = "1.0.57"
clap = { version = "3.1.14", features = ["derive"] }
hex = "0.4.3"
ignore = "0.4.18"
indoc = "1.0"
miette = { version = "5.3.0", features = ["fancy"] }
owo-colors = "3.5.0"
pallas-addresses = "0.14.0"
pallas-codec = "0.14.0"
pallas-crypto = "0.14.0"
pallas-primitives = "0.14.0"
pallas-traverse = "0.14.0"
regex = "1.5.4"
thiserror = "1.0.31"
aiken-lang = { path = "../aiken-lang", version = "0.0.26" }
aiken-lsp = { path = "../aiken-lsp", version = "0.0.26" }
aiken-project = { path = '../aiken-project', version = "0.0.26" }
uplc = { path = '../uplc', version = "0.0.25" }

8
crates/aiken/README.md Normal file
View File

@@ -0,0 +1,8 @@
# CLI
This is the crate that contains the aiken command line application
which bundles together all the other crates in this project.
## Install
`cargo install aiken`

View File

@@ -0,0 +1,17 @@
use std::path::PathBuf;
#[derive(clap::Args)]
/// Build an Aiken project
pub struct Args {
/// Path to project
#[clap(short, long)]
directory: Option<PathBuf>,
/// Also dump textual uplc
#[clap(short, long)]
uplc: bool,
}
pub fn exec(Args { directory, uplc }: Args) -> miette::Result<()> {
crate::with_project(directory, |p| p.build(uplc))
}

View File

@@ -0,0 +1,34 @@
use std::path::PathBuf;
#[derive(clap::Args)]
/// Type-check an Aiken project
pub struct Args {
/// Path to project
#[clap(short, long)]
directory: Option<PathBuf>,
/// Skip tests; run only the type-checker
#[clap(short, long)]
skip_tests: bool,
/// When enabled, also pretty-print test UPLC on failure
#[clap(long)]
debug: bool,
/// Only run tests if their path + name match the given string
#[clap(short, long)]
match_tests: Option<String>,
}
pub fn exec(
Args {
directory,
skip_tests,
debug,
match_tests,
}: Args,
) -> miette::Result<()> {
crate::with_project(directory, |p| {
p.check(skip_tests, match_tests.clone(), debug)
})
}

View File

@@ -0,0 +1,22 @@
use std::path::PathBuf;
#[derive(clap::Args)]
/// Build an Aiken project
pub struct Args {
/// Path to project
#[clap(short, long)]
directory: Option<PathBuf>,
/// Output directory for the documentation
#[clap(short = 'o', long)]
destination: Option<PathBuf>,
}
pub fn exec(
Args {
directory,
destination,
}: Args,
) -> miette::Result<()> {
crate::with_project(directory, |p| p.docs(destination.clone()))
}

View File

@@ -0,0 +1,37 @@
use std::fmt;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("'{}' is not a valid project name: {}", name, reason.to_string())]
InvalidProjectName {
name: String,
reason: InvalidProjectNameReason,
},
}
#[derive(Debug, Clone, Copy)]
pub enum InvalidProjectNameReason {
AikenPrefix,
AikenReservedModule,
Format,
}
impl fmt::Display for InvalidProjectNameReason {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InvalidProjectNameReason::AikenPrefix => write!(f, "it's a reserved word in Aiken."),
InvalidProjectNameReason::AikenReservedModule => {
write!(f, "it's a reserved module name in Aiken.")
}
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 \
and may only contain lowercase letters, numbers, hyphens or underscores.\
\nFor example,\n\n\taiken-lang/stdlib"
),
}
}
}

View File

@@ -0,0 +1,31 @@
#[derive(clap::Args)]
/// Format an Aiken project
pub struct Args {
/// Files to format
#[clap(default_value = ".")]
files: Vec<String>,
/// Read source from STDIN
#[clap(long)]
stdin: bool,
/// Check if inputs are formatted without changing them
#[clap(long)]
check: bool,
}
pub fn exec(
Args {
check,
stdin,
files,
}: Args,
) -> miette::Result<()> {
if let Err(err) = aiken_project::format::run(stdin, check, files) {
err.report();
miette::bail!("failed: {} error(s)", err.len());
};
Ok(())
}

View File

@@ -0,0 +1,13 @@
use miette::IntoDiagnostic;
#[derive(clap::Args)]
/// Start the Aiken language server
pub struct Args {
/// Run on stdio
#[clap(long)]
stdio: bool,
}
pub fn exec(_args: Args) -> miette::Result<()> {
aiken_lsp::start().into_diagnostic()
}

View File

@@ -0,0 +1,9 @@
pub mod build;
pub mod check;
pub mod docs;
pub mod error;
pub mod fmt;
pub mod lsp;
pub mod new;
pub mod tx;
pub mod uplc;

175
crates/aiken/src/cmd/new.rs Normal file
View File

@@ -0,0 +1,175 @@
use indoc::formatdoc;
use miette::IntoDiagnostic;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use super::error::{Error, InvalidProjectNameReason};
#[derive(clap::Args)]
/// Create a new Aiken project
pub struct Args {
/// Project name
name: PathBuf,
}
pub struct Creator {
root: PathBuf,
lib: PathBuf,
validators: PathBuf,
project_lib: PathBuf,
project_name: String,
}
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 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 readme(&self) -> miette::Result<()> {
write(
self.root.join("README.md"),
&format! {
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
```console
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:
```console
aiken check
```
To run only tests matching the string `foo`, do:
```console
aiken check -m foo
```
## Documentation
If you're writing a library, you might want to generate an HTML documentation for it.
Use:
```
aiken docs
```
## 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}"
version = "0.0.0"
licences = ["Apache-2.0"]
description = "Aiken contracts for project '{name}'"
dependencies = []
"#,
name = self.project_name,
},
)
}
}
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(())
}
fn validate_name(name: &str) -> Result<(), Error> {
if name.starts_with("aiken_") {
Err(Error::InvalidProjectName {
name: name.to_string(),
reason: InvalidProjectNameReason::AikenPrefix,
})
} else if name == "aiken" {
Err(Error::InvalidProjectName {
name: name.to_string(),
reason: InvalidProjectNameReason::AikenReservedModule,
})
} else if !regex::Regex::new("^[a-z0-9_-]+/[a-z0-9_-]+$")
.expect("regex could not be compiled")
.is_match(name)
{
Err(Error::InvalidProjectName {
name: name.to_string(),
reason: InvalidProjectNameReason::Format,
})
} else {
Ok(())
}
}
pub fn exec(args: Args) -> miette::Result<()> {
if !args.name.exists() {
let project_name = args.name.clone().into_os_string().into_string().unwrap();
validate_name(&project_name).into_diagnostic()?;
Creator::new(args, project_name).run()?;
}
Ok(())
}

View File

@@ -0,0 +1,16 @@
pub mod simulate;
use clap::Subcommand;
/// Commands for working with transactions
#[derive(Subcommand)]
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
pub enum Cmd {
Simulate(simulate::Args),
}
pub fn exec(cmd: Cmd) -> miette::Result<()> {
match cmd {
Cmd::Simulate(args) => simulate::exec(args),
}
}

View File

@@ -0,0 +1,125 @@
use miette::IntoDiagnostic;
use pallas_primitives::{
babbage::{TransactionInput, TransactionOutput},
Fragment,
};
use pallas_traverse::{Era, MultiEraTx};
use std::fs;
use std::path::PathBuf;
use uplc::{
machine::cost_model::ExBudget,
tx::{
self,
script_context::{ResolvedInput, SlotConfig},
},
};
#[derive(clap::Args)]
/// Simulate a transaction by evaluating it's script
pub struct Args {
/// A file containing cbor hex for a transaction
input: PathBuf,
/// Toggle whether input is raw cbor or a hex string
#[clap(short, long)]
cbor: bool,
/// A file containing cbor hex for the raw inputs
raw_inputs: PathBuf,
/// A file containing cbor hex for the raw outputs
raw_outputs: PathBuf,
/// Time between each slot
#[clap(short, long, default_value_t = 1000)]
slot_length: u32,
/// Time of shelley hardfork
#[clap(long, default_value_t = 1596059091000)]
zero_time: u64,
/// Slot number at the start of the shelley hardfork
#[clap(long, default_value_t = 4492800)]
zero_slot: u64,
}
pub fn exec(
Args {
input,
cbor,
raw_inputs,
raw_outputs,
slot_length,
zero_time,
zero_slot,
}: Args,
) -> miette::Result<()> {
let (tx_bytes, inputs_bytes, outputs_bytes) = if cbor {
(
fs::read(input).into_diagnostic()?,
fs::read(raw_inputs).into_diagnostic()?,
fs::read(raw_outputs).into_diagnostic()?,
)
} else {
let cbor_hex = fs::read_to_string(input).into_diagnostic()?;
let inputs_hex = fs::read_to_string(raw_inputs).into_diagnostic()?;
let outputs_hex = fs::read_to_string(raw_outputs).into_diagnostic()?;
(
hex::decode(cbor_hex.trim()).into_diagnostic()?,
hex::decode(inputs_hex.trim()).into_diagnostic()?,
hex::decode(outputs_hex.trim()).into_diagnostic()?,
)
};
let tx = MultiEraTx::decode(Era::Babbage, &tx_bytes)
.or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes))
.into_diagnostic()?;
let inputs = Vec::<TransactionInput>::decode_fragment(&inputs_bytes).unwrap();
let outputs = Vec::<TransactionOutput>::decode_fragment(&outputs_bytes).unwrap();
let resolved_inputs: Vec<ResolvedInput> = inputs
.iter()
.zip(outputs.iter())
.map(|(input, output)| ResolvedInput {
input: input.clone(),
output: output.clone(),
})
.collect();
println!("Simulating: {}", tx.hash());
if let Some(tx_babbage) = tx.as_babbage() {
let slot_config = SlotConfig {
zero_time,
zero_slot,
slot_length,
};
let result =
tx::eval_phase_two(tx_babbage, &resolved_inputs, None, None, &slot_config, true);
match result {
Ok(redeemers) => {
println!("\nTotal Budget Used\n-----------------\n");
let total_budget_used =
redeemers
.iter()
.fold(ExBudget { mem: 0, cpu: 0 }, |accum, curr| ExBudget {
mem: accum.mem + curr.ex_units.mem as i64,
cpu: accum.cpu + curr.ex_units.steps as i64,
});
println!("mem: {}", total_budget_used.mem);
println!("cpu: {}", total_budget_used.cpu);
}
Err(err) => {
eprintln!("\nError\n-----\n\n{}\n", err);
}
}
}
Ok(())
}

View File

@@ -0,0 +1,94 @@
use miette::IntoDiagnostic;
use std::path::PathBuf;
use uplc::{
ast::{FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term},
machine::cost_model::ExBudget,
parser,
};
#[derive(clap::Args)]
/// Evaluate an Untyped Plutus Core program
pub struct Args {
script: PathBuf,
#[clap(short, long)]
flat: bool,
#[clap(short, long)]
cbor: bool,
/// Arguments to pass to the uplc program
args: Vec<String>,
}
pub fn exec(
Args {
script,
flat,
args,
cbor,
}: Args,
) -> miette::Result<()> {
let mut program = if cbor {
let cbor_hex = std::fs::read_to_string(&script).into_diagnostic()?;
let raw_cbor = hex::decode(cbor_hex).into_diagnostic()?;
let prog = Program::<FakeNamedDeBruijn>::from_cbor(&raw_cbor, &mut Vec::new())
.into_diagnostic()?;
prog.into()
} else if flat {
let bytes = std::fs::read(&script).into_diagnostic()?;
let prog = Program::<FakeNamedDeBruijn>::from_flat(&bytes).into_diagnostic()?;
prog.into()
} else {
let code = std::fs::read_to_string(&script).into_diagnostic()?;
let prog = parser::program(&code).into_diagnostic()?;
Program::<NamedDeBruijn>::try_from(prog).into_diagnostic()?
};
for arg in args {
let term: Term<NamedDeBruijn> = parser::term(&arg)
.into_diagnostic()?
.try_into()
.into_diagnostic()?;
program = program.apply_term(&term);
}
let budget = ExBudget::default();
let (term, cost, logs) = program.eval(budget);
match term {
Ok(term) => {
let term: Term<Name> = term.try_into().into_diagnostic()?;
println!("\nResult\n------\n\n{}\n", term.to_pretty());
}
Err(err) => {
eprintln!("\nError\n-----\n\n{}\n", err);
}
}
println!(
"\nCosts\n-----\ncpu: {}\nmemory: {}",
budget.cpu - cost.cpu,
budget.mem - cost.mem
);
println!(
"\nBudget\n------\ncpu: {}\nmemory: {}\n",
cost.cpu, cost.mem
);
if !logs.is_empty() {
println!("\nLogs\n----\n{}", logs.join("\n"))
}
Ok(())
}

View File

@@ -0,0 +1,83 @@
use miette::IntoDiagnostic;
use std::{fmt::Write, fs, path::PathBuf};
use uplc::{
ast::{DeBruijn, Program},
parser,
};
#[derive(clap::Args)]
/// Encode textual Untyped Plutus Core to flat bytes
pub struct Args {
/// Textual Untyped Plutus Core file
input: PathBuf,
/// Output file name
#[clap(short, long)]
out: Option<String>,
/// Print output instead of saving to file
#[clap(short, long)]
print: bool,
#[clap(short, long)]
cbor_hex: bool,
}
pub fn exec(
Args {
input,
out,
print,
cbor_hex,
}: Args,
) -> miette::Result<()> {
let code = std::fs::read_to_string(&input).into_diagnostic()?;
let program = parser::program(&code).into_diagnostic()?;
let program = Program::<DeBruijn>::try_from(program).into_diagnostic()?;
if !cbor_hex {
let bytes = program.to_flat().into_diagnostic()?;
if print {
let mut output = String::new();
for (i, byte) in bytes.iter().enumerate() {
let _ = write!(output, "{:08b}", byte);
if (i + 1) % 4 == 0 {
output.push('\n');
} else {
output.push(' ');
}
}
println!("{}", output);
} else {
let out_name = if let Some(out) = out {
out
} else {
format!("{}.flat", input.file_stem().unwrap().to_str().unwrap())
};
fs::write(out_name, &bytes).into_diagnostic()?;
}
} else {
let cbor = program.to_hex().into_diagnostic()?;
if print {
println!("{}", &cbor);
} else {
let out_name = if let Some(out) = out {
out
} else {
format!("{}.cbor", input.file_stem().unwrap().to_str().unwrap())
};
fs::write(out_name, &cbor).into_diagnostic()?;
}
}
Ok(())
}

View File

@@ -0,0 +1,30 @@
use miette::IntoDiagnostic;
use std::{fs, path::PathBuf};
use uplc::parser;
#[derive(clap::Args)]
/// Format an Untyped Plutus Core program
pub struct Args {
/// Textual Untyped Plutus Core file
input: PathBuf,
/// Print output instead of saving to file
#[clap(short, long)]
print: bool,
}
pub fn exec(Args { input, print }: Args) -> miette::Result<()> {
let code = std::fs::read_to_string(&input).into_diagnostic()?;
let program = parser::program(&code).into_diagnostic()?;
let pretty = program.to_pretty();
if print {
println!("{}", pretty);
} else {
fs::write(&input, pretty).into_diagnostic()?;
}
Ok(())
}

View File

@@ -0,0 +1,25 @@
mod eval;
mod flat;
mod fmt;
mod unflat;
use clap::Subcommand;
/// Commands for working with untyped Plutus-core
#[derive(Subcommand)]
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
pub enum Cmd {
Fmt(fmt::Args),
Eval(eval::Args),
Flat(flat::Args),
Unflat(unflat::Args),
}
pub fn exec(cmd: Cmd) -> miette::Result<()> {
match cmd {
Cmd::Fmt(args) => fmt::exec(args),
Cmd::Eval(args) => eval::exec(args),
Cmd::Flat(args) => flat::exec(args),
Cmd::Unflat(args) => unflat::exec(args),
}
}

View File

@@ -0,0 +1,61 @@
use miette::IntoDiagnostic;
use std::{fs, path::PathBuf};
use uplc::ast::{DeBruijn, Name, Program};
#[derive(clap::Args)]
/// Decode flat bytes to textual Untyped Plutus Core
pub struct Args {
/// Flat encoded Untyped Plutus Core file
input: PathBuf,
/// Output file name
#[clap(short, long)]
out: Option<String>,
/// Print output instead of saving to file
#[clap(short, long)]
print: bool,
#[clap(short, long)]
cbor_hex: bool,
}
pub fn exec(
Args {
input,
out,
print,
cbor_hex,
}: Args,
) -> miette::Result<()> {
let program = if cbor_hex {
let cbor = std::fs::read_to_string(&input).into_diagnostic()?;
let mut cbor_buffer = Vec::new();
let mut flat_buffer = Vec::new();
Program::<DeBruijn>::from_hex(cbor.trim(), &mut cbor_buffer, &mut flat_buffer)
.into_diagnostic()?
} else {
let bytes = std::fs::read(&input).into_diagnostic()?;
Program::<DeBruijn>::from_flat(&bytes).into_diagnostic()?
};
let program: Program<Name> = program.try_into().into_diagnostic()?;
let pretty = program.to_pretty();
if print {
println!("{}", pretty);
} else {
let out_name = if let Some(out) = out {
out
} else {
format!("{}.uplc", input.file_stem().unwrap().to_str().unwrap())
};
fs::write(out_name, pretty).into_diagnostic()?;
}
Ok(())
}

291
crates/aiken/src/lib.rs Normal file
View File

@@ -0,0 +1,291 @@
use aiken_project::{pretty, script::EvalInfo, telemetry, Project};
use miette::IntoDiagnostic;
use owo_colors::OwoColorize;
use std::{collections::BTreeMap, env, path::PathBuf, process};
use uplc::machine::cost_model::ExBudget;
pub mod cmd;
pub fn with_project<A>(directory: Option<PathBuf>, mut action: A) -> miette::Result<()>
where
A: FnMut(&mut Project<Terminal>) -> Result<(), aiken_project::error::Error>,
{
let project_path = if let Some(d) = directory {
d
} else {
env::current_dir().into_diagnostic()?
};
let mut project = match Project::new(project_path, Terminal::default()) {
Ok(p) => p,
Err(e) => {
e.report();
process::exit(1);
}
};
let build_result = action(&mut project);
let warning_count = project.warnings.len();
for warning in project.warnings {
warning.report()
}
if let Err(err) = build_result {
err.report();
println!("\n{}", "Summary".purple().bold());
println!(
" {} error(s), {}",
err.len(),
format!("{warning_count} warning(s)").yellow(),
);
process::exit(1);
} else {
println!("\n{}", "Summary".purple().bold());
println!(
" 0 error, {}",
format!("{warning_count} warning(s)").yellow(),
);
}
Ok(())
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Terminal;
impl telemetry::EventListener for Terminal {
fn handle_event(&self, event: telemetry::Event) {
match event {
telemetry::Event::StartingCompilation {
name,
version,
root,
} => {
println!(
"{} {} {} ({})",
" Compiling".bold().purple(),
name.bold(),
version,
root.display().bright_blue()
);
}
telemetry::Event::BuildingDocumentation {
name,
version,
root,
} => {
println!(
"{} {} {} ({})",
" Generating documentation".bold().purple(),
name.bold(),
version,
root.to_str().unwrap_or("").bright_blue()
);
}
telemetry::Event::WaitingForBuildDirLock => {
println!("{}", "Waiting for build directory lock ...".bold().purple());
}
telemetry::Event::GeneratingUPLC { output_path, name } => {
println!(
"{} {} in {}",
" Generating".bold().purple(),
name.bold(),
output_path.display().bright_blue()
);
}
telemetry::Event::GeneratingDocFiles { output_path } => {
println!(
"{} in {}",
" Generating documentation files".bold().purple(),
output_path.to_str().unwrap_or("").bright_blue()
);
}
telemetry::Event::GeneratingUPLCFor { name, path } => {
println!(
"{} {}.{{{}}}",
" Generating Untyped Plutus Core for".bold().purple(),
path.to_str().unwrap_or("").blue(),
name.bright_blue(),
);
}
telemetry::Event::EvaluatingFunction { results } => {
println!("{}\n", " Evaluating function ...".bold().purple());
let (max_mem, max_cpu) = find_max_execution_units(&results);
for eval_info in &results {
println!(" {}", fmt_eval(eval_info, max_mem, max_cpu))
}
}
telemetry::Event::RunningTests => {
println!("{} {}\n", " Testing".bold().purple(), "...".bold());
}
telemetry::Event::FinishedTests { tests } => {
let (max_mem, max_cpu) = find_max_execution_units(&tests);
for (module, infos) in &group_by_module(&tests) {
let title = module.bold().blue().to_string();
let tests = infos
.iter()
.map(|eval_info| fmt_test(eval_info, max_mem, max_cpu, true))
.collect::<Vec<String>>()
.join("\n");
let summary = fmt_test_summary(infos, true);
println!(
"{}\n",
pretty::indent(
&pretty::open_box(&title, &tests, &summary, |border| border
.bright_black()
.to_string()),
4
)
);
}
}
telemetry::Event::DownloadingPackage { name } => {
println!("{} {}", " Downloading".bold().purple(), name.bold())
}
telemetry::Event::PackagesDownloaded { start, count } => {
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
let msg = match count {
1 => format!("1 package in {}", elapsed),
_ => format!("{} packages in {}", count, elapsed),
};
println!("{} {}", " Downloaded".bold().purple(), msg.bold())
}
telemetry::Event::ResolvingVersions => {
println!("{}", " Resolving versions".bold().purple(),)
}
}
}
}
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
let EvalInfo {
success,
script,
spent_budget,
logs,
..
} = eval_info;
let ExBudget { mem, cpu } = spent_budget;
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
let test = format!(
"{status} [mem: {mem_unit}, cpu: {cpu_unit}] {module}",
status = if *success {
pretty::style_if(styled, "PASS".to_string(), |s| s.bold().green().to_string())
} else {
pretty::style_if(styled, "FAIL".to_string(), |s| s.bold().red().to_string())
},
mem_unit = pretty::style_if(styled, mem_pad, |s| s.bright_white().to_string()),
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s.bright_white().to_string()),
module = pretty::style_if(styled, script.name.clone(), |s| s.bright_blue().to_string()),
);
let logs = if logs.is_empty() {
String::new()
} else {
logs.iter()
.map(|line| {
format!(
"{arrow} {styled_line}",
arrow = "".bright_yellow(),
styled_line = line.bright_black()
)
})
.collect::<Vec<_>>()
.join("\n")
};
if logs.is_empty() {
test
} else {
[test, logs].join("\n")
}
}
fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String {
let (n_passed, n_failed) = tests
.iter()
.fold((0, 0), |(n_passed, n_failed), test_info| {
if test_info.success {
(n_passed + 1, n_failed)
} else {
(n_passed, n_failed + 1)
}
});
format!(
"{} | {} | {}",
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
.bold()
.to_string()),
pretty::style_if(styled, format!("{} passed", n_passed), |s| s
.bright_green()
.bold()
.to_string()),
pretty::style_if(styled, format!("{} failed", n_failed), |s| s
.bright_red()
.bold()
.to_string()),
)
}
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize) -> String {
let EvalInfo {
output,
script,
spent_budget,
..
} = eval_info;
let ExBudget { mem, cpu } = spent_budget;
format!(
" {}::{} [mem: {}, cpu: {}]\n\n ╰─▶ {}",
script.module.blue(),
script.name.bright_blue(),
pretty::pad_left(mem.to_string(), max_mem, " "),
pretty::pad_left(cpu.to_string(), max_cpu, " "),
output
.as_ref()
.map(|x| format!("{}", x))
.unwrap_or_else(|| "Error.".to_string()),
)
}
fn group_by_module(infos: &Vec<EvalInfo>) -> BTreeMap<String, Vec<&EvalInfo>> {
let mut modules = BTreeMap::new();
for eval_info in infos {
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
xs.push(eval_info);
}
modules
}
fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) {
let (max_mem, max_cpu) = xs.iter().fold(
(0, 0),
|(max_mem, max_cpu), EvalInfo { spent_budget, .. }| {
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
(spent_budget.mem, spent_budget.cpu)
} else if spent_budget.mem > max_mem {
(spent_budget.mem, max_cpu)
} else if spent_budget.cpu > max_cpu {
(max_mem, spent_budget.cpu)
} else {
(max_mem, max_cpu)
}
},
);
(max_mem.to_string().len(), max_cpu.to_string().len())
}

44
crates/aiken/src/main.rs Normal file
View File

@@ -0,0 +1,44 @@
use aiken::cmd::{build, check, docs, fmt, lsp, new, tx, uplc};
use clap::Parser;
/// Aiken: a smart-contract language and toolchain for Cardano
#[derive(Parser)]
#[clap(version, about, long_about = None)]
#[clap(propagate_version = true)]
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
pub enum Cmd {
New(new::Args),
Fmt(fmt::Args),
Build(build::Args),
Check(check::Args),
Docs(docs::Args),
#[clap(hide = true)]
Lsp(lsp::Args),
#[clap(subcommand)]
Tx(tx::Cmd),
#[clap(subcommand)]
Uplc(uplc::Cmd),
}
impl Default for Cmd {
fn default() -> Self {
Self::parse()
}
}
fn main() -> miette::Result<()> {
miette::set_panic_hook();
match Cmd::default() {
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::Check(args) => check::exec(args),
Cmd::Lsp(args) => lsp::exec(args),
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),
}
}