2022-06-28 14:21:19 +02:00
|
|
|
#![deny(rust_2018_idioms)]
|
2023-01-06 14:53:49 +01:00
|
|
|
#![allow(clippy::missing_const_for_fn, clippy::future_not_send)]
|
2022-06-28 14:21:19 +02:00
|
|
|
|
2023-03-05 02:57:34 +01:00
|
|
|
use std::{env::var, fs::File, io::Write, process::Stdio, sync::Mutex};
|
|
|
|
|
|
|
|
use args::{Args, ShellCompletion};
|
|
|
|
use clap::CommandFactory;
|
|
|
|
use clap_complete::{Generator, Shell};
|
|
|
|
use clap_mangen::Man;
|
|
|
|
use is_terminal::IsTerminal;
|
2023-06-04 10:58:58 +02:00
|
|
|
use miette::{IntoDiagnostic, Result};
|
2023-03-05 02:57:34 +01:00
|
|
|
use tokio::{fs::metadata, io::AsyncWriteExt, process::Command};
|
2022-09-02 11:12:47 +02:00
|
|
|
use tracing::{debug, info, warn};
|
2023-11-25 21:33:44 +01:00
|
|
|
use watchexec::Watchexec;
|
|
|
|
use watchexec_events::{Event, Priority};
|
2022-06-28 14:21:19 +02:00
|
|
|
|
2023-03-05 02:57:34 +01:00
|
|
|
pub mod args;
|
2022-06-28 14:21:19 +02:00
|
|
|
mod config;
|
2023-03-18 09:32:24 +01:00
|
|
|
mod emits;
|
2022-06-28 14:21:19 +02:00
|
|
|
mod filterer;
|
2023-03-18 09:32:24 +01:00
|
|
|
mod state;
|
2022-06-28 14:21:19 +02:00
|
|
|
|
2023-03-05 02:57:34 +01:00
|
|
|
async fn init() -> Result<Args> {
|
2022-06-28 14:21:19 +02:00
|
|
|
let mut log_on = false;
|
|
|
|
|
|
|
|
#[cfg(feature = "dev-console")]
|
|
|
|
match console_subscriber::try_init() {
|
|
|
|
Ok(_) => {
|
|
|
|
warn!("dev-console enabled");
|
|
|
|
log_on = true;
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("Failed to initialise tokio console, falling back to normal logging\n{e}")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !log_on && var("RUST_LOG").is_ok() {
|
|
|
|
match tracing_subscriber::fmt::try_init() {
|
|
|
|
Ok(_) => {
|
|
|
|
warn!(RUST_LOG=%var("RUST_LOG").unwrap(), "logging configured from RUST_LOG");
|
|
|
|
log_on = true;
|
|
|
|
}
|
|
|
|
Err(e) => eprintln!("Failed to initialise logging with RUST_LOG, falling back\n{e}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-05 02:57:34 +01:00
|
|
|
let args = args::get_args();
|
|
|
|
let verbosity = args.verbose.unwrap_or(0);
|
2022-06-28 14:21:19 +02:00
|
|
|
|
|
|
|
if log_on {
|
|
|
|
warn!("ignoring logging options from args");
|
2022-06-28 15:40:20 +02:00
|
|
|
} else if verbosity > 0 {
|
2023-03-05 02:57:34 +01:00
|
|
|
let log_file = if let Some(file) = &args.log_file {
|
2023-06-04 10:58:58 +02:00
|
|
|
let is_dir = metadata(&file).await.map_or(false, |info| info.is_dir());
|
|
|
|
let path = if is_dir {
|
2023-03-05 02:57:34 +01:00
|
|
|
let filename = format!(
|
|
|
|
"watchexec.{}.log",
|
|
|
|
chrono::Utc::now().format("%Y-%m-%dT%H-%M-%SZ")
|
|
|
|
);
|
|
|
|
file.join(filename)
|
|
|
|
} else {
|
|
|
|
file.to_owned()
|
|
|
|
};
|
|
|
|
|
2022-06-28 14:21:19 +02:00
|
|
|
// TODO: use tracing-appender instead
|
2023-03-05 02:57:34 +01:00
|
|
|
Some(File::create(path).into_diagnostic()?)
|
2022-06-28 14:21:19 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut builder = tracing_subscriber::fmt().with_env_filter(match verbosity {
|
2022-06-28 15:40:20 +02:00
|
|
|
0 => unreachable!("checked by if earlier"),
|
|
|
|
1 => "warn",
|
|
|
|
2 => "info",
|
|
|
|
3 => "debug",
|
2022-06-28 14:21:19 +02:00
|
|
|
_ => "trace",
|
|
|
|
});
|
|
|
|
|
|
|
|
if verbosity > 2 {
|
|
|
|
use tracing_subscriber::fmt::format::FmtSpan;
|
|
|
|
builder = builder.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
match if let Some(writer) = log_file {
|
|
|
|
builder.json().with_writer(Mutex::new(writer)).try_init()
|
|
|
|
} else if verbosity > 3 {
|
|
|
|
builder.pretty().try_init()
|
|
|
|
} else {
|
|
|
|
builder.try_init()
|
|
|
|
} {
|
|
|
|
Ok(_) => info!("logging initialised"),
|
|
|
|
Err(e) => eprintln!("Failed to initialise logging, continuing with none\n{e}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-05 02:57:34 +01:00
|
|
|
Ok(args)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn run_watchexec(args: Args) -> Result<()> {
|
2022-06-28 15:40:20 +02:00
|
|
|
info!(version=%env!("CARGO_PKG_VERSION"), "constructing Watchexec from CLI");
|
2022-06-28 14:21:19 +02:00
|
|
|
|
2023-03-18 09:32:24 +01:00
|
|
|
let state = state::State::new()?;
|
2023-11-25 21:33:44 +01:00
|
|
|
let config = config::make_config(&args, &state)?;
|
|
|
|
config.filterer(filterer::globset(&args).await?);
|
2022-06-28 14:21:19 +02:00
|
|
|
|
2022-06-28 15:40:20 +02:00
|
|
|
info!("initialising Watchexec runtime");
|
2023-11-25 21:33:44 +01:00
|
|
|
let wx = Watchexec::with_config(config)?;
|
2022-06-28 14:21:19 +02:00
|
|
|
|
2023-03-05 02:57:34 +01:00
|
|
|
if !args.postpone {
|
2022-06-28 15:40:20 +02:00
|
|
|
debug!("kicking off with empty event");
|
2022-06-28 14:21:19 +02:00
|
|
|
wx.send_event(Event::default(), Priority::Urgent).await?;
|
|
|
|
}
|
|
|
|
|
2022-06-28 15:40:20 +02:00
|
|
|
info!("running main loop");
|
2022-06-28 14:21:19 +02:00
|
|
|
wx.main().await.into_diagnostic()??;
|
2022-06-28 15:40:20 +02:00
|
|
|
info!("done with main loop");
|
2022-06-28 14:21:19 +02:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-03-05 02:57:34 +01:00
|
|
|
|
|
|
|
async fn run_manpage(_args: Args) -> Result<()> {
|
|
|
|
info!(version=%env!("CARGO_PKG_VERSION"), "constructing manpage");
|
|
|
|
|
|
|
|
let man = Man::new(Args::command().long_version(None));
|
|
|
|
let mut buffer: Vec<u8> = Default::default();
|
|
|
|
man.render(&mut buffer).into_diagnostic()?;
|
|
|
|
|
|
|
|
if std::io::stdout().is_terminal() && which::which("man").is_ok() {
|
|
|
|
let mut child = Command::new("man")
|
|
|
|
.arg("-l")
|
|
|
|
.arg("-")
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
.stdout(Stdio::inherit())
|
|
|
|
.stderr(Stdio::inherit())
|
|
|
|
.kill_on_drop(true)
|
|
|
|
.spawn()
|
|
|
|
.into_diagnostic()?;
|
|
|
|
child
|
|
|
|
.stdin
|
|
|
|
.as_mut()
|
|
|
|
.unwrap()
|
|
|
|
.write_all(&buffer)
|
|
|
|
.await
|
|
|
|
.into_diagnostic()?;
|
|
|
|
|
|
|
|
if let Some(code) = child
|
|
|
|
.wait()
|
|
|
|
.await
|
|
|
|
.into_diagnostic()?
|
|
|
|
.code()
|
|
|
|
.and_then(|code| if code == 0 { None } else { Some(code) })
|
|
|
|
{
|
|
|
|
return Err(miette::miette!("Exited with status code {}", code));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
std::io::stdout()
|
|
|
|
.lock()
|
|
|
|
.write_all(&buffer)
|
|
|
|
.into_diagnostic()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn run_completions(shell: ShellCompletion) -> Result<()> {
|
|
|
|
info!(version=%env!("CARGO_PKG_VERSION"), "constructing completions");
|
|
|
|
|
|
|
|
fn generate(generator: impl Generator) {
|
|
|
|
let mut cmd = Args::command();
|
|
|
|
clap_complete::generate(generator, &mut cmd, "watchexec", &mut std::io::stdout());
|
|
|
|
}
|
|
|
|
|
|
|
|
match shell {
|
|
|
|
ShellCompletion::Bash => generate(Shell::Bash),
|
|
|
|
ShellCompletion::Elvish => generate(Shell::Elvish),
|
|
|
|
ShellCompletion::Fish => generate(Shell::Fish),
|
|
|
|
ShellCompletion::Nu => generate(clap_complete_nushell::Nushell),
|
|
|
|
ShellCompletion::Powershell => generate(Shell::PowerShell),
|
|
|
|
ShellCompletion::Zsh => generate(Shell::Zsh),
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn run() -> Result<()> {
|
|
|
|
let args = init().await?;
|
|
|
|
debug!(?args, "arguments");
|
|
|
|
|
2023-03-18 10:34:33 +01:00
|
|
|
if args.manual {
|
2023-03-05 02:57:34 +01:00
|
|
|
run_manpage(args).await
|
|
|
|
} else if let Some(shell) = args.completions {
|
|
|
|
run_completions(shell).await
|
|
|
|
} else {
|
|
|
|
run_watchexec(args).await
|
|
|
|
}
|
|
|
|
}
|