use clap::Parser;
use clap::ValueEnum;
use notify::RecursiveMode;
use notify::Watcher;
use notify_debouncer_full::new_debouncer;
use std::fmt::Display;
use std::process::Command;
use std::process::Stdio;
use std::time::Duration;
use wax::Glob;
#[derive(ValueEnum, Debug, Clone, Copy)]
pub enum Shell {
Bash,
Zsh,
Sh,
Fish,
Cmd,
Powershell,
Pwsh,
}
impl<'a> From<&'a str> for Shell {
fn from(value: &'a str) -> Self {
match value {
"bash" => Shell::Bash,
"zsh" => Shell::Zsh,
"sh" => Shell::Sh,
"fish" => Shell::Fish,
"cmd" => Shell::Cmd,
"powershell" => Shell::Powershell,
"pwsh" => Shell::Pwsh,
_ => Shell::Bash,
}
}
}
impl Display for Shell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Shell::Bash => "bash",
Shell::Zsh => "zsh",
Shell::Sh => "sh",
Shell::Fish => "fish",
Shell::Cmd => "cmd",
Shell::Powershell => "powershell",
Shell::Pwsh => "pwsh",
})
}
}
fn green(s: &str) {
println!("\x1b[32m{}\x1b[0m", s);
}
#[derive(Parser, Debug, Clone)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long, default_value_t = 400)]
debounce: usize,
#[arg(long, default_value_t = false)]
initial: bool,
#[arg(long)]
cwd: Option<String>,
#[arg(long, default_value_t = Shell::Bash)]
shell: Shell,
#[arg(short, long)]
cmd: String,
#[clap()]
pattern: String,
}
fn exec(shell: Shell, cmd: &str, cwd: String) {
green(&format!("[Running({}): {}]", shell, cmd));
let op = match shell {
Shell::Cmd => "/c",
_ => "-c",
};
let mut c = Command::new(shell.to_string());
c.args([op, cmd])
.current_dir(&cwd)
.stdout(Stdio::inherit())
.stdin(Stdio::inherit())
.stderr(Stdio::inherit());
c.output().expect("command exec error");
green("[Command was successful]");
}
fn main() {
let args = Args::parse();
let (tx, rx) = std::sync::mpsc::channel();
let mut debouncer = new_debouncer(
Duration::from_millis(args.debounce.try_into().unwrap()),
None,
tx,
)
.expect("debouncer create error");
let watcher = debouncer.watcher();
let pattern = &args.pattern;
let cwd = args.cwd.unwrap_or(
std::env::current_dir()
.expect("can't get current_dir")
.to_string_lossy()
.to_string(),
);
let glob = Glob::new(pattern).unwrap();
for entry in glob.walk(cwd.clone()).filter_map(|i| i.ok()) {
let path = entry.path();
watcher
.watch(path, RecursiveMode::Recursive)
.unwrap_or_else(|_| panic!("watch file error: {:?} ", path));
}
let shell = args.shell;
if args.initial {
green("[initial run]");
exec(shell, &args.cmd, cwd.clone());
}
green(&format!("[watching: {}]", pattern));
for result in rx {
match result {
Ok(_) => {
exec(shell, &args.cmd, cwd.clone());
}
Err(error) => println!("Error {error:?}"),
}
}
}