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