fb2_clean/config/
parser.rs

1use super::{Config, Input, Output, Tags};
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;
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        let input = match m.remove_one::<Input>("input") {
25            Some(i) => i,
26            None => Input::new(".").unwrap(),
27        };
28        let output = match m.remove_one::<Output>("output") {
29            Some(o) => o,
30            None => Output::try_from_input(&input).unwrap(),
31        };
32
33        Ok(Config {
34            input,
35            output,
36            recursive: *m.get_one::<u8>("recursive").unwrap_or(&0),
37            tags: m
38                .remove_one::<Tags>("tags")
39                .unwrap_or_else(|| Tags::default()),
40            zip: m.get_flag("zip"),
41            unzip: m.get_flag("unzip"),
42            force: m.get_flag("force"),
43            exit_on_err: m.get_flag("exit-on-err"),
44            jobs: *m.get_one::<u8>("jobs").unwrap_or(&1),
45        })
46    }
47
48    fn update_from_arg_matches_mut(&mut self, m: &mut ArgMatches) -> Result<()> {
49        *self = Self::from_arg_matches_mut(m)?;
50        Ok(())
51    }
52}
53
54impl CommandFactory for Config {
55    fn command() -> Command {
56        Command::new(env!("CARGO_PKG_NAME"))
57            .version(concat!("v", env!("CARGO_PKG_VERSION")))
58            .disable_help_flag(true)
59            .disable_version_flag(true)
60            .override_usage(concat!(env!("CARGO_PKG_NAME"), " [options]"))
61            .arg(
62                Arg::new("input")
63                    .short('i')
64                    .long("input")
65                    .value_name("path")
66                    .help("Input directory OR file")
67                    .value_parser(ValueParser::new(InputParser)),
68            )
69            .arg(
70                Arg::new("output")
71                    .short('o')
72                    .long("output")
73                    .value_name("dir")
74                    .help("Output directory")
75                    .value_parser(ValueParser::new(OutputParser)),
76            )
77            .arg(
78                Arg::new("recursive")
79                    .short('r')
80                    .long("recursive")
81                    .value_name("n")
82                    .help("Recursive file search [up to n]")
83                    .num_args(0..=1)
84                    .default_missing_value("16")
85                    .value_parser(value_parser!(u8).range(1..)),
86            )
87            .arg(
88                Arg::new("tags")
89                    .short('t')
90                    .long("tags")
91                    .value_name("n[,m...]")
92                    .help("Remove tags")
93                    .value_parser(ValueParser::new(TagsParser)),
94            )
95            .arg(
96                Arg::new("zip")
97                    .short('z')
98                    .long("zip")
99                    .help("Compress fb2 to fb2.zip")
100                    .action(ArgAction::SetTrue),
101            )
102            .arg(
103                Arg::new("unzip")
104                    .short('Z')
105                    .long("unzip")
106                    .alias("no-zip")
107                    .help("Uncompress fb2.zip to fb2")
108                    .conflicts_with("zip")
109                    .action(ArgAction::SetTrue),
110            )
111            .arg(
112                Arg::new("force")
113                    .short('f')
114                    .long("force")
115                    .help("Force overwrite input files")
116                    .action(ArgAction::SetTrue),
117            )
118            .arg(
119                Arg::new("exit-on-err")
120                    .short('e')
121                    .long("exit-on-err")
122                    .alias("exit-on-error")
123                    .help("Skip clean next files on error")
124                    .action(ArgAction::SetTrue),
125            )
126            .arg(
127                Arg::new("jobs")
128                    .short('j')
129                    .long("jobs")
130                    .value_name("n")
131                    .help("Max parallel jobs")
132                    .value_parser(value_parser!(u8).range(1..)),
133            )
134            .next_help_heading("Other")
135            .arg(
136                Arg::new("version")
137                    .short('V')
138                    .long("version")
139                    .help("Show version")
140                    .action(ArgAction::Version),
141            )
142            .arg(
143                Arg::new("help")
144                    .short('h')
145                    .long("help")
146                    .help("Show help")
147                    .action(ArgAction::Help),
148            )
149    }
150
151    fn command_for_update() -> Command {
152        Self::command()
153    }
154}
155
156macro_rules! ty_parser {
157    ($parser:ident, $ty:ty, $try_from_os_str:path) => {
158        #[derive(Clone)]
159        pub(super) struct $parser;
160
161        impl TypedValueParser for $parser {
162            type Value = $ty;
163
164            fn parse_ref(
165                &self,
166                cmd: &Command,
167                arg: Option<&Arg>,
168                value: &OsStr,
169            ) -> Result<Self::Value> {
170                $try_from_os_str(value).map_err(|e| {
171                    let mut err = Error::new(ErrorKind::InvalidValue).with_cmd(&cmd);
172
173                    if let Some(arg) = arg {
174                        err.insert(
175                            ContextKind::InvalidArg,
176                            ContextValue::String(arg.to_string()),
177                        );
178                    }
179                    err.insert(
180                        ContextKind::InvalidValue,
181                        ContextValue::String(format!(
182                            "'{}' (reason: {})",
183                            value.to_string_lossy(),
184                            e
185                        )),
186                    );
187
188                    err
189                })
190            }
191        }
192    };
193}
194
195ty_parser!(InputParser, Input, Input::new);
196ty_parser!(OutputParser, Output, Output::new);
197ty_parser!(TagsParser, Tags, Tags::fallible_new);