pub mod discovery;
pub mod plumber;
pub mod ui;
#[cfg(test)]
mod test_support;
use color_eyre::eyre::{Result, WrapErr};
use plumber::{Matcher, RuleEngine};
use ui::App;
use ui::cli::CliCommand;
pub fn run() -> Result<()> {
color_eyre::install()?;
let cli = ui::cli::parse();
let (matcher, config_warnings) = ui::config::load_matcher(&cli)?;
if cli.command == CliCommand::ListCommands {
print_commands(&matcher);
return Ok(());
}
let keybindings = ui::config::load_keybindings()?;
let mut discovery = discovery::start(&cli.discovery_config());
let discovery_rx = discovery.events();
let mut app = App::new(cli, matcher, keybindings, discovery_rx)
.with_discovery(discovery, Box::new(discovery::start))
.with_config_loader(Box::new(|cli| {
ui::config::load_matcher(cli)
.map(|(matcher, warnings)| (Box::new(matcher) as Box<dyn RuleEngine>, warnings))
}));
#[cfg(unix)]
sighup::install(app.reload_requested.clone());
if !config_warnings.is_empty() {
app.status = format!(
"skipped {} command config file(s); details printed on exit",
config_warnings.len()
);
}
let exec_action = ratatui::run(|terminal| app.run(terminal))?;
drop(app);
for warning in &config_warnings {
eprintln!("warning: {warning}");
}
if let Some(action) = exec_action {
let command_line = action.argv.join(" ");
plumber::exec::exec(action).wrap_err_with(|| format!("failed to run `{command_line}`"))?;
}
Ok(())
}
#[cfg(unix)]
mod sighup {
use std::sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
};
static RELOAD_REQUESTED: OnceLock<Arc<AtomicBool>> = OnceLock::new();
extern "C" fn on_sighup(_signal: libc::c_int) {
if let Some(flag) = RELOAD_REQUESTED.get() {
flag.store(true, Ordering::Relaxed);
}
}
pub(crate) fn install(flag: Arc<AtomicBool>) {
let _ = RELOAD_REQUESTED.set(flag);
unsafe {
libc::signal(
libc::SIGHUP,
on_sighup as extern "C" fn(libc::c_int) as libc::sighandler_t,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sighup_sets_the_reload_flag_instead_of_terminating() {
let flag = Arc::new(AtomicBool::new(false));
install(flag.clone());
unsafe { libc::raise(libc::SIGHUP) };
assert!(flag.load(Ordering::Relaxed));
}
}
}
fn print_commands(matcher: &Matcher) {
println!("{:<22} {:<8} {:<36} COMMAND", "NAME", "MODE", "DESCRIPTION");
for command in matcher.commands() {
println!(
"{:<22} {:<8} {:<36} {}",
command.name,
command.action.mode,
command.description.as_deref().unwrap_or(""),
command.action.command
);
}
}