Skip to main content

pigeon/commands/
mod.rs

1pub mod job;
2pub mod keyring;
3
4use crate::cli::Commands;
5
6/// Exit code returned by a command that ran but didn't succeed (e.g. a
7/// failed IMAP login, an unreadable config file). Distinct from clap's own
8/// usage-error exit code (2), so "input rejected" and "input accepted but
9/// the operation failed" stay distinguishable.
10pub const FAILURE_EXIT_CODE: i32 = 1;
11
12/// Prints `message` to stderr and returns `FAILURE_EXIT_CODE` -- the shared
13/// landing point every job wizard's `dispatch_async` uses to turn an `Err`
14/// into an exit code (ADR-0097). Previously each of the six job wizards
15/// defined this identically but without the `tracing::error!` call, so a
16/// handled job failure reached stderr but never the structured JSONL log
17/// (issue #18). `run_instrumented` still logs its own "command finished"
18/// line afterward with the exit code; this is the line that explains why.
19pub(crate) fn fail(message: impl std::fmt::Display) -> i32 {
20    tracing::error!(error = %message, "job failed");
21    eprintln!("Error: {message}");
22    FAILURE_EXIT_CODE
23}
24
25pub fn dispatch(command: Commands) -> i32 {
26    match command {
27        Commands::Keyring(args) => crate::commands::keyring::commands::dispatch(args.command),
28        Commands::Job(args) => crate::commands::job::commands::dispatch(args.command),
29    }
30}
31
32/// Prints `rows` as a left-aligned table with a header row, columns padded
33/// to their widest cell (except the last, which is never padded) and joined
34/// by two spaces -- shared by `keyring list`/`job` summaries. Hand-rolled, no
35/// table-formatting crate.
36pub fn print_table(headers: &[&str], rows: &[Vec<String>]) {
37    let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
38    for row in rows {
39        for (i, cell) in row.iter().enumerate() {
40            widths[i] = widths[i].max(cell.len());
41        }
42    }
43    let format_row = |cells: &[String]| -> String {
44        cells
45            .iter()
46            .enumerate()
47            .map(|(i, cell)| format!("{:width$}", cell, width = widths[i]))
48            .collect::<Vec<_>>()
49            .join("  ")
50            .trim_end()
51            .to_string()
52    };
53    let header_row: Vec<String> = headers.iter().map(|h| h.to_string()).collect();
54    println!("{}", format_row(&header_row));
55    for row in rows {
56        println!("{}", format_row(row));
57    }
58}