1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#![deny(rust_2018_idioms)]
#![allow(clippy::missing_const_for_fn, clippy::future_not_send)]

use std::{io::Write, process::Stdio};

use args::{Args, ShellCompletion};
use clap::CommandFactory;
use clap_complete::{Generator, Shell};
use clap_mangen::Man;
use is_terminal::IsTerminal;
use miette::{IntoDiagnostic, Result};
use tokio::{io::AsyncWriteExt, process::Command};
use tracing::{debug, info};
use watchexec::Watchexec;
use watchexec_events::{Event, Priority};

use crate::filterer::WatchexecFilterer;

pub mod args;
mod config;
mod dirs;
mod emits;
mod filterer;
mod state;

async fn run_watchexec(args: Args) -> Result<()> {
	info!(version=%env!("CARGO_PKG_VERSION"), "constructing Watchexec from CLI");

	let state = state::State::default();
	let config = config::make_config(&args, &state)?;
	config.filterer(WatchexecFilterer::new(&args).await?);

	info!("initialising Watchexec runtime");
	let wx = Watchexec::with_config(config)?;

	if !args.postpone {
		debug!("kicking off with empty event");
		wx.send_event(Event::default(), Priority::Urgent).await?;
	}

	info!("running main loop");
	wx.main().await.into_diagnostic()??;

	if matches!(args.screen_clear, Some(args::ClearMode::Reset)) {
		config::reset_screen();
	}

	info!("done with main loop");

	Ok(())
}

async fn run_manpage(_args: Args) -> Result<()> {
	info!(version=%env!("CARGO_PKG_VERSION"), "constructing manpage");

	let man = Man::new(Args::command().long_version(None));
	let mut buffer: Vec<u8> = Default::default();
	man.render(&mut buffer).into_diagnostic()?;

	if std::io::stdout().is_terminal() && which::which("man").is_ok() {
		let mut child = Command::new("man")
			.arg("-l")
			.arg("-")
			.stdin(Stdio::piped())
			.stdout(Stdio::inherit())
			.stderr(Stdio::inherit())
			.kill_on_drop(true)
			.spawn()
			.into_diagnostic()?;
		child
			.stdin
			.as_mut()
			.unwrap()
			.write_all(&buffer)
			.await
			.into_diagnostic()?;

		if let Some(code) = child
			.wait()
			.await
			.into_diagnostic()?
			.code()
			.and_then(|code| if code == 0 { None } else { Some(code) })
		{
			return Err(miette::miette!("Exited with status code {}", code));
		}
	} else {
		std::io::stdout()
			.lock()
			.write_all(&buffer)
			.into_diagnostic()?;
	}

	Ok(())
}

#[allow(clippy::unused_async)]
async fn run_completions(shell: ShellCompletion) -> Result<()> {
	fn generate(generator: impl Generator) {
		let mut cmd = Args::command();
		clap_complete::generate(generator, &mut cmd, "watchexec", &mut std::io::stdout());
	}

	info!(version=%env!("CARGO_PKG_VERSION"), "constructing completions");

	match shell {
		ShellCompletion::Bash => generate(Shell::Bash),
		ShellCompletion::Elvish => generate(Shell::Elvish),
		ShellCompletion::Fish => generate(Shell::Fish),
		ShellCompletion::Nu => generate(clap_complete_nushell::Nushell),
		ShellCompletion::Powershell => generate(Shell::PowerShell),
		ShellCompletion::Zsh => generate(Shell::Zsh),
	}

	Ok(())
}

pub async fn run() -> Result<()> {
	let (args, _log_guard) = args::get_args().await?;

	if args.manual {
		run_manpage(args).await
	} else if let Some(shell) = args.completions {
		run_completions(shell).await
	} else {
		run_watchexec(args).await
	}
}