Implement a basic watch command
This adds the following command ``` aiken watch ``` There are some open questions to answer, though: - I really like the ergonomics of `aiken watch`; but it also makes sense as a flag to `aiken check` or `aiken build` etc.; should we just support the flag, the command, or both? - Right now I duplicated the with_project method, because it forces process::exit(1); Should we refactor this, and if so, how? - Are there other configuration options we want?
This commit is contained in:
parent
45177cd08b
commit
689a41ded4
|
@ -25,6 +25,7 @@ hex = "0.4.3"
|
|||
ignore = "0.4.20"
|
||||
indoc = "2.0"
|
||||
miette = { version = "5.5.0", features = ["fancy"] }
|
||||
notify = "6.1.1"
|
||||
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
||||
pallas-addresses = "0.18.0"
|
||||
pallas-codec = "0.18.0"
|
||||
|
|
|
@ -18,6 +18,10 @@ pub struct Args {
|
|||
#[clap(long)]
|
||||
debug: bool,
|
||||
|
||||
// When enabled, re-run the command on file changes instead of exiting
|
||||
#[clap(long)]
|
||||
watch: 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}"`
|
||||
|
@ -43,6 +47,7 @@ pub fn exec(
|
|||
match_tests,
|
||||
exact_match,
|
||||
no_traces,
|
||||
watch,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
crate::with_project(directory, deny, |p| {
|
||||
|
|
|
@ -4,6 +4,7 @@ use clap::Parser;
|
|||
pub mod blueprint;
|
||||
pub mod build;
|
||||
pub mod check;
|
||||
pub mod watch;
|
||||
pub mod completion;
|
||||
pub mod docs;
|
||||
pub mod fmt;
|
||||
|
@ -23,6 +24,7 @@ pub enum Cmd {
|
|||
Build(build::Args),
|
||||
Address(blueprint::address::Args),
|
||||
Check(check::Args),
|
||||
Watch(watch::Args),
|
||||
Docs(docs::Args),
|
||||
Add(packages::add::Args),
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
use notify::{RecursiveMode, Watcher, Event, event::EventAttributes};
|
||||
use std::{path::Path, error::Error, time::SystemTime, collections::VecDeque, sync::{Mutex, Arc}};
|
||||
#[derive(clap::Args)]
|
||||
/// Type-check an Aiken project
|
||||
pub struct Args {
|
||||
/// Clear the screen between each run
|
||||
#[clap(long)]
|
||||
clear: bool,
|
||||
}
|
||||
|
||||
pub fn exec(Args { clear }: Args) -> miette::Result<()> {
|
||||
let project = Path::new("../sundae-contracts/aiken").to_path_buf().canonicalize().expect("");
|
||||
let build = project.join("build").canonicalize().expect("");
|
||||
let lock = project.join("aiken.lock");
|
||||
let queue = Arc::new(Mutex::new(VecDeque::new()));
|
||||
queue.lock().unwrap().push_back(Event { kind: notify::EventKind::Any, paths: vec![], attrs: EventAttributes::new() });
|
||||
|
||||
let queue_write = queue.clone();
|
||||
let mut watcher = notify::recommended_watcher(move |res: notify::Result<Event>| {
|
||||
match res {
|
||||
Ok(event) => {
|
||||
match event.kind {
|
||||
notify::EventKind::Create(_) |
|
||||
notify::EventKind::Modify(_) |
|
||||
notify::EventKind::Remove(_) => {
|
||||
let mut queue = queue_write.lock().expect("lock queue");
|
||||
queue.push_back(event.clone());
|
||||
drop(queue);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("watch error: {:?}", e)
|
||||
},
|
||||
};
|
||||
}).expect("watcher");
|
||||
let _ = watcher.watch(Path::new("../sundae-contracts/aiken"), RecursiveMode::Recursive);
|
||||
let queue_read = queue.clone();
|
||||
let mut last_evt = SystemTime::UNIX_EPOCH;
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
|
||||
let mut queue = queue_read.lock().expect("lock queue");
|
||||
let mut latest = None;
|
||||
while let Some(evt) = queue.pop_back() {
|
||||
if evt.paths.iter().any(|p| {
|
||||
let p = p.canonicalize().expect("");
|
||||
p.starts_with(&build) || p.starts_with(&lock)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
latest = Some(evt);
|
||||
}
|
||||
drop(queue);
|
||||
if let Some(evt) = latest {
|
||||
if clear {
|
||||
println!("{esc}c", esc = 27 as char);
|
||||
}
|
||||
let _ = crate::with_project_ok(Some(project.clone()), false, |p| {
|
||||
p.check(
|
||||
false,
|
||||
None,
|
||||
true,
|
||||
false,
|
||||
false.into(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -94,6 +94,88 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: we probably want to rework with_project slightly to avoid duplication here,
|
||||
// but this is a quick hack to get the aiken watch working
|
||||
pub fn with_project_ok<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) => p,
|
||||
Err(e) => {
|
||||
e.report();
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let build_result = action(&mut project);
|
||||
|
||||
let warnings = project.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}");
|
||||
|
||||
return Ok(());
|
||||
} 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()),
|
||||
);
|
||||
}
|
||||
|
||||
if warning_count > 0 && deny {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Terminal;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use aiken::cmd::{
|
||||
blueprint::{self, address},
|
||||
build, check, completion, docs, fmt, lsp, new,
|
||||
build, check, watch, completion, docs, fmt, lsp, new,
|
||||
packages::{self, add},
|
||||
tx, uplc, Cmd,
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ fn main() -> miette::Result<()> {
|
|||
Cmd::Build(args) => build::exec(args),
|
||||
Cmd::Address(args) => address::exec(args),
|
||||
Cmd::Check(args) => check::exec(args),
|
||||
Cmd::Watch(args) => watch::exec(args),
|
||||
Cmd::Docs(args) => docs::exec(args),
|
||||
Cmd::Add(args) => add::exec(args),
|
||||
Cmd::Blueprint(args) => blueprint::exec(args),
|
||||
|
|
Loading…
Reference in New Issue