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