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::default(),
39        };
40        let output = match m.remove_one::<Output>("output") {
41            Some(o) => o,
42            None => Output::try_from_input(&input).unwrap_or_default(),
43        };
44
45        Ok(Config {
46            input,
47            output,
48            recursive: *m
49                .get_one::<u8>("recursive")
50                .unwrap_or(&Self::default_recursive()),
51            tags: m
52                .remove_one::<Tags>("tags")
53                .unwrap_or_else(|| Tags::default()),
54            zip: m.get_flag("zip"),
55            unzip: m.get_flag("unzip"),
56            overwrite: m.get_flag("overwrite"),
57            exit_on_err: m.get_flag("exit-on-err"),
58            jobs: *m.get_one::<u64>("jobs").unwrap_or(&Self::default_jobs()),
59        })
60    }
61
62    fn update_from_arg_matches_mut(&mut self, m: &mut ArgMatches) -> Result<()> {
63        *self = Self::from_arg_matches_mut(m)?;
64        Ok(())
65    }
66}
67
68impl CommandFactory for Config {
69    fn command() -> Command {
70        Command::new(env!("CARGO_PKG_NAME"))
71            .version(concat!("v", env!("CARGO_PKG_VERSION")))
72            .disable_help_flag(true)
73            .disable_version_flag(true)
74            .override_usage(concat!(env!("CARGO_PKG_NAME"), " [options]"))
75            .arg(
76                Arg::new("input")
77                    .short('i')
78                    .long("input")
79                    .value_name("path")
80                    .help(msg!(HelpInput))
81                    .value_parser(ValueParser::new(InputParser)),
82            )
83            .arg(
84                Arg::new("output")
85                    .short('o')
86                    .long("output")
87                    .value_name("dir")
88                    .help(msg!(HelpOutput))
89                    .value_parser(ValueParser::new(OutputParser)),
90            )
91            .arg(
92                Arg::new("recursive")
93                    .short('r')
94                    .long("recursive")
95                    .value_name("n")
96                    .help(msg!(HelpRecursive))
97                    .value_parser(value_parser!(u8)),
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("overwrite")
125                    .short('w')
126                    .long("overwrite")
127                    .help(msg!(HelpOverwrite))
128                    .action(ArgAction::SetTrue),
129            )
130            .arg(
131                Arg::new("exit-on-err")
132                    .short('e')
133                    .long("exit-on-err")
134                    .alias("exit-on-error")
135                    .help(msg!(HelpExitOnError))
136                    .action(ArgAction::SetTrue),
137            )
138            .arg(
139                Arg::new("jobs")
140                    .short('j')
141                    .long("jobs")
142                    .value_name("n")
143                    .help(msg!(HelpJobs))
144                    .value_parser(value_parser!(u64).range(1..)),
145            )
146            .next_help_heading("Other")
147            .arg(
148                Arg::new("lang")
149                    .short('l')
150                    .long("lang")
151                    .alias("language")
152                    .value_name("lng")
153                    .help(msg!(HelpLang))
154                    .value_parser(ValueParser::new(Lang::from_str)),
155            )
156            .arg(
157                Arg::new("version")
158                    .short('V')
159                    .long("version")
160                    .help(msg!(HelpVersion))
161                    .action(ArgAction::Version),
162            )
163            .arg(
164                Arg::new("help")
165                    .short('h')
166                    .long("help")
167                    .help(msg!(HelpHelp))
168                    .action(ArgAction::SetTrue),
169            )
170    }
171
172    fn command_for_update() -> Command {
173        Self::command()
174    }
175}
176
177macro_rules! ty_parser {
178    ($parser:ident, $ty:ty, $try_from_os_str:path) => {
179        #[derive(Clone)]
180        pub(super) struct $parser;
181
182        impl TypedValueParser for $parser {
183            type Value = $ty;
184
185            fn parse_ref(
186                &self,
187                cmd: &Command,
188                arg: Option<&Arg>,
189                value: &OsStr,
190            ) -> Result<Self::Value> {
191                $try_from_os_str(value).map_err(|e| {
192                    let mut err = Error::new(ErrorKind::InvalidValue).with_cmd(&cmd);
193
194                    if let Some(arg) = arg {
195                        err.insert(
196                            ContextKind::InvalidArg,
197                            ContextValue::String(arg.to_string()),
198                        );
199                    }
200                    err.insert(
201                        ContextKind::InvalidValue,
202                        ContextValue::String(format!(
203                            "'{}' (reason: {})",
204                            value.to_string_lossy(),
205                            e
206                        )),
207                    );
208
209                    err
210                })
211            }
212        }
213    };
214}
215
216ty_parser!(InputParser, Input, Input::new);
217ty_parser!(OutputParser, Output, Output::new);
218ty_parser!(TagsParser, Tags, Tags::fallible_new);
219
220impl Tags {
221    /// Always returns `Ok`.
222    fn fallible_new(os: impl AsRef<OsStr>) -> std::result::Result<Tags, String> {
223        Ok(Self::new(os))
224    }
225}