watchexec_cli/
lib.rs

1#![deny(rust_2018_idioms)]
2#![allow(clippy::missing_const_for_fn, clippy::future_not_send)]
3
4use std::{
5	io::Write,
6	process::{ExitCode, Stdio},
7};
8
9use clap::CommandFactory;
10use clap_complete::{Generator, Shell};
11use clap_mangen::Man;
12use is_terminal::IsTerminal;
13use miette::{IntoDiagnostic, Result};
14use tokio::{io::AsyncWriteExt, process::Command};
15use tracing::{debug, info};
16use watchexec::Watchexec;
17use watchexec_events::{Event, Priority};
18
19use crate::{
20	args::{Args, ShellCompletion},
21	filterer::WatchexecFilterer,
22};
23
24pub mod args;
25mod config;
26mod dirs;
27mod emits;
28mod filterer;
29mod socket;
30mod state;
31
32async fn run_watchexec(args: Args, state: state::State) -> Result<()> {
33	info!(version=%env!("CARGO_PKG_VERSION"), "constructing Watchexec from CLI");
34
35	let config = config::make_config(&args, &state)?;
36	config.filterer(WatchexecFilterer::new(&args).await?);
37
38	info!("initialising Watchexec runtime");
39	let wx = Watchexec::with_config(config)?;
40
41	if !args.events.postpone {
42		debug!("kicking off with empty event");
43		wx.send_event(Event::default(), Priority::Urgent).await?;
44	}
45
46	info!("running main loop");
47	wx.main().await.into_diagnostic()??;
48
49	if matches!(
50		args.output.screen_clear,
51		Some(args::output::ClearMode::Reset)
52	) {
53		config::reset_screen();
54	}
55
56	info!("done with main loop");
57
58	Ok(())
59}
60
61async fn run_manpage() -> Result<()> {
62	info!(version=%env!("CARGO_PKG_VERSION"), "constructing manpage");
63
64	let man = Man::new(Args::command().long_version(None));
65	let mut buffer: Vec<u8> = Default::default();
66	man.render(&mut buffer).into_diagnostic()?;
67
68	if std::io::stdout().is_terminal() && which::which("man").is_ok() {
69		let mut child = Command::new("man")
70			.arg("-l")
71			.arg("-")
72			.stdin(Stdio::piped())
73			.stdout(Stdio::inherit())
74			.stderr(Stdio::inherit())
75			.kill_on_drop(true)
76			.spawn()
77			.into_diagnostic()?;
78		child
79			.stdin
80			.as_mut()
81			.unwrap()
82			.write_all(&buffer)
83			.await
84			.into_diagnostic()?;
85
86		if let Some(code) = child
87			.wait()
88			.await
89			.into_diagnostic()?
90			.code()
91			.and_then(|code| if code == 0 { None } else { Some(code) })
92		{
93			return Err(miette::miette!("Exited with status code {}", code));
94		}
95	} else {
96		std::io::stdout()
97			.lock()
98			.write_all(&buffer)
99			.into_diagnostic()?;
100	}
101
102	Ok(())
103}
104
105#[allow(clippy::unused_async)]
106async fn run_completions(shell: ShellCompletion) -> Result<()> {
107	fn generate(generator: impl Generator) {
108		let mut cmd = Args::command();
109		clap_complete::generate(generator, &mut cmd, "watchexec", &mut std::io::stdout());
110	}
111
112	info!(version=%env!("CARGO_PKG_VERSION"), "constructing completions");
113
114	match shell {
115		ShellCompletion::Bash => generate(Shell::Bash),
116		ShellCompletion::Elvish => generate(Shell::Elvish),
117		ShellCompletion::Fish => generate(Shell::Fish),
118		ShellCompletion::Nu => generate(clap_complete_nushell::Nushell),
119		ShellCompletion::Powershell => generate(Shell::PowerShell),
120		ShellCompletion::Zsh => generate(Shell::Zsh),
121	}
122
123	Ok(())
124}
125
126pub async fn run() -> Result<ExitCode> {
127	let (args, _guards) = args::get_args().await?;
128
129	Ok(if args.manual {
130		run_manpage().await?;
131		ExitCode::SUCCESS
132	} else if let Some(shell) = args.completions {
133		run_completions(shell).await?;
134		ExitCode::SUCCESS
135	} else {
136		let state = state::new(&args).await?;
137		run_watchexec(args, state.clone()).await?;
138		let exit = *(state.exit_code.lock().unwrap());
139		exit
140	})
141}