Rework 'watch_project' to reuse 'with_project'
Also removed the 'clear' flag to do it by default instead of clogging the terminal view. This now works pretty nicely, and the logic is back under `aiken_project`.
This commit is contained in:
parent
777d30b8ac
commit
6c039708c3
|
@ -1,5 +1,11 @@
|
|||
use crate::pretty;
|
||||
use crate::script::EvalInfo;
|
||||
use std::{fmt::Display, path::PathBuf};
|
||||
use owo_colors::{
|
||||
OwoColorize,
|
||||
Stream::{self, Stderr},
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
||||
pub trait EventListener {
|
||||
fn handle_event(&self, _event: Event) {}
|
||||
|
@ -64,3 +70,339 @@ impl Display for DownloadSource {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Terminal;
|
||||
|
||||
impl EventListener for Terminal {
|
||||
fn handle_event(&self, event: Event) {
|
||||
match event {
|
||||
Event::StartingCompilation {
|
||||
name,
|
||||
version,
|
||||
root,
|
||||
} => {
|
||||
eprintln!(
|
||||
"{} {} {} ({})",
|
||||
" Compiling"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
name.if_supports_color(Stderr, |s| s.bold()),
|
||||
version,
|
||||
root.display()
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
Event::BuildingDocumentation {
|
||||
name,
|
||||
version,
|
||||
root,
|
||||
} => {
|
||||
eprintln!(
|
||||
"{} {} {} ({})",
|
||||
" Generating documentation"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
name.if_supports_color(Stderr, |s| s.bold()),
|
||||
version,
|
||||
root.to_str()
|
||||
.unwrap_or("")
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
Event::WaitingForBuildDirLock => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
"Waiting for build directory lock ..."
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple())
|
||||
);
|
||||
}
|
||||
Event::DumpingUPLC { path } => {
|
||||
eprintln!(
|
||||
"{} {} ({})",
|
||||
" Exporting"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
"UPLC".if_supports_color(Stderr, |s| s.bold()),
|
||||
path.display()
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
Event::GeneratingBlueprint { path } => {
|
||||
eprintln!(
|
||||
"{} {} ({})",
|
||||
" Generating"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
"project's blueprint".if_supports_color(Stderr, |s| s.bold()),
|
||||
path.display()
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
Event::GeneratingDocFiles { output_path } => {
|
||||
eprintln!(
|
||||
"{} in {}",
|
||||
" Generating documentation files"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
output_path
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
Event::GeneratingUPLCFor { name, path } => {
|
||||
eprintln!(
|
||||
"{} {}.{{{}}}",
|
||||
" Generating UPLC for"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
path.to_str()
|
||||
.unwrap_or("")
|
||||
.if_supports_color(Stderr, |s| s.blue()),
|
||||
name.if_supports_color(Stderr, |s| s.bright_blue()),
|
||||
);
|
||||
}
|
||||
Event::EvaluatingFunction { results } => {
|
||||
eprintln!(
|
||||
"{}\n",
|
||||
" Evaluating function ..."
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.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, Stderr))
|
||||
}
|
||||
}
|
||||
Event::RunningTests => {
|
||||
eprintln!(
|
||||
"{} {}\n",
|
||||
" Testing"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
"...".if_supports_color(Stderr, |s| s.bold())
|
||||
);
|
||||
}
|
||||
Event::FinishedTests { tests } => {
|
||||
let (max_mem, max_cpu) = find_max_execution_units(&tests);
|
||||
|
||||
for (module, infos) in &group_by_module(&tests) {
|
||||
let title = module
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.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);
|
||||
|
||||
eprintln!(
|
||||
"{}\n",
|
||||
pretty::indent(
|
||||
&pretty::open_box(&title, &tests, &summary, |border| border
|
||||
.if_supports_color(Stderr, |s| s.bright_black())
|
||||
.to_string()),
|
||||
4
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Event::ResolvingPackages { name } => {
|
||||
eprintln!(
|
||||
"{} {}",
|
||||
" Resolving"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
name.if_supports_color(Stderr, |s| s.bold())
|
||||
)
|
||||
}
|
||||
Event::PackageResolveFallback { name } => {
|
||||
eprintln!(
|
||||
"{} {}\n ↳ You're seeing this message because the package version is unpinned and the network is not accessible.",
|
||||
" Using"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.yellow()),
|
||||
format!("uncertain local version for {name}")
|
||||
.if_supports_color(Stderr, |s| s.yellow())
|
||||
)
|
||||
}
|
||||
Event::PackagesDownloaded {
|
||||
start,
|
||||
count,
|
||||
source,
|
||||
} => {
|
||||
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
|
||||
|
||||
let msg = match count {
|
||||
1 => format!("1 package in {elapsed}"),
|
||||
_ => format!("{count} packages in {elapsed}"),
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"{} {} from {source}",
|
||||
match source {
|
||||
DownloadSource::Network => " Downloaded",
|
||||
DownloadSource::Cache => " Fetched",
|
||||
}
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
msg.if_supports_color(Stderr, |s| s.bold())
|
||||
)
|
||||
}
|
||||
Event::ResolvingVersions => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
" Resolving dependencies"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.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.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.green())
|
||||
.to_string()
|
||||
})
|
||||
} else {
|
||||
pretty::style_if(styled, "FAIL".to_string(), |s| {
|
||||
s.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.red())
|
||||
.to_string()
|
||||
})
|
||||
},
|
||||
mem_unit = pretty::style_if(styled, mem_pad, |s| s
|
||||
.if_supports_color(Stderr, |s| s.cyan())
|
||||
.to_string()),
|
||||
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
||||
.if_supports_color(Stderr, |s| s.cyan())
|
||||
.to_string()),
|
||||
module = pretty::style_if(styled, script.name.clone(), |s| s
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
.to_string()),
|
||||
);
|
||||
|
||||
let logs = if logs.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
logs.iter()
|
||||
.map(|line| {
|
||||
format!(
|
||||
"{arrow} {styled_line}",
|
||||
arrow = "↳".if_supports_color(Stderr, |s| s.bright_yellow()),
|
||||
styled_line = line.if_supports_color(Stderr, |s| s.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
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{n_passed} passed"), |s| s
|
||||
.if_supports_color(Stderr, |s| s.bright_green())
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{n_failed} failed"), |s| s
|
||||
.if_supports_color(Stderr, |s| s.bright_red())
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
|
||||
let EvalInfo {
|
||||
output,
|
||||
script,
|
||||
spent_budget,
|
||||
..
|
||||
} = eval_info;
|
||||
|
||||
let ExBudget { mem, cpu } = spent_budget;
|
||||
|
||||
format!(
|
||||
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
||||
script.module.if_supports_color(stream, |s| s.blue()),
|
||||
script.name.if_supports_color(stream, |s| s.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())
|
||||
}
|
||||
|
|
|
@ -1,14 +1,57 @@
|
|||
use miette::IntoDiagnostic;
|
||||
use crate::{telemetry::Terminal, Project};
|
||||
use miette::{Diagnostic, IntoDiagnostic};
|
||||
use notify::{Event, RecursiveMode, Watcher};
|
||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
env,
|
||||
ffi::OsStr,
|
||||
path::PathBuf,
|
||||
fmt::{self, Display},
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::{telemetry, Project};
|
||||
#[derive(Debug, Diagnostic, thiserror::Error)]
|
||||
enum ExitFailure {
|
||||
#[error("")]
|
||||
ExitFailure,
|
||||
}
|
||||
|
||||
impl ExitFailure {
|
||||
fn into_report() -> miette::Report {
|
||||
ExitFailure::ExitFailure.into()
|
||||
}
|
||||
}
|
||||
|
||||
struct Summary {
|
||||
warning_count: usize,
|
||||
error_count: usize,
|
||||
}
|
||||
|
||||
impl Display for Summary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&format!(
|
||||
"{}\n {} {}, {} {}",
|
||||
"Summary"
|
||||
.if_supports_color(Stderr, |s| s.purple())
|
||||
.if_supports_color(Stderr, |s| s.bold()),
|
||||
self.error_count,
|
||||
if self.error_count == 1 {
|
||||
"error"
|
||||
} else {
|
||||
"errors"
|
||||
}
|
||||
.if_supports_color(Stderr, |s| s.red()),
|
||||
self.warning_count,
|
||||
if self.warning_count == 1 {
|
||||
"warning"
|
||||
} else {
|
||||
"warnings"
|
||||
}
|
||||
.if_supports_color(Stderr, |s| s.yellow()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A default filter for file events that catches the most relevant "source" changes
|
||||
pub fn default_filter(evt: &Event) -> bool {
|
||||
|
@ -30,6 +73,65 @@ pub fn default_filter(evt: &Event) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_project<A>(directory: Option<&Path>, deny: bool, mut action: A) -> miette::Result<()>
|
||||
where
|
||||
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
|
||||
{
|
||||
let project_path = if let Some(d) = directory {
|
||||
d.to_path_buf()
|
||||
} else {
|
||||
env::current_dir().into_diagnostic()?
|
||||
};
|
||||
|
||||
let mut project = match Project::new(project_path, Terminal) {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => {
|
||||
e.report();
|
||||
Err(ExitFailure::into_report())
|
||||
}
|
||||
}?;
|
||||
|
||||
let build_result = action(&mut project);
|
||||
|
||||
let warnings = project.warnings();
|
||||
|
||||
let warning_count = warnings.len();
|
||||
|
||||
for warning in &warnings {
|
||||
warning.report()
|
||||
}
|
||||
|
||||
if let Err(errs) = build_result {
|
||||
for err in &errs {
|
||||
err.report()
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"\n{}",
|
||||
Summary {
|
||||
warning_count,
|
||||
error_count: errs.len(),
|
||||
}
|
||||
);
|
||||
|
||||
return Err(ExitFailure::into_report());
|
||||
} else {
|
||||
eprintln!(
|
||||
"\n{}",
|
||||
Summary {
|
||||
error_count: 0,
|
||||
warning_count
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if warning_count > 0 && deny {
|
||||
Err(ExitFailure::into_report())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a function each time a file in the project changes
|
||||
///
|
||||
/// ```text
|
||||
|
@ -41,19 +143,19 @@ pub fn default_filter(evt: &Event) -> bool {
|
|||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn watch_project<T, F, A>(
|
||||
directory: Option<PathBuf>,
|
||||
events: T,
|
||||
pub fn watch_project<F, A>(
|
||||
directory: Option<&Path>,
|
||||
filter: F,
|
||||
debounce: u32,
|
||||
mut action: A,
|
||||
) -> miette::Result<()>
|
||||
where
|
||||
T: Copy + telemetry::EventListener,
|
||||
F: Fn(&Event) -> bool,
|
||||
A: FnMut(&mut Project<T>) -> Result<(), Vec<crate::error::Error>>,
|
||||
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
|
||||
{
|
||||
let project_path = directory.unwrap_or(env::current_dir().into_diagnostic()?);
|
||||
let project_path = directory
|
||||
.map(|p| p.to_path_buf())
|
||||
.unwrap_or(env::current_dir().into_diagnostic()?);
|
||||
|
||||
// Set up a queue for events, primarily so we can debounce on related events
|
||||
let queue = Arc::new(Mutex::new(VecDeque::new()));
|
||||
|
@ -108,18 +210,14 @@ where
|
|||
|
||||
// If we have an event that survived the filter, then we can construct the project and invoke the action
|
||||
if latest.is_some() {
|
||||
let mut project = match Project::new(project_path.clone(), events) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
// TODO: what should we actually do here?
|
||||
e.report();
|
||||
return Err(miette::Report::msg("??"));
|
||||
}
|
||||
};
|
||||
|
||||
// Invoke the action, and abort on an error
|
||||
// TODO: what should we actually do with the error here?
|
||||
action(&mut project).or(Err(miette::Report::msg("??")))?;
|
||||
print!("{esc}c", esc = 27 as char);
|
||||
println!(
|
||||
"{} ...",
|
||||
" Watching"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
);
|
||||
with_project(directory, false, &mut action).unwrap_or(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::with_project;
|
||||
use aiken_lang::ast::Tracing;
|
||||
use aiken_project::watch::with_project;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Compute a validator's address.
|
||||
|
@ -34,7 +34,7 @@ pub fn exec(
|
|||
rebuild,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory, false, |p| {
|
||||
with_project(directory.as_deref(), false, |p| {
|
||||
if rebuild {
|
||||
p.build(false, Tracing::NoTraces)?;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::with_project;
|
||||
use aiken_project::{
|
||||
blueprint::{
|
||||
self,
|
||||
|
@ -7,6 +6,7 @@ use aiken_project::{
|
|||
},
|
||||
error::Error,
|
||||
pretty::multiline,
|
||||
watch::with_project,
|
||||
};
|
||||
use inquire;
|
||||
use num_bigint::BigInt;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::with_project;
|
||||
use aiken_lang::ast::Tracing;
|
||||
use aiken_project::watch::with_project;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Compute a validator's hash
|
||||
|
@ -29,7 +29,7 @@ pub fn exec(
|
|||
rebuild,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory, false, |p| {
|
||||
with_project(directory.as_deref(), false, |p| {
|
||||
if rebuild {
|
||||
p.build(false, Tracing::NoTraces)?;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::with_project;
|
||||
use aiken_lang::ast::Tracing;
|
||||
use aiken_project::watch::with_project;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Compute a minting scripts Policy ID
|
||||
|
@ -29,7 +29,7 @@ pub fn exec(
|
|||
rebuild,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory, false, |p| {
|
||||
with_project(directory.as_deref(), false, |p| {
|
||||
if rebuild {
|
||||
p.build(false, Tracing::NoTraces)?;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use aiken_project::watch::with_project;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
|
@ -27,5 +28,7 @@ pub fn exec(
|
|||
keep_traces,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
crate::with_project(directory, deny, |p| p.build(uplc, keep_traces.into()))
|
||||
with_project(directory.as_deref(), deny, |p| {
|
||||
p.build(uplc, keep_traces.into())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
use aiken_project::watch::{self, watch_project, with_project};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use aiken_project::watch;
|
||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||
|
||||
use crate::Terminal;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
/// Type-check an Aiken project
|
||||
pub struct Args {
|
||||
|
@ -27,10 +23,6 @@ pub struct Args {
|
|||
#[clap(long)]
|
||||
watch: bool,
|
||||
|
||||
/// When enabled, clear the screen before running
|
||||
#[clap(long)]
|
||||
clear: bool,
|
||||
|
||||
/// Only run tests if they match any of these strings.
|
||||
/// You can match a module with `-m aiken/list` or `-m list`.
|
||||
/// You can match a test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"`
|
||||
|
@ -57,77 +49,21 @@ pub fn exec(
|
|||
exact_match,
|
||||
no_traces,
|
||||
watch,
|
||||
clear,
|
||||
..
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
if watch {
|
||||
watch::watch_project(directory, Terminal, watch::default_filter, 500, |p| {
|
||||
if clear {
|
||||
println!("{esc}c", esc = 27 as char);
|
||||
}
|
||||
let build_result = p.check(
|
||||
watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
|
||||
p.check(
|
||||
skip_tests,
|
||||
match_tests.clone(),
|
||||
debug,
|
||||
exact_match,
|
||||
(!no_traces).into(),
|
||||
);
|
||||
|
||||
let warnings = p.warnings();
|
||||
|
||||
let warning_count = warnings.len();
|
||||
|
||||
for warning in &warnings {
|
||||
warning.report()
|
||||
}
|
||||
|
||||
let plural = if warning_count == 1 { "" } else { "s" };
|
||||
|
||||
if let Err(errs) = build_result {
|
||||
for err in &errs {
|
||||
err.report()
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"\n{}",
|
||||
"Summary"
|
||||
.if_supports_color(Stderr, |s| s.purple())
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
);
|
||||
|
||||
let warning_text = format!("{warning_count} warning{plural}");
|
||||
|
||||
let plural = if errs.len() == 1 { "" } else { "s" };
|
||||
|
||||
let error_text = format!("{} error{}", errs.len(), plural);
|
||||
|
||||
let full_summary = format!(
|
||||
" {}, {}",
|
||||
error_text.if_supports_color(Stderr, |s| s.red()),
|
||||
warning_text.if_supports_color(Stderr, |s| s.yellow())
|
||||
);
|
||||
|
||||
eprintln!("{full_summary}");
|
||||
} else {
|
||||
eprintln!(
|
||||
"\n{}",
|
||||
"Summary"
|
||||
.if_supports_color(Stderr, |s| s.purple())
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
);
|
||||
|
||||
let warning_text = format!("{warning_count} warning{plural}");
|
||||
|
||||
eprintln!(
|
||||
" 0 errors, {}",
|
||||
warning_text.if_supports_color(Stderr, |s| s.yellow()),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
)
|
||||
})
|
||||
} else {
|
||||
crate::with_project(directory, deny, |p| {
|
||||
with_project(directory.as_deref(), deny, |p| {
|
||||
p.check(
|
||||
skip_tests,
|
||||
match_tests.clone(),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use aiken_project::watch::with_project;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
|
@ -22,5 +23,5 @@ pub fn exec(
|
|||
destination,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
crate::with_project(directory, deny, |p| p.docs(destination.clone()))
|
||||
with_project(directory.as_deref(), deny, |p| p.docs(destination.clone()))
|
||||
}
|
||||
|
|
|
@ -1,458 +0,0 @@
|
|||
use aiken_project::{
|
||||
pretty,
|
||||
script::EvalInfo,
|
||||
telemetry::{self, DownloadSource},
|
||||
Project,
|
||||
};
|
||||
use miette::Diagnostic;
|
||||
use miette::IntoDiagnostic;
|
||||
use owo_colors::{
|
||||
OwoColorize,
|
||||
Stream::{self, Stderr},
|
||||
};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
env,
|
||||
fmt::{self, Display},
|
||||
path::PathBuf,
|
||||
};
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
||||
pub mod cmd;
|
||||
|
||||
#[derive(Debug, Diagnostic, thiserror::Error)]
|
||||
enum ExitFailure {
|
||||
#[error("")]
|
||||
ExitFailure,
|
||||
}
|
||||
|
||||
impl ExitFailure {
|
||||
fn into_report() -> miette::Report {
|
||||
ExitFailure::ExitFailure.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_project<A>(directory: Option<PathBuf>, deny: bool, mut action: A) -> miette::Result<()>
|
||||
where
|
||||
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<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) {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => {
|
||||
e.report();
|
||||
Err(ExitFailure::into_report())
|
||||
}
|
||||
}?;
|
||||
|
||||
let build_result = action(&mut project);
|
||||
|
||||
let warnings = project.warnings();
|
||||
|
||||
let warning_count = warnings.len();
|
||||
|
||||
for warning in &warnings {
|
||||
warning.report()
|
||||
}
|
||||
|
||||
if let Err(errs) = build_result {
|
||||
for err in &errs {
|
||||
err.report()
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"\n{}",
|
||||
Summary {
|
||||
warning_count,
|
||||
error_count: errs.len(),
|
||||
}
|
||||
);
|
||||
|
||||
return Err(ExitFailure::into_report());
|
||||
} else {
|
||||
eprintln!(
|
||||
"\n{}",
|
||||
Summary {
|
||||
error_count: 0,
|
||||
warning_count
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if warning_count > 0 && deny {
|
||||
Err(ExitFailure::into_report())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Summary {
|
||||
warning_count: usize,
|
||||
error_count: usize,
|
||||
}
|
||||
|
||||
impl Display for Summary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&format!(
|
||||
"{}\n {} {}, {} {}",
|
||||
"Summary"
|
||||
.if_supports_color(Stderr, |s| s.purple())
|
||||
.if_supports_color(Stderr, |s| s.bold()),
|
||||
self.error_count,
|
||||
if self.error_count == 1 {
|
||||
"error"
|
||||
} else {
|
||||
"errors"
|
||||
}
|
||||
.if_supports_color(Stderr, |s| s.red()),
|
||||
self.warning_count,
|
||||
if self.warning_count == 1 {
|
||||
"warning"
|
||||
} else {
|
||||
"warnings"
|
||||
}
|
||||
.if_supports_color(Stderr, |s| s.yellow()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
} => {
|
||||
eprintln!(
|
||||
"{} {} {} ({})",
|
||||
" Compiling"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
name.if_supports_color(Stderr, |s| s.bold()),
|
||||
version,
|
||||
root.display()
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
telemetry::Event::BuildingDocumentation {
|
||||
name,
|
||||
version,
|
||||
root,
|
||||
} => {
|
||||
eprintln!(
|
||||
"{} {} {} ({})",
|
||||
" Generating documentation"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
name.if_supports_color(Stderr, |s| s.bold()),
|
||||
version,
|
||||
root.to_str()
|
||||
.unwrap_or("")
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
telemetry::Event::WaitingForBuildDirLock => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
"Waiting for build directory lock ..."
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple())
|
||||
);
|
||||
}
|
||||
telemetry::Event::DumpingUPLC { path } => {
|
||||
eprintln!(
|
||||
"{} {} ({})",
|
||||
" Exporting"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
"UPLC".if_supports_color(Stderr, |s| s.bold()),
|
||||
path.display()
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
telemetry::Event::GeneratingBlueprint { path } => {
|
||||
eprintln!(
|
||||
"{} {} ({})",
|
||||
" Generating"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
"project's blueprint".if_supports_color(Stderr, |s| s.bold()),
|
||||
path.display()
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
telemetry::Event::GeneratingDocFiles { output_path } => {
|
||||
eprintln!(
|
||||
"{} in {}",
|
||||
" Generating documentation files"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
output_path
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
);
|
||||
}
|
||||
telemetry::Event::GeneratingUPLCFor { name, path } => {
|
||||
eprintln!(
|
||||
"{} {}.{{{}}}",
|
||||
" Generating UPLC for"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
path.to_str()
|
||||
.unwrap_or("")
|
||||
.if_supports_color(Stderr, |s| s.blue()),
|
||||
name.if_supports_color(Stderr, |s| s.bright_blue()),
|
||||
);
|
||||
}
|
||||
telemetry::Event::EvaluatingFunction { results } => {
|
||||
eprintln!(
|
||||
"{}\n",
|
||||
" Evaluating function ..."
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.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, Stderr))
|
||||
}
|
||||
}
|
||||
telemetry::Event::RunningTests => {
|
||||
eprintln!(
|
||||
"{} {}\n",
|
||||
" Testing"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
"...".if_supports_color(Stderr, |s| s.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
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.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);
|
||||
|
||||
eprintln!(
|
||||
"{}\n",
|
||||
pretty::indent(
|
||||
&pretty::open_box(&title, &tests, &summary, |border| border
|
||||
.if_supports_color(Stderr, |s| s.bright_black())
|
||||
.to_string()),
|
||||
4
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
telemetry::Event::ResolvingPackages { name } => {
|
||||
eprintln!(
|
||||
"{} {}",
|
||||
" Resolving"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
name.if_supports_color(Stderr, |s| s.bold())
|
||||
)
|
||||
}
|
||||
telemetry::Event::PackageResolveFallback { name } => {
|
||||
eprintln!(
|
||||
"{} {}\n ↳ You're seeing this message because the package version is unpinned and the network is not accessible.",
|
||||
" Using"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.yellow()),
|
||||
format!("uncertain local version for {name}")
|
||||
.if_supports_color(Stderr, |s| s.yellow())
|
||||
)
|
||||
}
|
||||
telemetry::Event::PackagesDownloaded {
|
||||
start,
|
||||
count,
|
||||
source,
|
||||
} => {
|
||||
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
|
||||
|
||||
let msg = match count {
|
||||
1 => format!("1 package in {elapsed}"),
|
||||
_ => format!("{count} packages in {elapsed}"),
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"{} {} from {source}",
|
||||
match source {
|
||||
DownloadSource::Network => " Downloaded",
|
||||
DownloadSource::Cache => " Fetched",
|
||||
}
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
msg.if_supports_color(Stderr, |s| s.bold())
|
||||
)
|
||||
}
|
||||
telemetry::Event::ResolvingVersions => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
" Resolving dependencies"
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.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.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.green())
|
||||
.to_string()
|
||||
})
|
||||
} else {
|
||||
pretty::style_if(styled, "FAIL".to_string(), |s| {
|
||||
s.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.red())
|
||||
.to_string()
|
||||
})
|
||||
},
|
||||
mem_unit = pretty::style_if(styled, mem_pad, |s| s
|
||||
.if_supports_color(Stderr, |s| s.cyan())
|
||||
.to_string()),
|
||||
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
||||
.if_supports_color(Stderr, |s| s.cyan())
|
||||
.to_string()),
|
||||
module = pretty::style_if(styled, script.name.clone(), |s| s
|
||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||
.to_string()),
|
||||
);
|
||||
|
||||
let logs = if logs.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
logs.iter()
|
||||
.map(|line| {
|
||||
format!(
|
||||
"{arrow} {styled_line}",
|
||||
arrow = "↳".if_supports_color(Stderr, |s| s.bright_yellow()),
|
||||
styled_line = line.if_supports_color(Stderr, |s| s.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
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{n_passed} passed"), |s| s
|
||||
.if_supports_color(Stderr, |s| s.bright_green())
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{n_failed} failed"), |s| s
|
||||
.if_supports_color(Stderr, |s| s.bright_red())
|
||||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
|
||||
let EvalInfo {
|
||||
output,
|
||||
script,
|
||||
spent_budget,
|
||||
..
|
||||
} = eval_info;
|
||||
|
||||
let ExBudget { mem, cpu } = spent_budget;
|
||||
|
||||
format!(
|
||||
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
||||
script.module.if_supports_color(stream, |s| s.blue()),
|
||||
script.name.if_supports_color(stream, |s| s.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())
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
use aiken::cmd::{
|
||||
use aiken_project::{config, pretty};
|
||||
use cmd::{
|
||||
blueprint::{self, address},
|
||||
build, check, completion, docs, fmt, lsp, new,
|
||||
packages::{self, add},
|
||||
tx, uplc, Cmd,
|
||||
};
|
||||
use aiken_project::{config, pretty};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::process;
|
||||
|
||||
mod cmd;
|
||||
|
||||
fn main() {
|
||||
panic_handler();
|
||||
|
||||
|
|
Loading…
Reference in New Issue