mod config;
mod cost;
mod deps;
mod events;
mod executor;
mod gate;
mod help;
mod mailbox;
mod paths;
mod run;
mod seed;
mod sensor;
mod service;
mod session;
mod tick;
mod util;
mod worldhash;
use anyhow::Result;
use paths::Paths;
use std::process::ExitCode;
fn main() -> ExitCode {
restore_sigpipe();
let paths = Paths::resolve();
export_env(&paths);
util::init_format();
util::init_color();
let args: Vec<String> = std::env::args().skip(1).collect();
let Some(cmd) = args.first().map(String::as_str) else {
help::print(&paths);
return ExitCode::SUCCESS;
};
let rest = &args[1..];
let result: Result<ExitCode> = match cmd {
"help" | "-h" | "--help" => {
help::print(&paths);
Ok(ExitCode::SUCCESS)
}
"version" | "--version" | "-V" => {
println!("looop {}", env!("CARGO_PKG_VERSION"));
Ok(ExitCode::SUCCESS)
}
"run" if rest.first().map(String::as_str) == Some("--detached-id") => {
session::run_detached_worker(rest).map(|c| ExitCode::from(c.clamp(0, 255) as u8))
}
"up" => deps::require_deps(&paths).and_then(|_| service::cmd_up(&paths, rest)),
"down" => deps::require_deps(&paths).and_then(|_| service::cmd_down(&paths)),
"_" => {
match rest.first().map(String::as_str) {
Some("pulse") => {
deps::require_deps(&paths).and_then(|_| service::cmd_pulse(&paths))
}
Some("state") => {
deps::require_deps(&paths).and_then(|_| tick::cmd_state(&paths, &rest[1..]))
}
Some("wait") => {
deps::require_deps(&paths).and_then(|_| tick::cmd_wait(&paths, &rest[1..]))
}
Some("answer") => {
deps::require_deps(&paths).and_then(|_| mailbox::cmd_answer(&paths, &rest[1..]))
}
Some("goal") => {
deps::require_deps(&paths).and_then(|_| executor::cmd_goal(&paths, &rest[1..]))
}
Some("sensor") => deps::require_deps(&paths)
.and_then(|_| executor::cmd_sensor(&paths, &rest[1..])),
Some("playbook") => deps::require_deps(&paths)
.and_then(|_| executor::cmd_playbook(&paths, &rest[1..])),
Some("run") => {
deps::require_deps(&paths).and_then(|_| executor::cmd_run(&paths, &rest[1..]))
}
Some("notify") => deps::require_deps(&paths)
.and_then(|_| executor::cmd_notify(&paths, &rest[1..])),
Some("worker") => match rest.get(1).map(String::as_str) {
Some("start") => deps::require_deps(&paths)
.and_then(|_| executor::cmd_worker_start(&paths, &rest[2..])),
Some("kill") => deps::require_deps(&paths)
.and_then(|_| session::cmd_kill(&paths, &rest[2..])),
other => {
eprintln!("looop _ worker: unknown subverb {other:?} (start, kill)");
Ok(ExitCode::from(1))
}
},
Some("ask") => {
deps::require_deps(&paths).and_then(|_| mailbox::cmd_ask(&paths, &rest[1..]))
}
Some("kill") => {
deps::require_deps(&paths).and_then(|_| session::cmd_kill(&paths, &rest[1..]))
}
Some("claim") => {
deps::require_deps(&paths).and_then(|_| gate::cmd_claim(&paths, &rest[1..]))
}
Some("unclaim") => {
deps::require_deps(&paths).and_then(|_| gate::cmd_unclaim(&paths, &rest[1..]))
}
Some("cost") => cost::cmd_cost_record(&paths, &rest[1..]),
other => {
eprintln!(
"looop _: unknown internal verb {other:?} (root: state, wait, answer, goal, sensor, playbook, run, notify, worker; worker: ask, kill, claim, unclaim, cost; pulse)"
);
Ok(ExitCode::from(1))
}
}
}
"config" => match rest.first().map(String::as_str) {
Some("zsh") => {
print!("{}", include_str!("completions/looop.zsh"));
Ok(ExitCode::SUCCESS)
}
Some("bash") => {
print!("{}", include_str!("completions/looop.bash"));
Ok(ExitCode::SUCCESS)
}
_ => {
eprintln!("looop config: specify a shell — zsh or bash");
eprintln!(" zsh: eval \"$(looop config zsh)\"");
eprintln!(" bash: eval \"$(looop config bash)\"");
Ok(ExitCode::from(1))
}
},
"cost" => cost::cmd_cost(&paths, rest),
other => {
eprintln!("looop: unknown command '{other}' (up, down, cost, config, version, help)");
Ok(ExitCode::from(1))
}
};
match result {
Ok(code) => code,
Err(e) => {
eprintln!("{e}");
ExitCode::from(1)
}
}
}
#[cfg(unix)]
fn restore_sigpipe() {
const SIGPIPE: i32 = 13;
const SIG_DFL: usize = 0;
unsafe extern "C" {
fn signal(signum: i32, handler: usize) -> usize;
}
unsafe {
signal(SIGPIPE, SIG_DFL);
}
}
#[cfg(not(unix))]
fn restore_sigpipe() {}
fn export_env(paths: &Paths) {
let set = |k: &str, v: &std::ffi::OsStr| unsafe { std::env::set_var(k, v) };
set("LOOOP_BIN", paths.bin.as_os_str());
set("LOOOP_DATA_DIR", paths.data_dir.as_os_str());
set("LOOOP_CONFIG", paths.config.as_os_str());
set("LOOOP_CLAIMS_DIR", paths.claims_dir().as_os_str());
set("LOOOP_REPORTS_DIR", paths.reports_dir().as_os_str());
set("LOOOP_COST_LEDGER", paths.cost_ledger().as_os_str());
}
#[cfg(test)]
mod tests {
#[test]
fn zsh_completion_registers_itself() {
let s = include_str!("completions/looop.zsh");
assert!(s.contains("#compdef looop"), "missing #compdef tag");
assert!(s.contains("compdef _looop looop"), "missing compdef call");
}
#[test]
fn bash_completion_registers_itself() {
let s = include_str!("completions/looop.bash");
assert!(
s.contains("complete -F _looop looop"),
"missing complete -F registration"
);
}
}