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:
KtorZ 2023-11-25 14:48:22 +01:00
parent 777d30b8ac
commit 6c039708c3
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
11 changed files with 484 additions and 560 deletions

View File

@ -1,5 +1,11 @@
use crate::pretty;
use crate::script::EvalInfo; 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 { pub trait EventListener {
fn handle_event(&self, _event: Event) {} 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())
}

View File

@ -1,14 +1,57 @@
use miette::IntoDiagnostic; use crate::{telemetry::Terminal, Project};
use miette::{Diagnostic, IntoDiagnostic};
use notify::{Event, RecursiveMode, Watcher}; use notify::{Event, RecursiveMode, Watcher};
use owo_colors::{OwoColorize, Stream::Stderr};
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
env, env,
ffi::OsStr, ffi::OsStr,
path::PathBuf, fmt::{self, Display},
path::Path,
sync::{Arc, Mutex}, 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 /// A default filter for file events that catches the most relevant "source" changes
pub fn default_filter(evt: &Event) -> bool { 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 /// Run a function each time a file in the project changes
/// ///
/// ```text /// ```text
@ -41,19 +143,19 @@ pub fn default_filter(evt: &Event) -> bool {
/// Ok(()) /// Ok(())
/// }); /// });
/// ``` /// ```
pub fn watch_project<T, F, A>( pub fn watch_project<F, A>(
directory: Option<PathBuf>, directory: Option<&Path>,
events: T,
filter: F, filter: F,
debounce: u32, debounce: u32,
mut action: A, mut action: A,
) -> miette::Result<()> ) -> miette::Result<()>
where where
T: Copy + telemetry::EventListener,
F: Fn(&Event) -> bool, 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 // Set up a queue for events, primarily so we can debounce on related events
let queue = Arc::new(Mutex::new(VecDeque::new())); 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 we have an event that survived the filter, then we can construct the project and invoke the action
if latest.is_some() { if latest.is_some() {
let mut project = match Project::new(project_path.clone(), events) { print!("{esc}c", esc = 27 as char);
Ok(p) => p, println!(
Err(e) => { "{} ...",
// TODO: what should we actually do here? " Watching"
e.report(); .if_supports_color(Stderr, |s| s.bold())
return Err(miette::Report::msg("??")); .if_supports_color(Stderr, |s| s.purple()),
} );
}; with_project(directory, false, &mut action).unwrap_or(())
// 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("??")))?;
} }
} }
} }

View File

@ -1,5 +1,5 @@
use crate::with_project;
use aiken_lang::ast::Tracing; use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
/// Compute a validator's address. /// Compute a validator's address.
@ -34,7 +34,7 @@ pub fn exec(
rebuild, rebuild,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory, false, |p| { with_project(directory.as_deref(), false, |p| {
if rebuild { if rebuild {
p.build(false, Tracing::NoTraces)?; p.build(false, Tracing::NoTraces)?;
} }

View File

@ -1,4 +1,3 @@
use crate::with_project;
use aiken_project::{ use aiken_project::{
blueprint::{ blueprint::{
self, self,
@ -7,6 +6,7 @@ use aiken_project::{
}, },
error::Error, error::Error,
pretty::multiline, pretty::multiline,
watch::with_project,
}; };
use inquire; use inquire;
use num_bigint::BigInt; use num_bigint::BigInt;

View File

@ -1,5 +1,5 @@
use crate::with_project;
use aiken_lang::ast::Tracing; use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
/// Compute a validator's hash /// Compute a validator's hash
@ -29,7 +29,7 @@ pub fn exec(
rebuild, rebuild,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory, false, |p| { with_project(directory.as_deref(), false, |p| {
if rebuild { if rebuild {
p.build(false, Tracing::NoTraces)?; p.build(false, Tracing::NoTraces)?;
} }

View File

@ -1,5 +1,5 @@
use crate::with_project;
use aiken_lang::ast::Tracing; use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
/// Compute a minting scripts Policy ID /// Compute a minting scripts Policy ID
@ -29,7 +29,7 @@ pub fn exec(
rebuild, rebuild,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory, false, |p| { with_project(directory.as_deref(), false, |p| {
if rebuild { if rebuild {
p.build(false, Tracing::NoTraces)?; p.build(false, Tracing::NoTraces)?;
} }

View File

@ -1,3 +1,4 @@
use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(clap::Args)] #[derive(clap::Args)]
@ -27,5 +28,7 @@ pub fn exec(
keep_traces, keep_traces,
}: Args, }: Args,
) -> miette::Result<()> { ) -> 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())
})
} }

View File

@ -1,10 +1,6 @@
use aiken_project::watch::{self, watch_project, with_project};
use std::path::PathBuf; use std::path::PathBuf;
use aiken_project::watch;
use owo_colors::{OwoColorize, Stream::Stderr};
use crate::Terminal;
#[derive(clap::Args)] #[derive(clap::Args)]
/// Type-check an Aiken project /// Type-check an Aiken project
pub struct Args { pub struct Args {
@ -27,10 +23,6 @@ pub struct Args {
#[clap(long)] #[clap(long)]
watch: bool, watch: bool,
/// When enabled, clear the screen before running
#[clap(long)]
clear: bool,
/// Only run tests if they match any of these strings. /// 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 module with `-m aiken/list` or `-m list`.
/// You can match a test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"` /// 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, exact_match,
no_traces, no_traces,
watch, watch,
clear,
.. ..
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
if watch { if watch {
watch::watch_project(directory, Terminal, watch::default_filter, 500, |p| { watch_project(directory.as_deref(), watch::default_filter, 500, |p| {
if clear { p.check(
println!("{esc}c", esc = 27 as char);
}
let build_result = p.check(
skip_tests, skip_tests,
match_tests.clone(), match_tests.clone(),
debug, debug,
exact_match, exact_match,
(!no_traces).into(), (!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 { } else {
crate::with_project(directory, deny, |p| { with_project(directory.as_deref(), deny, |p| {
p.check( p.check(
skip_tests, skip_tests,
match_tests.clone(), match_tests.clone(),

View File

@ -1,3 +1,4 @@
use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(clap::Args)] #[derive(clap::Args)]
@ -22,5 +23,5 @@ pub fn exec(
destination, destination,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
crate::with_project(directory, deny, |p| p.docs(destination.clone())) with_project(directory.as_deref(), deny, |p| p.docs(destination.clone()))
} }

View File

@ -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())
}

View File

@ -1,13 +1,15 @@
use aiken::cmd::{ use aiken_project::{config, pretty};
use cmd::{
blueprint::{self, address}, blueprint::{self, address},
build, check, completion, docs, fmt, lsp, new, build, check, completion, docs, fmt, lsp, new,
packages::{self, add}, packages::{self, add},
tx, uplc, Cmd, tx, uplc, Cmd,
}; };
use aiken_project::{config, pretty};
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use std::process; use std::process;
mod cmd;
fn main() { fn main() {
panic_handler(); panic_handler();