gitignore_template_generator/parser/
impls.rs

1use std::{ffi::OsString, process::exit};
2
3use clap::{ArgMatches, Command};
4
5use crate::{ExitKind, ProgramExit, constant};
6
7use super::{Args, ArgsParser, command::build_clap_args};
8
9/// Default implementation of args parser that parses CLI args using
10/// [`clap`].
11pub struct ClapArgsParser {
12    cli_parser: Command,
13}
14
15#[allow(clippy::new_without_default)]
16impl ClapArgsParser {
17    pub fn new() -> Self {
18        Self {
19            cli_parser: Command::new(env!("CARGO_PKG_NAME"))
20                .version(env!("CARGO_PKG_VERSION"))
21                .author(env!("CARGO_PKG_AUTHORS"))
22                .about(constant::parser_infos::ABOUT)
23                .help_template(include_str!("../../assets/help_template.txt"))
24                .disable_help_flag(true)
25                .disable_version_flag(true)
26                .args(build_clap_args()),
27        }
28    }
29
30    fn parse_global_options(&self, args: &Args) -> Option<ProgramExit> {
31        if args.show_help {
32            let rendered_help = self.cli_parser.clone().render_help();
33            Some(ProgramExit {
34                message: rendered_help.to_string().trim_end().to_string(),
35                exit_status: constant::exit_status::SUCCESS,
36                styled_message: Some(
37                    rendered_help.ansi().to_string().trim_end().to_string(),
38                ),
39                kind: ExitKind::HelpInfos,
40            })
41        } else if args.show_version {
42            let message = match self.cli_parser.get_version() {
43                Some(version) => {
44                    format!("{} {version}", env!("CARGO_PKG_NAME"))
45                }
46                None => constant::error_messages::VERSION_INFOS_NOT_AVAILABLE
47                    .to_string(),
48            };
49
50            Some(ProgramExit {
51                message,
52                exit_status: constant::exit_status::SUCCESS,
53                styled_message: None,
54                kind: ExitKind::VersionInfos,
55            })
56        } else if args.show_author {
57            let message = String::from(match self.cli_parser.get_author() {
58                Some(author) => author,
59                None => constant::error_messages::AUTHOR_INFOS_NOT_AVAILABLE,
60            });
61
62            Some(ProgramExit {
63                message,
64                exit_status: constant::exit_status::SUCCESS,
65                styled_message: None,
66                kind: ExitKind::AuthorInfos,
67            })
68        } else {
69            None
70        }
71    }
72
73    fn map_arg_matches_to_struct(arg_matches: &ArgMatches) -> Args {
74        Args {
75            template_names: arg_matches
76                .get_many::<String>("TEMPLATE_NAMES")
77                .map(|vals| vals.cloned().collect())
78                .unwrap_or_default(),
79
80            server_url: arg_matches
81                .get_one::<String>("SERVER_URL")
82                .unwrap()
83                .to_string(),
84
85            endpoint_uri: arg_matches
86                .get_one::<String>("ENDPOINT_URI")
87                .unwrap()
88                .to_string(),
89
90            show_help: arg_matches.get_flag("HELP"),
91            show_version: arg_matches.get_flag("VERSION"),
92            show_author: arg_matches.get_flag("AUTHOR"),
93        }
94    }
95
96    fn print_error_message(error: &ProgramExit, message: &str) {
97        match error.kind {
98            ExitKind::VersionInfos
99            | ExitKind::HelpInfos
100            | ExitKind::AuthorInfos => println!("{message}"),
101            ExitKind::Error => eprintln!("{message}"),
102        }
103    }
104}
105
106impl ArgsParser for ClapArgsParser {
107    /// Parses given cli args and perform basic error handling.
108    ///
109    /// * If the underlying [`ProgramExit`] contains a
110    ///     [`ProgramExit::styled_message`], it will be printed instead of
111    ///     [`ProgramExit::message`].
112    /// * Will exit using [`ProgramExit::exit_status`] if any
113    ///     [`ProgramExit`] received.
114    /// * Will print to stderr on error, to stdout on early exit (i.e. version,
115    ///     author, help options)
116    ///
117    /// See [`ArgsParser::parse`] for more infos.
118    fn parse(&self, args: impl IntoIterator<Item = OsString>) -> Args {
119        match self.try_parse(args) {
120            Ok(parsed_args) => parsed_args,
121            Err(error) => {
122                if let Some(value) = &error.styled_message {
123                    Self::print_error_message(&error, value);
124                } else {
125                    Self::print_error_message(&error, &error.message);
126                }
127
128                exit(error.exit_status);
129            }
130        }
131    }
132
133    fn try_parse(
134        &self,
135        args: impl IntoIterator<Item = OsString>,
136    ) -> Result<Args, ProgramExit> {
137        match self.cli_parser.clone().try_get_matches_from(args) {
138            Ok(parsing_result) => {
139                let parsed_args =
140                    Self::map_arg_matches_to_struct(&parsing_result);
141                match self.parse_global_options(&parsed_args) {
142                    Some(error) => Err(error),
143                    None => Ok(parsed_args),
144                }
145            }
146            Err(error) => Err(ProgramExit {
147                message: format!(
148                    "{}\nFor more information, try '--help'.",
149                    error.render()
150                ),
151                exit_status: error.exit_code(),
152                styled_message: Some(format!(
153                    "{}\nFor more information, try '\u{1b}[1m--help\u{1b}[0m'.",
154                    error.render().ansi()
155                )),
156                kind: ExitKind::Error,
157            }),
158        }
159    }
160}