gitignore_template_generator/parser/
impls.rs

1use std::{ffi::OsString, process::exit};
2
3use clap::{CommandFactory, Parser};
4
5use crate::{ExitKind, ProgramExit, constant};
6
7use super::{Args, ArgsParser};
8
9/// Default implementation of args parser that parses CLI args using
10/// [`clap`].
11pub struct DefaultArgsParser;
12
13impl DefaultArgsParser {
14    fn parse_global_options(args: &Args) -> Option<ProgramExit> {
15        if args.show_help {
16            let mut cmd = Args::command();
17            let rendered_help = cmd.render_help();
18            Some(ProgramExit {
19                message: rendered_help.to_string().trim_end().to_string(),
20                exit_status: constant::exit_status::SUCCESS,
21                styled_message: Some(
22                    rendered_help.ansi().to_string().trim_end().to_string(),
23                ),
24                kind: ExitKind::HelpInfos,
25            })
26        } else if args.show_version {
27            let cmd = Args::command();
28            let message = match cmd.get_version() {
29                Some(version) => {
30                    format!("{} {version}", env!("CARGO_PKG_NAME"))
31                }
32                None => constant::error_messages::VERSION_INFOS_NOT_AVAILABLE
33                    .to_string(),
34            };
35
36            Some(ProgramExit {
37                message,
38                exit_status: constant::exit_status::SUCCESS,
39                styled_message: None,
40                kind: ExitKind::VersionInfos,
41            })
42        } else if args.show_author {
43            let cmd = Args::command();
44            let message = String::from(match cmd.get_author() {
45                Some(author) => author,
46                None => constant::error_messages::AUTHOR_INFOS_NOT_AVAILABLE,
47            });
48
49            Some(ProgramExit {
50                message,
51                exit_status: constant::exit_status::SUCCESS,
52                styled_message: None,
53                kind: ExitKind::AuthorInfos,
54            })
55        } else {
56            None
57        }
58    }
59
60    fn print_error_message(error: &ProgramExit, message: &str) {
61        match error.kind {
62            ExitKind::VersionInfos
63            | ExitKind::HelpInfos
64            | ExitKind::AuthorInfos => println!("{message}"),
65            ExitKind::Error => eprintln!("{message}"),
66        }
67    }
68}
69
70impl ArgsParser for DefaultArgsParser {
71    /// Parses given cli args and perform basic error handling.
72    ///
73    /// * If the underlying [`ProgramExit`] contains a
74    ///     [`ProgramExit::styled_message`], it will be printed instead of
75    ///     [`ProgramExit::message`].
76    /// * Will exit using [`ProgramExit::exit_status`] if any
77    ///     [`ProgramExit`] received.
78    /// * Will print to stderr on error, to stdout on early exit (i.e. version,
79    ///     author, help options)
80    ///
81    /// See [`ArgsParser::parse`] for more infos.
82    fn parse(args: impl IntoIterator<Item = OsString>) -> Args {
83        match DefaultArgsParser::try_parse(args) {
84            Ok(parsed_args) => parsed_args,
85            Err(error) => {
86                if let Some(value) = &error.styled_message {
87                    Self::print_error_message(&error, value);
88                } else {
89                    Self::print_error_message(&error, &error.message);
90                }
91
92                exit(error.exit_status);
93            }
94        }
95    }
96
97    fn try_parse(
98        args: impl IntoIterator<Item = OsString>,
99    ) -> Result<Args, ProgramExit> {
100        match Args::try_parse_from(args) {
101            Ok(parsed_args) => match Self::parse_global_options(&parsed_args) {
102                Some(error) => Err(error),
103                None => Ok(parsed_args),
104            },
105            Err(error) => Err(ProgramExit {
106                message: format!(
107                    "{}\nFor more information, try '--help'.",
108                    error.render()
109                ),
110                exit_status: error.exit_code(),
111                styled_message: Some(format!(
112                    "{}\nFor more information, try '\u{1b}[1m--help\u{1b}[0m'.",
113                    error.render().ansi()
114                )),
115                kind: ExitKind::Error,
116            }),
117        }
118    }
119}