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