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 fn fallible_new(os: impl AsRef<OsStr>) -> std::result::Result<Tags, String> {
223 Ok(Self::new(os))
224 }
225}