mod action;
mod config;
mod engine;
mod keys;
mod platform;
#[cfg(feature = "self-update")]
mod updater;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use config::Config;
use engine::Engine;
use std::path::PathBuf;
#[derive(Parser)]
#[command(
name = "kagi",
version,
about = "Cross-platform key mapper with first-class IME control"
)]
struct Cli {
#[arg(short, long, global = true)]
config: Option<PathBuf>,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
Run,
Check,
Watch,
#[cfg(feature = "self-update")]
Update {
#[arg(long)]
check: bool,
#[arg(short, long)]
yes: bool,
#[arg(long)]
non_interactive: bool,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
#[cfg(feature = "self-update")]
if let Some(Command::Update {
check,
yes,
non_interactive,
}) = cli.command
{
return updater::run_self_update(yes, check, non_interactive);
}
let path = match cli.config {
Some(p) => p,
None => config::default_path()?,
};
let cfg = Config::load(&path)?;
let rules = cfg
.rules_for(std::env::consts::OS)
.with_context(|| format!("compiling {}", path.display()))?;
match cli.command.unwrap_or(Command::Run) {
Command::Check => {
println!(
"{}: {} rule(s) for {}",
path.display(),
rules.len(),
std::env::consts::OS
);
for rule in &rules {
let flags = match (rule.passthrough, rule.wildcard_mods) {
(true, true) => "~*",
(true, false) => "~",
(false, true) => "*",
(false, false) => "",
};
let actions = if rule.actions.is_empty() {
"<swallow>".to_string()
} else {
rule.actions
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(" ")
};
print!(" {flags}{} -> {actions}", rule.trigger);
match &rule.description {
Some(d) => println!(" # {d}"),
None => println!(),
}
}
Ok(())
}
Command::Watch => platform::watch(&cfg),
Command::Run => {
if rules.is_empty() {
eprintln!("kagi: no rules for {}; nothing to do", std::env::consts::OS);
}
#[cfg(feature = "self-update")]
updater::spawn_auto_update();
platform::run(Engine::new(rules), &cfg)
}
#[cfg(feature = "self-update")]
Command::Update { .. } => unreachable!("dispatched above"),
}
}