cli.rs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! Types and functions related to CLI arguments.
  5. use crate::{
  6. utils::config::{CliArg, CliConfig},
  7. PackageInfo,
  8. };
  9. use clap::{Arg, ArgMatches, ErrorKind};
  10. use serde::Serialize;
  11. use serde_json::Value;
  12. use std::collections::HashMap;
  13. #[macro_use]
  14. mod macros;
  15. mod clapfix {
  16. //! Compatibility between `clap` 3.0 and 3.1+ without deprecation errors.
  17. #![allow(deprecated)]
  18. pub type ClapCommand<'help> = clap::App<'help>;
  19. pub trait ErrorExt {
  20. fn kind(&self) -> clap::ErrorKind;
  21. }
  22. impl ErrorExt for clap::Error {
  23. fn kind(&self) -> clap::ErrorKind {
  24. self.kind
  25. }
  26. }
  27. }
  28. use clapfix::{ClapCommand as App, ErrorExt};
  29. /// The resolution of a argument match.
  30. #[derive(Default, Debug, Serialize)]
  31. #[non_exhaustive]
  32. pub struct ArgData {
  33. /// - [`Value::Bool`] if it's a flag,
  34. /// - [`Value::Array`] if it's multiple,
  35. /// - [`Value::String`] if it has value,
  36. /// - [`Value::Null`] otherwise.
  37. pub value: Value,
  38. /// The number of occurrences of the argument.
  39. /// e.g. `./app --arg 1 --arg 2 --arg 2 3 4` results in three occurrences.
  40. pub occurrences: u64,
  41. }
  42. /// The matched subcommand.
  43. #[derive(Default, Debug, Serialize)]
  44. #[non_exhaustive]
  45. pub struct SubcommandMatches {
  46. /// The subcommand name.
  47. pub name: String,
  48. /// The subcommand argument matches.
  49. pub matches: Matches,
  50. }
  51. /// The argument matches of a command.
  52. #[derive(Default, Debug, Serialize)]
  53. #[non_exhaustive]
  54. pub struct Matches {
  55. /// Data structure mapping each found arg with its resolution.
  56. pub args: HashMap<String, ArgData>,
  57. /// The matched subcommand if found.
  58. pub subcommand: Option<Box<SubcommandMatches>>,
  59. }
  60. impl Matches {
  61. /// Set a arg match.
  62. pub(crate) fn set_arg(&mut self, name: String, value: ArgData) {
  63. self.args.insert(name, value);
  64. }
  65. /// Sets the subcommand matches.
  66. pub(crate) fn set_subcommand(&mut self, name: String, matches: Matches) {
  67. self.subcommand = Some(Box::new(SubcommandMatches { name, matches }));
  68. }
  69. }
  70. /// Gets the argument matches of the CLI definition.
  71. ///
  72. /// # Examples
  73. ///
  74. /// ```rust,no_run
  75. /// use tauri::api::cli::get_matches;
  76. /// tauri::Builder::default()
  77. /// .setup(|app| {
  78. /// let matches = get_matches(app.config().tauri.cli.as_ref().unwrap(), app.package_info())?;
  79. /// Ok(())
  80. /// });
  81. /// ```
  82. pub fn get_matches(cli: &CliConfig, package_info: &PackageInfo) -> crate::api::Result<Matches> {
  83. let about = cli
  84. .description()
  85. .unwrap_or(&package_info.description.to_string())
  86. .to_string();
  87. let app = get_app(package_info, &package_info.name, Some(&about), cli);
  88. match app.try_get_matches() {
  89. Ok(matches) => Ok(get_matches_internal(cli, &matches)),
  90. Err(e) => match ErrorExt::kind(&e) {
  91. ErrorKind::DisplayHelp => {
  92. let mut matches = Matches::default();
  93. let help_text = e.to_string();
  94. matches.args.insert(
  95. "help".to_string(),
  96. ArgData {
  97. value: Value::String(help_text),
  98. occurrences: 0,
  99. },
  100. );
  101. Ok(matches)
  102. }
  103. ErrorKind::DisplayVersion => {
  104. let mut matches = Matches::default();
  105. matches
  106. .args
  107. .insert("version".to_string(), Default::default());
  108. Ok(matches)
  109. }
  110. _ => Err(e.into()),
  111. },
  112. }
  113. }
  114. fn get_matches_internal(config: &CliConfig, matches: &ArgMatches) -> Matches {
  115. let mut cli_matches = Matches::default();
  116. map_matches(config, matches, &mut cli_matches);
  117. if let Some((subcommand_name, subcommand_matches)) = matches.subcommand() {
  118. let mut subcommand_cli_matches = Matches::default();
  119. map_matches(
  120. config.subcommands().unwrap().get(subcommand_name).unwrap(),
  121. subcommand_matches,
  122. &mut subcommand_cli_matches,
  123. );
  124. cli_matches.set_subcommand(subcommand_name.to_string(), subcommand_cli_matches);
  125. }
  126. cli_matches
  127. }
  128. fn map_matches(config: &CliConfig, matches: &ArgMatches, cli_matches: &mut Matches) {
  129. if let Some(args) = config.args() {
  130. for arg in args {
  131. let occurrences = matches.occurrences_of(arg.name.clone());
  132. let value = if occurrences == 0 || !arg.takes_value {
  133. Value::Bool(occurrences > 0)
  134. } else if arg.multiple {
  135. matches
  136. .values_of(arg.name.clone())
  137. .map(|v| {
  138. let mut values = Vec::new();
  139. for value in v {
  140. values.push(Value::String(value.to_string()));
  141. }
  142. Value::Array(values)
  143. })
  144. .unwrap_or(Value::Null)
  145. } else {
  146. matches
  147. .value_of(arg.name.clone())
  148. .map(|v| Value::String(v.to_string()))
  149. .unwrap_or(Value::Null)
  150. };
  151. cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences });
  152. }
  153. }
  154. }
  155. fn get_app<'a>(
  156. package_info: &'a PackageInfo,
  157. command_name: &'a str,
  158. about: Option<&'a String>,
  159. config: &'a CliConfig,
  160. ) -> App<'a> {
  161. let mut app = App::new(command_name)
  162. .author(package_info.authors)
  163. .version(&*package_info.version);
  164. if let Some(about) = about {
  165. app = app.about(&**about);
  166. }
  167. if let Some(long_description) = config.long_description() {
  168. app = app.long_about(&**long_description);
  169. }
  170. if let Some(before_help) = config.before_help() {
  171. app = app.before_help(&**before_help);
  172. }
  173. if let Some(after_help) = config.after_help() {
  174. app = app.after_help(&**after_help);
  175. }
  176. if let Some(args) = config.args() {
  177. for arg in args {
  178. let arg_name = arg.name.as_ref();
  179. app = app.arg(get_arg(arg_name, arg));
  180. }
  181. }
  182. if let Some(subcommands) = config.subcommands() {
  183. for (subcommand_name, subcommand) in subcommands {
  184. let clap_subcommand = get_app(
  185. package_info,
  186. subcommand_name,
  187. subcommand.description(),
  188. subcommand,
  189. );
  190. app = app.subcommand(clap_subcommand);
  191. }
  192. }
  193. app
  194. }
  195. fn get_arg<'a>(arg_name: &'a str, arg: &'a CliArg) -> Arg<'a> {
  196. let mut clap_arg = Arg::new(arg_name);
  197. if arg.index.is_none() {
  198. clap_arg = clap_arg.long(arg_name);
  199. if let Some(short) = arg.short {
  200. clap_arg = clap_arg.short(short);
  201. }
  202. }
  203. clap_arg = bind_string_arg!(arg, clap_arg, description, help);
  204. clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help);
  205. clap_arg = clap_arg.takes_value(arg.takes_value);
  206. clap_arg = clap_arg.multiple_values(arg.multiple);
  207. clap_arg = clap_arg.multiple_occurrences(arg.multiple_occurrences);
  208. clap_arg = bind_value_arg!(arg, clap_arg, number_of_values);
  209. clap_arg = bind_string_slice_arg!(arg, clap_arg, possible_values);
  210. clap_arg = bind_value_arg!(arg, clap_arg, min_values);
  211. clap_arg = bind_value_arg!(arg, clap_arg, max_values);
  212. clap_arg = clap_arg.required(arg.required);
  213. clap_arg = bind_string_arg!(
  214. arg,
  215. clap_arg,
  216. required_unless_present,
  217. required_unless_present
  218. );
  219. clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all);
  220. clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any);
  221. clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with);
  222. if let Some(value) = &arg.conflicts_with_all {
  223. let v: Vec<&str> = value.iter().map(|x| &**x).collect();
  224. clap_arg = clap_arg.conflicts_with_all(&v);
  225. }
  226. clap_arg = bind_string_arg!(arg, clap_arg, requires, requires);
  227. if let Some(value) = &arg.requires_all {
  228. let v: Vec<&str> = value.iter().map(|x| &**x).collect();
  229. clap_arg = clap_arg.requires_all(&v);
  230. }
  231. clap_arg = bind_if_arg!(arg, clap_arg, requires_if);
  232. clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq);
  233. clap_arg = bind_value_arg!(arg, clap_arg, require_equals);
  234. clap_arg = bind_value_arg!(arg, clap_arg, index);
  235. clap_arg
  236. }