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"
|
ignore = "0.4.20"
|
||||||
indoc = "2.0"
|
indoc = "2.0"
|
||||||
miette = { version = "5.5.0", features = ["fancy"] }
|
miette = { version = "5.5.0", features = ["fancy"] }
|
||||||
|
notify = "6.1.1"
|
||||||
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
||||||
pallas-addresses = "0.18.0"
|
pallas-addresses = "0.18.0"
|
||||||
pallas-codec = "0.18.0"
|
pallas-codec = "0.18.0"
|
||||||
|
|
|
@ -18,6 +18,10 @@ pub struct Args {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
debug: bool,
|
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.
|
/// 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}"`
|
||||||
|
@ -43,6 +47,7 @@ pub fn exec(
|
||||||
match_tests,
|
match_tests,
|
||||||
exact_match,
|
exact_match,
|
||||||
no_traces,
|
no_traces,
|
||||||
|
watch,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
crate::with_project(directory, deny, |p| {
|
crate::with_project(directory, deny, |p| {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use clap::Parser;
|
||||||
pub mod blueprint;
|
pub mod blueprint;
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod check;
|
pub mod check;
|
||||||
|
pub mod watch;
|
||||||
pub mod completion;
|
pub mod completion;
|
||||||
pub mod docs;
|
pub mod docs;
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
|
@ -23,6 +24,7 @@ pub enum Cmd {
|
||||||
Build(build::Args),
|
Build(build::Args),
|
||||||
Address(blueprint::address::Args),
|
Address(blueprint::address::Args),
|
||||||
Check(check::Args),
|
Check(check::Args),
|
||||||
|
Watch(watch::Args),
|
||||||
Docs(docs::Args),
|
Docs(docs::Args),
|
||||||
Add(packages::add::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(())
|
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)]
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
pub struct Terminal;
|
pub struct Terminal;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use aiken::cmd::{
|
use aiken::cmd::{
|
||||||
blueprint::{self, address},
|
blueprint::{self, address},
|
||||||
build, check, completion, docs, fmt, lsp, new,
|
build, check, watch, completion, docs, fmt, lsp, new,
|
||||||
packages::{self, add},
|
packages::{self, add},
|
||||||
tx, uplc, Cmd,
|
tx, uplc, Cmd,
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@ fn main() -> miette::Result<()> {
|
||||||
Cmd::Build(args) => build::exec(args),
|
Cmd::Build(args) => build::exec(args),
|
||||||
Cmd::Address(args) => address::exec(args),
|
Cmd::Address(args) => address::exec(args),
|
||||||
Cmd::Check(args) => check::exec(args),
|
Cmd::Check(args) => check::exec(args),
|
||||||
|
Cmd::Watch(args) => watch::exec(args),
|
||||||
Cmd::Docs(args) => docs::exec(args),
|
Cmd::Docs(args) => docs::exec(args),
|
||||||
Cmd::Add(args) => add::exec(args),
|
Cmd::Add(args) => add::exec(args),
|
||||||
Cmd::Blueprint(args) => blueprint::exec(args),
|
Cmd::Blueprint(args) => blueprint::exec(args),
|
||||||
|
|
Loading…
Reference in New Issue