fb2_clean/config/
parser.rs1use 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);