cli.rs 7.1 KB

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