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}