Skip to main content

cabin/
lib.rs

1//! Library half of the `cabin` CLI binary.
2//!
3//! The bin (`src/main.rs`) is intentionally a thin shim that
4//! calls [`run`]; the typed parser ([`Cli`]), the
5//! command dispatcher, and every glue module live here so
6//! integration tests can re-use the same surface the binary
7//! does — `Cli::command()` is the single source of truth for
8//! which subcommands exist and which are hidden.
9
10#![allow(
11    clippy::struct_excessive_bools,
12    clippy::too_many_lines,
13    clippy::unused_self,
14    // Remaining hits are `root_settings: Default::default()` in test
15    // graph fixtures, where the typed-default suggestion is MaybeIncorrect.
16    clippy::default_trait_access
17)]
18
19use std::process::ExitCode;
20
21use clap::FromArgMatches;
22
23pub use crate::cli::Cli;
24use crate::term_setup::{EarlyTerminalState, resolve_early_terminal_state};
25
26// The clap parser stays reachable for this crate's glue modules,
27// but `Cli` is re-exported at the crate root for integration
28// tests and downstream command-tree generation.
29mod build_prep_glue;
30mod cli;
31mod command_list;
32mod completions;
33mod config_glue;
34mod diagnostic_registry;
35mod env_flags_glue;
36mod error_rendering;
37mod explain_glue;
38mod fetch_output_glue;
39mod fmt_glue;
40mod help_rendering;
41mod manpages;
42mod metadata_glue;
43mod ninja_glue;
44mod patch_glue;
45mod port_glue;
46mod port_subcommand;
47mod run_glue;
48mod source_tooling_glue;
49mod system_deps_glue;
50mod term_color_glue;
51mod term_setup;
52mod term_verbosity_glue;
53mod test_glue;
54mod tidy_glue;
55mod tree_glue;
56mod vendor_glue;
57mod version_glue;
58mod version_info;
59
60/// Return the English plural suffix for a count: empty for one,
61/// `s` for everything else. Used across the reporter glue files
62/// to keep `"<n> file"` / `"<n> files"` consistent.
63pub(crate) fn plural(n: usize) -> &'static str {
64    if n == 1 { "" } else { "s" }
65}
66
67/// Serialize `value` as pretty-printed JSON and write it to stdout
68/// followed by a newline. `error_context` is attached to any
69/// serialization failure, matching `anyhow::Context::context`.
70pub(crate) fn print_pretty_json<T>(value: &T, error_context: &'static str) -> anyhow::Result<()>
71where
72    T: serde::Serialize + ?Sized,
73{
74    use anyhow::Context;
75    let body = serde_json::to_string_pretty(value).context(error_context)?;
76    println!("{body}");
77    Ok(())
78}
79
80/// Run the `cabin` CLI to completion using the given argv
81/// iterator.  Owns parsing, color/verbosity resolution,
82/// dispatch, and top-level error rendering.  The binary
83/// `main` calls this with the process's own arguments.
84pub fn run<I, T>(args: I) -> ExitCode
85where
86    I: IntoIterator<Item = T>,
87    T: Into<std::ffi::OsString> + Clone,
88{
89    let cmd = help_rendering::prepare_top_level_command();
90    let matches = match cmd.try_get_matches_from(args) {
91        Ok(m) => m,
92        Err(err) => {
93            // `clap::Error` already routes `--help` /
94            // `--version` to stdout and real errors to stderr
95            // with the correct ANSI handling.  Just hand it
96            // through.
97            err.exit();
98        }
99    };
100    // `cabin ...` is a help-row affordance that doubles as a
101    // shortcut for `cabin --list`.  The unmapped subcommand
102    // produces `cmd: None` after `from_arg_matches`; promote
103    // it to `list = true` so the downstream dispatcher renders
104    // the listing with the same color-aware code path as the
105    // real flag.
106    let dots_shortcut = matches.subcommand_name() == Some(help_rendering::DOTS_HINT);
107    let mut parsed = match Cli::from_arg_matches(&matches) {
108        Ok(cli) => cli,
109        Err(err) => err.exit(),
110    };
111    if dots_shortcut {
112        parsed.list = true;
113    }
114
115    let EarlyTerminalState { color, reporter } =
116        match resolve_early_terminal_state(parsed.color, parsed.verbose, parsed.quiet) {
117            Ok(state) => state,
118            Err(exit_code) => return exit_code,
119        };
120
121    match cli::run(parsed, reporter, color) {
122        Ok(code) => code,
123        Err(error) => {
124            error_rendering::render_error(&error, color);
125            ExitCode::FAILURE
126        }
127    }
128}