Skip to main content

fb2_clean/cli/
parser.rs

1use crate::{Config, Input, Lang, Msg, Output, Tags, msg};
2use clap::{
3    Arg, ArgAction, ArgMatches, Command, CommandFactory, Error, FromArgMatches, Parser,
4    builder::{TypedValueParser, ValueParser},
5    error::{ContextKind, ContextValue, ErrorKind},
6    value_parser,
7};
8use std::{ffi::OsStr, str::FromStr};
9
10type Result<T> = std::result::Result<T, clap::Error>;
11
12impl Parser for Config {}
13
14impl FromArgMatches for Config {
15    fn from_arg_matches(m: &ArgMatches) -> Result<Config> {
16        Self::from_arg_matches_mut(&mut m.clone())
17    }
18
19    fn update_from_arg_matches(&mut self, m: &ArgMatches) -> Result<()> {
20        self.update_from_arg_matches_mut(&mut m.clone())
21    }
22
23    fn from_arg_matches_mut(m: &mut ArgMatches) -> Result<Config> {
24        if let Some(lang) = m.remove_one::<Lang>("lang") {
25            let _ = Msg::set_lang(lang);
26        }
27
28        if m.get_flag("help") {
29            let mut cmd = Config::command();
30            if let Err(_) = cmd.print_help() {
31                println!("{}", cmd.render_help());
32            }
33            std::process::exit(0);
34        }
35
36        let input = match m.remove_one::<Input>("input") {
37            Some(i) => i,
38            None => Input::new(".").unwrap(),
39        };
40        let output = match m.remove_one::<Output>("output") {
41            Some(o) => o,
42            None => Output::try_from_input(&input).unwrap(),
43        };
44
45        Ok(Config {
46            input,
47            output,
48            recursive: *m.get_one::<u8>("recursive").unwrap_or(&0),
49            tags: m
50                .remove_one::<Tags>("tags")
51                .unwrap_or_else(|| Tags::default()),
52            zip: m.get_flag("zip"),
53            unzip: m.get_flag("unzip"),
54            force: m.get_flag("force"),
55            exit_on_err: m.get_flag("exit-on-err"),
56            jobs: *m.get_one::<u8>("jobs").unwrap_or(&1),
57        })
58    }
59
60    fn update_from_arg_matches_mut(&mut self, m: &mut ArgMatches) -> Result<()> {
61        *self = Self::from_arg_matches_mut(m)?;
62        Ok(())
63    }
64}
65
66impl CommandFactory for Config {
67    fn command() -> Command {
68        Command::new(env!("CARGO_PKG_NAME"))
69            .version(concat!("v", env!("CARGO_PKG_VERSION")))
70            .disable_help_flag(true)
71            .disable_version_flag(true)
72            .override_usage(concat!(env!("CARGO_PKG_NAME"), " [options]"))
73            .arg(
74                Arg::new("input")
75                    .short('i')
76                    .long("input")
77                    .value_name("path")
78                    .help(msg!(HelpInput))
79                    .value_parser(ValueParser::new(InputParser)),
80            )
81            .arg(
82                Arg::new("output")
83                    .short('o')
84                    .long("output")
85                    .value_name("dir")
86                    .help(msg!(HelpOutput))
87                    .value_parser(ValueParser::new(OutputParser)),
88            )
89            .arg(
90                Arg::new("recursive")
91                    .short('r')
92                    .long("recursive")
93                    .value_name("n")
94                    .help(msg!(HelpRecursive))
95                    .num_args(0..=1)
96                    .default_missing_value("16")
97                    .value_parser(value_parser!(u8).range(1..)),
98            )
99            .arg(
100                Arg::new("tags")
101                    .short('t')
102                    .long("tags")
103                    .value_name("n[,m...]")
104                    .help(msg!(HelpTags))
105                    .value_parser(ValueParser::new(TagsParser)),
106            )
107            .arg(
108                Arg::new("zip")
109                    .short('z')
110                    .long("zip")
111                    .help(msg!(HelpZip))
112                    .action(ArgAction::SetTrue),
113            )
114            .arg(
115                Arg::new("unzip")
116                    .short('Z')
117                    .long("unzip")
118                    .alias("no-zip")
119                    .help(msg!(HelpUnzip))
120                    .conflicts_with("zip")
121                    .action(ArgAction::SetTrue),
122            )
123            .arg(
124                Arg::new("force")
125                    .short('f')
126                    .long("force")
127                    .alias("overwrite")
128                    .help(msg!(HelpForce))
129                    .action(ArgAction::SetTrue),
130            )
131            .arg(
132                Arg::new("exit-on-err")
133                    .short('e')
134                    .long("exit-on-err")
135                    .alias("exit-on-error")
136                    .help(msg!(HelpExitOnError))
137                    .action(ArgAction::SetTrue),
138            )
139            .arg(
140                Arg::new("jobs")
141                    .short('j')
142                    .long("jobs")
143                    .value_name("n")
144                    .help(msg!(HelpJobs))
145                    .value_parser(value_parser!(u8).range(1..)),
146            )
147            .next_help_heading("Other")
148            .arg(
149                Arg::new("lang")
150                    .short('l')
151                    .long("lang")
152                    .alias("language")
153                    .value_name("lng")
154                    .help(msg!(HelpLang))
155                    .value_parser(ValueParser::new(Lang::from_str)),
156            )
157            .arg(
158                Arg::new("version")
159                    .short('V')
160                    .long("version")
161                    .help(msg!(HelpVersion))
162                    .action(ArgAction::Version),
163            )
164            .arg(
165                Arg::new("help")
166                    .short('h')
167                    .long("help")
168                    .help(msg!(HelpHelp))
169                    .action(ArgAction::SetTrue),
170            )
171    }
172
173    fn command_for_update() -> Command {
174        Self::command()
175    }
176}
177
178macro_rules! ty_parser {
179    ($parser:ident, $ty:ty, $try_from_os_str:path) => {
180        #[derive(Clone)]
181        pub(super) struct $parser;
182
183        impl TypedValueParser for $parser {
184            type Value = $ty;
185
186            fn parse_ref(
187                &self,
188                cmd: &Command,
189                arg: Option<&Arg>,
190                value: &OsStr,
191            ) -> Result<Self::Value> {
192                $try_from_os_str(value).map_err(|e| {
193                    let mut err = Error::new(ErrorKind::InvalidValue).with_cmd(&cmd);
194
195                    if let Some(arg) = arg {
196                        err.insert(
197                            ContextKind::InvalidArg,
198                            ContextValue::String(arg.to_string()),
199                        );
200                    }
201                    err.insert(
202                        ContextKind::InvalidValue,
203                        ContextValue::String(format!(
204                            "'{}' (reason: {})",
205                            value.to_string_lossy(),
206                            e
207                        )),
208                    );
209
210                    err
211                })
212            }
213        }
214    };
215}
216
217ty_parser!(InputParser, Input, Input::new);
218ty_parser!(OutputParser, Output, Output::new);
219ty_parser!(TagsParser, Tags, Tags::fallible_new);
220
221impl Tags {
222    /// Always returns `Ok`.
223    fn fallible_new(os: impl AsRef<OsStr>) -> std::result::Result<Tags, String> {
224        Ok(Self::new(os))
225    }
226}