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 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}