cli.rs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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. /// This is a low level API. If the application has been built,
  73. /// prefer [`App::get_cli_matches`](`crate::App#method.get_cli_matches`).
  74. ///
  75. /// # Examples
  76. ///
  77. /// ```rust,no_run
  78. /// use tauri::api::cli::get_matches;
  79. /// tauri::Builder::default()
  80. /// .setup(|app| {
  81. /// let matches = get_matches(app.config().tauri.cli.as_ref().unwrap(), app.package_info())?;
  82. /// Ok(())
  83. /// });
  84. /// ```
  85. pub fn get_matches(cli: &CliConfig, package_info: &PackageInfo) -> crate::api::Result<Matches> {
  86. let about = cli
  87. .description()
  88. .unwrap_or(&package_info.description.to_string())
  89. .to_string();
  90. let version = &*package_info.version.to_string();
  91. let app = get_app(package_info, version, &package_info.name, Some(&about), cli);
  92. match app.try_get_matches() {
  93. Ok(matches) => Ok(get_matches_internal(cli, &matches)),
  94. Err(e) => match ErrorExt::kind(&e) {
  95. ErrorKind::DisplayHelp => {
  96. let mut matches = Matches::default();
  97. let help_text = e.to_string();
  98. matches.args.insert(
  99. "help".to_string(),
  100. ArgData {
  101. value: Value::String(help_text),
  102. occurrences: 0,
  103. },
  104. );
  105. Ok(matches)
  106. }
  107. ErrorKind::DisplayVersion => {
  108. let mut matches = Matches::default();
  109. matches
  110. .args
  111. .insert("version".to_string(), Default::default());
  112. Ok(matches)
  113. }
  114. _ => Err(e.into()),
  115. },
  116. }
  117. }
  118. fn get_matches_internal(config: &CliConfig, matches: &ArgMatches) -> Matches {
  119. let mut cli_matches = Matches::default();
  120. map_matches(config, matches, &mut cli_matches);
  121. if let Some((subcommand_name, subcommand_matches)) = matches.subcommand() {
  122. let mut subcommand_cli_matches = Matches::default();
  123. map_matches(
  124. config.subcommands().unwrap().get(subcommand_name).unwrap(),
  125. subcommand_matches,
  126. &mut subcommand_cli_matches,
  127. );
  128. cli_matches.set_subcommand(subcommand_name.to_string(), subcommand_cli_matches);
  129. }
  130. cli_matches
  131. }
  132. fn map_matches(config: &CliConfig, matches: &ArgMatches, cli_matches: &mut Matches) {
  133. if let Some(args) = config.args() {
  134. for arg in args {
  135. let occurrences = matches.occurrences_of(arg.name.clone());
  136. let value = if occurrences == 0 || !arg.takes_value {
  137. Value::Bool(occurrences > 0)
  138. } else if arg.multiple {
  139. matches
  140. .values_of(arg.name.clone())
  141. .map(|v| {
  142. let mut values = Vec::new();
  143. for value in v {
  144. values.push(Value::String(value.to_string()));
  145. }
  146. Value::Array(values)
  147. })
  148. .unwrap_or(Value::Null)
  149. } else {
  150. matches
  151. .value_of(arg.name.clone())
  152. .map(|v| Value::String(v.to_string()))
  153. .unwrap_or(Value::Null)
  154. };
  155. cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences });
  156. }
  157. }
  158. }
  159. fn get_app<'a>(
  160. package_info: &'a PackageInfo,
  161. version: &'a str,
  162. command_name: &'a str,
  163. about: Option<&'a String>,
  164. config: &'a CliConfig,
  165. ) -> App<'a> {
  166. let mut app = App::new(command_name)
  167. .author(package_info.authors)
  168. .version(version);
  169. if let Some(about) = about {
  170. app = app.about(&**about);
  171. }
  172. if let Some(long_description) = config.long_description() {
  173. app = app.long_about(&**long_description);
  174. }
  175. if let Some(before_help) = config.before_help() {
  176. app = app.before_help(&**before_help);
  177. }
  178. if let Some(after_help) = config.after_help() {
  179. app = app.after_help(&**after_help);
  180. }
  181. if let Some(args) = config.args() {
  182. for arg in args {
  183. let arg_name = arg.name.as_ref();
  184. app = app.arg(get_arg(arg_name, arg));
  185. }
  186. }
  187. if let Some(subcommands) = config.subcommands() {
  188. for (subcommand_name, subcommand) in subcommands {
  189. let clap_subcommand = get_app(
  190. package_info,
  191. version,
  192. subcommand_name,
  193. subcommand.description(),
  194. subcommand,
  195. );
  196. app = app.subcommand(clap_subcommand);
  197. }
  198. }
  199. app
  200. }
  201. fn get_arg<'a>(arg_name: &'a str, arg: &'a CliArg) -> Arg<'a> {
  202. let mut clap_arg = Arg::new(arg_name);
  203. if arg.index.is_none() {
  204. clap_arg = clap_arg.long(arg_name);
  205. if let Some(short) = arg.short {
  206. clap_arg = clap_arg.short(short);
  207. }
  208. }
  209. clap_arg = bind_string_arg!(arg, clap_arg, description, help);
  210. clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help);
  211. clap_arg = clap_arg.takes_value(arg.takes_value);
  212. clap_arg = clap_arg.multiple_values(arg.multiple);
  213. clap_arg = clap_arg.multiple_occurrences(arg.multiple_occurrences);
  214. clap_arg = bind_value_arg!(arg, clap_arg, number_of_values);
  215. clap_arg = bind_string_slice_arg!(arg, clap_arg, possible_values);
  216. clap_arg = bind_value_arg!(arg, clap_arg, min_values);
  217. clap_arg = bind_value_arg!(arg, clap_arg, max_values);
  218. clap_arg = clap_arg.required(arg.required);
  219. clap_arg = bind_string_arg!(
  220. arg,
  221. clap_arg,
  222. required_unless_present,
  223. required_unless_present
  224. );
  225. clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all);
  226. clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any);
  227. clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with);
  228. if let Some(value) = &arg.conflicts_with_all {
  229. let v: Vec<&str> = value.iter().map(|x| &**x).collect();
  230. clap_arg = clap_arg.conflicts_with_all(&v);
  231. }
  232. clap_arg = bind_string_arg!(arg, clap_arg, requires, requires);
  233. if let Some(value) = &arg.requires_all {
  234. let v: Vec<&str> = value.iter().map(|x| &**x).collect();
  235. clap_arg = clap_arg.requires_all(&v);
  236. }
  237. clap_arg = bind_if_arg!(arg, clap_arg, requires_if);
  238. clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq);
  239. clap_arg = bind_value_arg!(arg, clap_arg, require_equals);
  240. clap_arg = bind_value_arg!(arg, clap_arg, index);
  241. clap_arg
  242. }