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