Skip to main content

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