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