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}