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::new(".").unwrap(),
39 };
40 let output = match m.remove_one::<Output>("output") {
41 Some(o) => o,
42 None => Output::try_from_input(&input).unwrap(),
43 };
44
45 Ok(Config {
46 input,
47 output,
48 recursive: *m.get_one::<u8>("recursive").unwrap_or(&0),
49 tags: m
50 .remove_one::<Tags>("tags")
51 .unwrap_or_else(|| Tags::default()),
52 zip: m.get_flag("zip"),
53 unzip: m.get_flag("unzip"),
54 force: m.get_flag("force"),
55 exit_on_err: m.get_flag("exit-on-err"),
56 jobs: *m.get_one::<u8>("jobs").unwrap_or(&1),
57 })
58 }
59
60 fn update_from_arg_matches_mut(&mut self, m: &mut ArgMatches) -> Result<()> {
61 *self = Self::from_arg_matches_mut(m)?;
62 Ok(())
63 }
64}
65
66impl CommandFactory for Config {
67 fn command() -> Command {
68 Command::new(env!("CARGO_PKG_NAME"))
69 .version(concat!("v", env!("CARGO_PKG_VERSION")))
70 .disable_help_flag(true)
71 .disable_version_flag(true)
72 .override_usage(concat!(env!("CARGO_PKG_NAME"), " [options]"))
73 .arg(
74 Arg::new("input")
75 .short('i')
76 .long("input")
77 .value_name("path")
78 .help(msg!(HelpInput))
79 .value_parser(ValueParser::new(InputParser)),
80 )
81 .arg(
82 Arg::new("output")
83 .short('o')
84 .long("output")
85 .value_name("dir")
86 .help(msg!(HelpOutput))
87 .value_parser(ValueParser::new(OutputParser)),
88 )
89 .arg(
90 Arg::new("recursive")
91 .short('r')
92 .long("recursive")
93 .value_name("n")
94 .help(msg!(HelpRecursive))
95 .num_args(0..=1)
96 .default_missing_value("16")
97 .value_parser(value_parser!(u8).range(1..)),
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("force")
125 .short('f')
126 .long("force")
127 .alias("overwrite")
128 .help(msg!(HelpForce))
129 .action(ArgAction::SetTrue),
130 )
131 .arg(
132 Arg::new("exit-on-err")
133 .short('e')
134 .long("exit-on-err")
135 .alias("exit-on-error")
136 .help(msg!(HelpExitOnError))
137 .action(ArgAction::SetTrue),
138 )
139 .arg(
140 Arg::new("jobs")
141 .short('j')
142 .long("jobs")
143 .value_name("n")
144 .help(msg!(HelpJobs))
145 .value_parser(value_parser!(u8).range(1..)),
146 )
147 .next_help_heading("Other")
148 .arg(
149 Arg::new("lang")
150 .short('l')
151 .long("lang")
152 .alias("language")
153 .value_name("lng")
154 .help(msg!(HelpLang))
155 .value_parser(ValueParser::new(Lang::from_str)),
156 )
157 .arg(
158 Arg::new("version")
159 .short('V')
160 .long("version")
161 .help(msg!(HelpVersion))
162 .action(ArgAction::Version),
163 )
164 .arg(
165 Arg::new("help")
166 .short('h')
167 .long("help")
168 .help(msg!(HelpHelp))
169 .action(ArgAction::SetTrue),
170 )
171 }
172
173 fn command_for_update() -> Command {
174 Self::command()
175 }
176}
177
178macro_rules! ty_parser {
179 ($parser:ident, $ty:ty, $try_from_os_str:path) => {
180 #[derive(Clone)]
181 pub(super) struct $parser;
182
183 impl TypedValueParser for $parser {
184 type Value = $ty;
185
186 fn parse_ref(
187 &self,
188 cmd: &Command,
189 arg: Option<&Arg>,
190 value: &OsStr,
191 ) -> Result<Self::Value> {
192 $try_from_os_str(value).map_err(|e| {
193 let mut err = Error::new(ErrorKind::InvalidValue).with_cmd(&cmd);
194
195 if let Some(arg) = arg {
196 err.insert(
197 ContextKind::InvalidArg,
198 ContextValue::String(arg.to_string()),
199 );
200 }
201 err.insert(
202 ContextKind::InvalidValue,
203 ContextValue::String(format!(
204 "'{}' (reason: {})",
205 value.to_string_lossy(),
206 e
207 )),
208 );
209
210 err
211 })
212 }
213 }
214 };
215}
216
217ty_parser!(InputParser, Input, Input::new);
218ty_parser!(OutputParser, Output, Output::new);
219ty_parser!(TagsParser, Tags, Tags::fallible_new);
220
221impl Tags {
222 fn fallible_new(os: impl AsRef<OsStr>) -> std::result::Result<Tags, String> {
224 Ok(Self::new(os))
225 }
226}