1use clap::{
6 Arg as ClapArg, ArgAction, ArgMatches, Command,
7 builder::{PossibleValue, PossibleValuesParser},
8 error::ErrorKind,
9};
10use serde::Serialize;
11use serde_json::Value;
12use tauri::PackageInfo;
13
14use crate::{Arg, Config};
15
16use std::collections::HashMap;
17
18#[macro_use]
19mod macros;
20
21#[derive(Default, Debug, Serialize, Clone)]
23#[non_exhaustive]
24pub struct ArgData {
25 pub value: Value,
30 pub occurrences: u8,
33}
34
35#[derive(Default, Debug, Serialize, Clone)]
37#[non_exhaustive]
38pub struct SubcommandMatches {
39 pub name: String,
41 pub matches: Matches,
43}
44
45#[derive(Default, Debug, Serialize, Clone)]
47#[non_exhaustive]
48pub struct Matches {
49 pub args: HashMap<String, ArgData>,
51 pub subcommand: Option<Box<SubcommandMatches>>,
53}
54
55impl Matches {
56 pub(crate) fn set_arg(&mut self, name: String, value: ArgData) {
58 self.args.insert(name, value);
59 }
60
61 pub(crate) fn set_subcommand(&mut self, name: String, matches: Matches) {
63 self.subcommand = Some(Box::new(SubcommandMatches { name, matches }));
64 }
65}
66
67pub fn get_matches(
83 cli: &Config,
84 package_info: &PackageInfo,
85 args: Option<Vec<String>>,
86) -> crate::Result<Matches> {
87 let about = cli
88 .description()
89 .unwrap_or(&package_info.description.to_string())
90 .to_string();
91 let version = package_info.version.to_string();
92 let app = get_app(
93 package_info,
94 version,
95 package_info.name.clone(),
96 Some(&about),
97 cli,
98 );
99
100 let matches = if let Some(args) = args {
101 app.try_get_matches_from(args)
102 } else {
103 app.try_get_matches()
104 };
105
106 match matches {
107 Ok(matches) => Ok(get_matches_internal(cli, &matches)),
108 Err(e) => match e.kind() {
109 ErrorKind::DisplayHelp => {
110 let mut matches = Matches::default();
111 let help_text = e.to_string();
112 matches.args.insert(
113 "help".to_string(),
114 ArgData {
115 value: Value::String(help_text),
116 occurrences: 0,
117 },
118 );
119 Ok(matches)
120 }
121 ErrorKind::DisplayVersion => {
122 let mut matches = Matches::default();
123 matches
124 .args
125 .insert("version".to_string(), Default::default());
126 Ok(matches)
127 }
128 _ => Err(e.into()),
129 },
130 }
131}
132
133fn get_matches_internal(config: &Config, matches: &ArgMatches) -> Matches {
134 let mut cli_matches = Matches::default();
135 map_matches(config, matches, &mut cli_matches);
136
137 if let Some((subcommand_name, subcommand_matches)) = matches.subcommand()
138 && let Some(subcommand_config) = config
139 .subcommands
140 .as_ref()
141 .and_then(|s| s.get(subcommand_name))
142 {
143 cli_matches.set_subcommand(
144 subcommand_name.to_string(),
145 get_matches_internal(subcommand_config, subcommand_matches),
146 );
147 }
148
149 cli_matches
150}
151
152fn map_matches(config: &Config, matches: &ArgMatches, cli_matches: &mut Matches) {
153 if let Some(args) = config.args() {
154 for arg in args {
155 let (occurrences, value) = if arg.takes_value {
156 if arg.multiple {
157 matches
158 .get_many::<String>(&arg.name)
159 .map(|v| {
160 let mut values = Vec::new();
161 for value in v {
162 values.push(Value::String(value.into()));
163 }
164 (values.len() as u8, Value::Array(values))
165 })
166 .unwrap_or((0, Value::Null))
167 } else {
168 matches
169 .get_one::<String>(&arg.name)
170 .map(|v| (1, Value::String(v.clone())))
171 .unwrap_or((0, Value::Null))
172 }
173 } else {
174 let occurrences = matches.get_count(&arg.name);
175 (occurrences, Value::Bool(occurrences > 0))
176 };
177
178 cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences });
179 }
180 }
181}
182
183fn get_app(
184 package_info: &PackageInfo,
185 version: String,
186 command_name: String,
187 about: Option<&String>,
188 config: &Config,
189) -> Command {
190 let mut app = Command::new(command_name)
191 .author(package_info.authors)
192 .version(version.clone());
193
194 if let Some(about) = about {
195 app = app.about(about);
196 }
197 if let Some(long_description) = config.long_description() {
198 app = app.long_about(long_description);
199 }
200 if let Some(before_help) = config.before_help() {
201 app = app.before_help(before_help);
202 }
203 if let Some(after_help) = config.after_help() {
204 app = app.after_help(after_help);
205 }
206
207 if let Some(args) = config.args() {
208 for arg in args {
209 app = app.arg(get_arg(arg.name.clone(), arg));
210 }
211 }
212
213 if let Some(subcommands) = config.subcommands() {
214 for (subcommand_name, subcommand) in subcommands {
215 let clap_subcommand = get_app(
216 package_info,
217 version.clone(),
218 subcommand_name.to_string(),
219 subcommand.description(),
220 subcommand,
221 );
222 app = app.subcommand(clap_subcommand);
223 }
224 }
225
226 app
227}
228
229fn get_arg(arg_name: String, arg: &Arg) -> ClapArg {
230 let mut clap_arg = ClapArg::new(arg_name.clone());
231
232 if arg.index.is_none() {
233 clap_arg = clap_arg.long(arg_name);
234 if let Some(short) = arg.short {
235 clap_arg = clap_arg.short(short);
236 }
237 }
238
239 clap_arg = bind_string_arg!(arg, clap_arg, description, help);
240 clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help);
241
242 let action = if arg.multiple {
243 ArgAction::Append
244 } else if arg.takes_value {
245 ArgAction::Set
246 } else {
247 ArgAction::Count
248 };
249
250 clap_arg = clap_arg.action(action);
251
252 clap_arg = bind_value_arg!(arg, clap_arg, number_of_values);
253
254 if let Some(values) = &arg.possible_values {
255 clap_arg = clap_arg.value_parser(PossibleValuesParser::new(
256 values
257 .iter()
258 .map(PossibleValue::new)
259 .collect::<Vec<PossibleValue>>(),
260 ));
261 }
262
263 clap_arg = match (arg.min_values, arg.max_values) {
264 (Some(min), Some(max)) => clap_arg.num_args(min..=max),
265 (Some(min), None) => clap_arg.num_args(min..),
266 (None, Some(max)) => clap_arg.num_args(0..max),
267 (None, None) => clap_arg,
268 };
269 clap_arg = clap_arg.required(arg.required);
270 clap_arg = bind_string_arg!(
271 arg,
272 clap_arg,
273 required_unless_present,
274 required_unless_present
275 );
276 clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all);
277 clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any);
278 clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with);
279 if let Some(value) = &arg.conflicts_with_all {
280 clap_arg = clap_arg.conflicts_with_all(value);
281 }
282 clap_arg = bind_string_arg!(arg, clap_arg, requires, requires);
283 if let Some(value) = &arg.requires_all {
284 clap_arg = clap_arg.requires_all(value);
285 }
286 clap_arg = bind_if_arg!(arg, clap_arg, requires_if);
287 clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq);
288 clap_arg = bind_value_arg!(arg, clap_arg, require_equals);
289 clap_arg = bind_value_arg!(arg, clap_arg, index);
290
291 clap_arg = clap_arg.global(arg.global);
292
293 clap_arg
294}