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 std::sync::Arc;
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 = Arc::new(Watchexec::with_config(config)?);
40
41	// Set the watchexec reference in state so it can be used for sending synthetic events
42	state
43		.watchexec
44		.set(wx.clone())
45		.expect("watchexec reference already set");
46
47	if !args.events.postpone {
48		debug!("kicking off with empty event");
49		wx.send_event(Event::default(), Priority::Urgent).await?;
50	}
51
52	if args.events.interactive {
53		eprintln!("[Interactive] q: quit, p: pause/unpause, r: restart");
54	}
55
56	info!("running main loop");
57	wx.main().await.into_diagnostic()??;
58
59	if matches!(
60		args.output.screen_clear,
61		Some(args::output::ClearMode::Reset)
62	) {
63		config::reset_screen();
64	}
65
66	info!("done with main loop");
67
68	Ok(())
69}
70
71async fn run_manpage() -> Result<()> {
72	info!(version=%env!("CARGO_PKG_VERSION"), "constructing manpage");
73
74	let man = Man::new(Args::command().long_version(None));
75	let mut buffer: Vec<u8> = Default::default();
76	man.render(&mut buffer).into_diagnostic()?;
77
78	if std::io::stdout().is_terminal() && which::which("man").is_ok() {
79		let mut child = Command::new("man")
80			.arg("-l")
81			.arg("-")
82			.stdin(Stdio::piped())
83			.stdout(Stdio::inherit())
84			.stderr(Stdio::inherit())
85			.kill_on_drop(true)
86			.spawn()
87			.into_diagnostic()?;
88		child
89			.stdin
90			.as_mut()
91			.unwrap()
92			.write_all(&buffer)
93			.await
94			.into_diagnostic()?;
95
96		if let Some(code) = child
97			.wait()
98			.await
99			.into_diagnostic()?
100			.code()
101			.and_then(|code| if code == 0 { None } else { Some(code) })
102		{
103			return Err(miette::miette!("Exited with status code {}", code));
104		}
105	} else {
106		std::io::stdout()
107			.lock()
108			.write_all(&buffer)
109			.into_diagnostic()?;
110	}
111
112	Ok(())
113}
114
115#[allow(clippy::unused_async)]
116async fn run_completions(shell: ShellCompletion) -> Result<()> {
117	fn generate(generator: impl Generator) {
118		let mut cmd = Args::command();
119		clap_complete::generate(generator, &mut cmd, "watchexec", &mut std::io::stdout());
120	}
121
122	info!(version=%env!("CARGO_PKG_VERSION"), "constructing completions");
123
124	match shell {
125		ShellCompletion::Bash => generate(Shell::Bash),
126		ShellCompletion::Elvish => generate(Shell::Elvish),
127		ShellCompletion::Fish => generate(Shell::Fish),
128		ShellCompletion::Nu => generate(clap_complete_nushell::Nushell),
129		ShellCompletion::Powershell => generate(Shell::PowerShell),
130		ShellCompletion::Zsh => generate(Shell::Zsh),
131	}
132
133	Ok(())
134}
135
136pub async fn run() -> Result<ExitCode> {
137	let (args, _guards) = args::get_args().await?;
138
139	Ok(if args.manual {
140		run_manpage().await?;
141		ExitCode::SUCCESS
142	} else if let Some(shell) = args.completions {
143		run_completions(shell).await?;
144		ExitCode::SUCCESS
145	} else {
146		let state = state::new(&args).await?;
147		run_watchexec(args, state.clone()).await?;
148		let exit = *(state.exit_code.lock().unwrap());
149		exit
150	})
151}