Skip to main content

ctl_core/
run.rs

1//! Process exit wrapper.
2
3use std::process::ExitCode;
4
5use anyhow::Result;
6#[cfg(feature = "help")]
7use clap::CommandFactory;
8
9use crate::color::ColorMode;
10use crate::format::OutputFormat;
11
12/// Parse `C`, emit styled help if asked, then run `body`.
13#[cfg(feature = "help")]
14#[must_use]
15pub fn go<C: clap::Parser + CommandFactory>(
16    bin: &str,
17    body: impl FnOnce(C) -> Result<()>,
18) -> ExitCode {
19    main_with_help::<C>(bin, || {
20        let raw: Vec<std::ffi::OsString> = std::env::args_os().collect();
21        let words: Vec<String> = raw
22            .iter()
23            .skip(1)
24            .map(|arg| arg.to_string_lossy().into_owned())
25            .collect();
26        let warnings = crate::flags::chassis_warnings(words.iter().map(String::as_str));
27        crate::flags::emit_warnings(bin, &warnings);
28        let matches = legacy_command::<C>().get_matches_from(&raw);
29        let cli = C::from_arg_matches(&matches).unwrap_or_else(|err| err.exit());
30        body(cli)
31    })
32}
33
34#[cfg(feature = "help")]
35fn legacy_command<C: CommandFactory>() -> clap::Command {
36    crate::parser::apply_defaults(C::command()).arg_required_else_help(true)
37}
38
39/// Parse-free entry: print `{bin}: {error:#}` and return 1.
40#[must_use]
41pub fn main(bin: &str, body: impl FnOnce() -> Result<()>) -> ExitCode {
42    main_with(bin, OutputFormat::Pretty, ColorMode::Auto, body)
43}
44
45/// Same as [`main`] with an explicit format and color for the error path.
46#[must_use]
47pub fn main_with(
48    bin: &str,
49    format: OutputFormat,
50    color: ColorMode,
51    body: impl FnOnce() -> Result<()>,
52) -> ExitCode {
53    match body() {
54        Ok(()) => ExitCode::SUCCESS,
55        Err(error) => {
56            #[cfg(feature = "view")]
57            {
58                let _ = crate::view::View::new(format, color).emit_err(bin, &format!("{error:#}"));
59            }
60            #[cfg(not(feature = "view"))]
61            {
62                let _ = (format, color);
63                eprintln!("{bin}: {error:#}");
64            }
65            ExitCode::FAILURE
66        }
67    }
68}
69
70/// Emit styled help when `-h`/`--help` is present, then run `body`.
71#[cfg(feature = "help")]
72#[must_use]
73pub fn main_with_help<C: CommandFactory>(bin: &str, body: impl FnOnce() -> Result<()>) -> ExitCode {
74    let raw = std::env::args_os().collect::<Vec<_>>();
75    if raw.len() == 1 && crate::parser::requires_input::<C>() {
76        return crate::help::emit_bare::<C>(ColorMode::Auto)
77            .map_or(ExitCode::FAILURE, |()| ExitCode::from(2));
78    }
79    match crate::help::try_emit::<C>() {
80        Ok(true) => return ExitCode::SUCCESS,
81        Ok(false) => {}
82        Err(_) => return ExitCode::FAILURE,
83    }
84    main(bin, body)
85}
86
87#[cfg(all(test, feature = "help"))]
88mod tests {
89    use clap::Parser;
90
91    use super::legacy_command;
92
93    #[derive(Parser)]
94    #[command(version)]
95    struct OptionalCli {}
96
97    #[test]
98    fn legacy_go_keeps_bare_help_required() {
99        assert!(legacy_command::<OptionalCli>().is_arg_required_else_help_set());
100    }
101}