1use clap::{
6 builder::{PossibleValue, PossibleValuesParser},
7 error::ErrorKind,
8 Arg as ClapArg, ArgAction, ArgMatches, Command,
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 if 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
150 cli_matches
151}
152
153fn map_matches(config: &Config, matches: &ArgMatches, cli_matches: &mut Matches) {
154 if let Some(args) = config.args() {
155 for arg in args {
156 let (occurrences, value) = if arg.takes_value {
157 if arg.multiple {
158 matches
159 .get_many::<String>(&arg.name)
160 .map(|v| {
161 let mut values = Vec::new();
162 for value in v {
163 values.push(Value::String(value.into()));
164 }
165 (values.len() as u8, Value::Array(values))
166 })
167 .unwrap_or((0, Value::Null))
168 } else {
169 matches
170 .get_one::<String>(&arg.name)
171 .map(|v| (1, Value::String(v.clone())))
172 .unwrap_or((0, Value::Null))
173 }
174 } else {
175 let occurrences = matches.get_count(&arg.name);
176 (occurrences, Value::Bool(occurrences > 0))
177 };
178
179 cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences });
180 }
181 }
182}
183
184fn get_app(
185 package_info: &PackageInfo,
186 version: String,
187 command_name: String,
188 about: Option<&String>,
189 config: &Config,
190) -> Command {
191 let mut app = Command::new(command_name)
192 .author(package_info.authors)
193 .version(version.clone());
194
195 if let Some(about) = about {
196 app = app.about(about);
197 }
198 if let Some(long_description) = config.long_description() {
199 app = app.long_about(long_description);
200 }
201 if let Some(before_help) = config.before_help() {
202 app = app.before_help(before_help);
203 }
204 if let Some(after_help) = config.after_help() {
205 app = app.after_help(after_help);
206 }
207
208 if let Some(args) = config.args() {
209 for arg in args {
210 app = app.arg(get_arg(arg.name.clone(), arg));
211 }
212 }
213
214 if let Some(subcommands) = config.subcommands() {
215 for (subcommand_name, subcommand) in subcommands {
216 let clap_subcommand = get_app(
217 package_info,
218 version.clone(),
219 subcommand_name.to_string(),
220 subcommand.description(),
221 subcommand,
222 );
223 app = app.subcommand(clap_subcommand);
224 }
225 }
226
227 app
228}
229
230fn get_arg(arg_name: String, arg: &Arg) -> ClapArg {
231 let mut clap_arg = ClapArg::new(arg_name.clone());
232
233 if arg.index.is_none() {
234 clap_arg = clap_arg.long(arg_name);
235 if let Some(short) = arg.short {
236 clap_arg = clap_arg.short(short);
237 }
238 }
239
240 clap_arg = bind_string_arg!(arg, clap_arg, description, help);
241 clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help);
242
243 let action = if arg.multiple {
244 ArgAction::Append
245 } else if arg.takes_value {
246 ArgAction::Set
247 } else {
248 ArgAction::Count
249 };
250
251 clap_arg = clap_arg.action(action);
252
253 clap_arg = bind_value_arg!(arg, clap_arg, number_of_values);
254
255 if let Some(values) = &arg.possible_values {
256 clap_arg = clap_arg.value_parser(PossibleValuesParser::new(
257 values
258 .iter()
259 .map(PossibleValue::new)
260 .collect::<Vec<PossibleValue>>(),
261 ));
262 }
263
264 clap_arg = match (arg.min_values, arg.max_values) {
265 (Some(min), Some(max)) => clap_arg.num_args(min..=max),
266 (Some(min), None) => clap_arg.num_args(min..),
267 (None, Some(max)) => clap_arg.num_args(0..max),
268 (None, None) => clap_arg,
269 };
270 clap_arg = clap_arg.required(arg.required);
271 clap_arg = bind_string_arg!(
272 arg,
273 clap_arg,
274 required_unless_present,
275 required_unless_present
276 );
277 clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all);
278 clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any);
279 clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with);
280 if let Some(value) = &arg.conflicts_with_all {
281 clap_arg = clap_arg.conflicts_with_all(value);
282 }
283 clap_arg = bind_string_arg!(arg, clap_arg, requires, requires);
284 if let Some(value) = &arg.requires_all {
285 clap_arg = clap_arg.requires_all(value);
286 }
287 clap_arg = bind_if_arg!(arg, clap_arg, requires_if);
288 clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq);
289 clap_arg = bind_value_arg!(arg, clap_arg, require_equals);
290 clap_arg = bind_value_arg!(arg, clap_arg, index);
291
292 clap_arg = clap_arg.global(arg.global);
293
294 clap_arg
295}