diff --git a/package.json b/package.json index 09a4861..04a6960 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,25 @@ { - "name": "@tauri-apps/plugin-cli", - "version": "2.0.0-rc.1", - "license": "MIT or APACHE-2.0", - "authors": [ - "Tauri Programme within The Commons Conservancy" - ], - "repository": "https://github.com/tauri-apps/plugins-workspace", - "type": "module", - "types": "./dist-js/index.d.ts", - "main": "./dist-js/index.cjs", - "module": "./dist-js/index.js", - "exports": { - "types": "./dist-js/index.d.ts", - "import": "./dist-js/index.js", - "require": "./dist-js/index.cjs" - }, - "scripts": { - "build": "rollup -c" - }, - "files": [ - "dist-js", - "README.md", - "LICENSE" - ], - "dependencies": { - "@tauri-apps/api": "^2.0.0-rc.4" - } + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "dependencies": { + "@tauri-apps/api": "^2.0.0-rc.4" + }, + "exports": { + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs", + "types": "./dist-js/index.d.ts" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "name": "@tauri-apps/plugin-cli", + "scripts": { + "build": "rollup -c" + }, + "types": "./dist-js/index.d.ts" } diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index f7f36a0..0000000 --- a/src/config.rs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::collections::HashMap; - -use serde::Deserialize; - -/// A CLI argument definition. -#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct Arg { - /// The short version of the argument, without the preceding -. - /// - /// NOTE: Any leading `-` characters will be stripped, and only the first non-character will be used as the short version. - pub short: Option, - /// The unique argument name - pub name: String, - /// The argument description which will be shown on the help information. - /// Typically, this is a short (one line) description of the arg. - pub description: Option, - /// The argument long description which will be shown on the help information. - /// Typically this a more detailed (multi-line) message that describes the argument. - #[serde(alias = "long-description")] - pub long_description: Option, - /// Specifies that the argument takes a value at run time. - /// - /// NOTE: values for arguments may be specified in any of the following methods - /// - Using a space such as -o value or --option value - /// - Using an equals and no space such as -o=value or --option=value - /// - Use a short and no space such as -ovalue - #[serde(default, alias = "takes-value")] - pub takes_value: bool, - /// Specifies that the argument may have an unknown number of multiple values. Without any other settings, this argument may appear only once. - /// - /// For example, --opt val1 val2 is allowed, but --opt val1 val2 --opt val3 is not. - /// - /// NOTE: Setting this requires `takes_value` to be set to true. - #[serde(default)] - pub multiple: bool, - /// Specifies how many values are required to satisfy this argument. For example, if you had a - /// `-f ` argument where you wanted exactly 3 'files' you would set - /// `number_of_values = 3`, and this argument wouldn't be satisfied unless the user provided - /// 3 and only 3 values. - /// - /// **NOTE:** Does *not* require `multiple_occurrences = true` to be set. Setting - /// `multiple_occurrences = true` would allow `-f -f ` where - /// as *not* setting it would only allow one occurrence of this argument. - /// - /// **NOTE:** implicitly sets `takes_value = true` and `multiple_values = true`. - #[serde(alias = "number-of-values")] - pub number_of_values: Option, - /// Specifies a list of possible values for this argument. - /// At runtime, the CLI verifies that only one of the specified values was used, or fails with an error message. - #[serde(alias = "possible-values")] - pub possible_values: Option>, - /// Specifies the minimum number of values for this argument. - /// For example, if you had a -f `` argument where you wanted at least 2 'files', - /// you would set `minValues: 2`, and this argument would be satisfied if the user provided, 2 or more values. - #[serde(alias = "min-values")] - pub min_values: Option, - /// Specifies the maximum number of values are for this argument. - /// For example, if you had a -f `` argument where you wanted up to 3 'files', - /// you would set .max_values(3), and this argument would be satisfied if the user provided, 1, 2, or 3 values. - #[serde(alias = "max-values")] - pub max_values: Option, - /// Sets whether or not the argument is required by default. - /// - /// - Required by default means it is required, when no other conflicting rules have been evaluated - /// - Conflicting rules take precedence over being required. - #[serde(default)] - pub required: bool, - /// Sets an arg that override this arg's required setting - /// i.e. this arg will be required unless this other argument is present. - #[serde(alias = "required-unless-present")] - pub required_unless_present: Option, - /// Sets args that override this arg's required setting - /// i.e. this arg will be required unless all these other arguments are present. - #[serde(alias = "required-unless-present-all")] - pub required_unless_present_all: Option>, - /// Sets args that override this arg's required setting - /// i.e. this arg will be required unless at least one of these other arguments are present. - #[serde(alias = "required-unless-present-any")] - pub required_unless_present_any: Option>, - /// Sets a conflicting argument by name - /// i.e. when using this argument, the following argument can't be present and vice versa. - #[serde(alias = "conflicts-with")] - pub conflicts_with: Option, - /// The same as conflictsWith but allows specifying multiple two-way conflicts per argument. - #[serde(alias = "conflicts-with-all")] - pub conflicts_with_all: Option>, - /// Tets an argument by name that is required when this one is present - /// i.e. when using this argument, the following argument must be present. - pub requires: Option, - /// Sts multiple arguments by names that are required when this one is present - /// i.e. when using this argument, the following arguments must be present. - #[serde(alias = "requires-all")] - pub requires_all: Option>, - /// Allows a conditional requirement with the signature [arg, value] - /// the requirement will only become valid if `arg`'s value equals `${value}`. - #[serde(alias = "requires-if")] - pub requires_if: Option<(String, String)>, - /// Allows specifying that an argument is required conditionally with the signature [arg, value] - /// the requirement will only become valid if the `arg`'s value equals `${value}`. - #[serde(alias = "required-if-eq")] - pub required_if_eq: Option<(String, String)>, - /// Requires that options use the --option=val syntax - /// i.e. an equals between the option and associated value. - #[serde(alias = "requires-equals")] - pub require_equals: Option, - /// The positional argument index, starting at 1. - /// - /// The index refers to position according to other positional argument. - /// It does not define position in the argument list as a whole. When utilized with multiple=true, - /// only the last positional argument may be defined as multiple (i.e. the one with the highest index). - pub index: Option, -} - -/// describes a CLI configuration -#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct Config { - /// Command description which will be shown on the help information. - pub description: Option, - /// Command long description which will be shown on the help information. - #[serde(alias = "long-description")] - pub long_description: Option, - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed before the auto-generated help information. - /// This is often used for header information. - #[serde(alias = "before-help")] - pub before_help: Option, - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed after the auto-generated help information. - /// This is often used to describe how to use the arguments, or caveats to be noted. - #[serde(alias = "after-help")] - pub after_help: Option, - /// List of arguments for the command - pub args: Option>, - /// List of subcommands of this command - pub subcommands: Option>, -} - -impl Config { - /// List of arguments for the command - pub fn args(&self) -> Option<&Vec> { - self.args.as_ref() - } - - /// List of subcommands of this command - pub fn subcommands(&self) -> Option<&HashMap> { - self.subcommands.as_ref() - } - - /// Command description which will be shown on the help information. - pub fn description(&self) -> Option<&String> { - self.description.as_ref() - } - - /// Command long description which will be shown on the help information. - pub fn long_description(&self) -> Option<&String> { - self.description.as_ref() - } - - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed before the auto-generated help information. - /// This is often used for header information. - pub fn before_help(&self) -> Option<&String> { - self.before_help.as_ref() - } - - /// Adds additional help information to be displayed in addition to auto-generated help. - /// This information is displayed after the auto-generated help information. - /// This is often used to describe how to use the arguments, or caveats to be noted. - pub fn after_help(&self) -> Option<&String> { - self.after_help.as_ref() - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index b4bd287..0000000 --- a/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use serde::{Serialize, Serializer}; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("failed to parse arguments: {0}")] - ParseCli(#[from] clap::Error), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 0e65409..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! [![](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/cli/banner.png)](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/cli) -//! -//! Parse arguments from your Command Line Interface. -//! -//! - Supported platforms: Windows, Linux and macOS. - -#![doc( - html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", - html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" -)] - -use tauri::{ - plugin::{Builder, PluginApi, TauriPlugin}, - AppHandle, Manager, Runtime, State, -}; - -mod config; -mod error; -mod parser; - -use config::{Arg, Config}; -pub use error::Error; -type Result = std::result::Result; - -pub struct Cli(PluginApi); - -impl Cli { - pub fn matches(&self) -> Result { - parser::get_matches(self.0.config(), self.0.app().package_info()) - } -} - -pub trait CliExt { - fn cli(&self) -> &Cli; -} - -impl> CliExt for T { - fn cli(&self) -> &Cli { - self.state::>().inner() - } -} - -#[tauri::command] -fn cli_matches(_app: AppHandle, cli: State<'_, Cli>) -> Result { - cli.matches() -} - -pub fn init() -> TauriPlugin { - Builder::new("cli") - .invoke_handler(tauri::generate_handler![cli_matches]) - .setup(|app, api| { - app.manage(Cli(api)); - Ok(()) - }) - .build() -} diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 69a926c..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use clap::{ - builder::{PossibleValue, PossibleValuesParser}, - error::ErrorKind, - Arg as ClapArg, ArgAction, ArgMatches, Command, -}; -use serde::Serialize; -use serde_json::Value; -use tauri::PackageInfo; - -use crate::{Arg, Config}; - -use std::collections::HashMap; - -#[macro_use] -mod macros; - -/// The resolution of a argument match. -#[derive(Default, Debug, Serialize)] -#[non_exhaustive] -pub struct ArgData { - /// - [`Value::Bool`] if it's a flag, - /// - [`Value::Array`] if it's multiple, - /// - [`Value::String`] if it has value, - /// - [`Value::Null`] otherwise. - pub value: Value, - /// The number of occurrences of the argument. - /// e.g. `./app --arg 1 --arg 2 --arg 2 3 4` results in three occurrences. - pub occurrences: u8, -} - -/// The matched subcommand. -#[derive(Default, Debug, Serialize)] -#[non_exhaustive] -pub struct SubcommandMatches { - /// The subcommand name. - pub name: String, - /// The subcommand argument matches. - pub matches: Matches, -} - -/// The argument matches of a command. -#[derive(Default, Debug, Serialize)] -#[non_exhaustive] -pub struct Matches { - /// Data structure mapping each found arg with its resolution. - pub args: HashMap, - /// The matched subcommand if found. - pub subcommand: Option>, -} - -impl Matches { - /// Set a arg match. - pub(crate) fn set_arg(&mut self, name: String, value: ArgData) { - self.args.insert(name, value); - } - - /// Sets the subcommand matches. - pub(crate) fn set_subcommand(&mut self, name: String, matches: Matches) { - self.subcommand = Some(Box::new(SubcommandMatches { name, matches })); - } -} - -/// Gets the argument matches of the CLI definition. -/// -/// This is a low level API. If the application has been built, -/// prefer [`App::get_cli_matches`](`crate::App#method.get_cli_matches`). -/// -/// # Examples -/// -/// ```rust,no_run -/// use tauri_plugin_cli::CliExt; -/// tauri::Builder::default() -/// .setup(|app| { -/// let matches = app.cli().matches()?; -/// Ok(()) -/// }); -/// ``` -pub fn get_matches(cli: &Config, package_info: &PackageInfo) -> crate::Result { - let about = cli - .description() - .unwrap_or(&package_info.description.to_string()) - .to_string(); - let version = package_info.version.to_string(); - let app = get_app( - package_info, - version, - package_info.name.clone(), - Some(&about), - cli, - ); - match app.try_get_matches() { - Ok(matches) => Ok(get_matches_internal(cli, &matches)), - Err(e) => match e.kind() { - ErrorKind::DisplayHelp => { - let mut matches = Matches::default(); - let help_text = e.to_string(); - matches.args.insert( - "help".to_string(), - ArgData { - value: Value::String(help_text), - occurrences: 0, - }, - ); - Ok(matches) - } - ErrorKind::DisplayVersion => { - let mut matches = Matches::default(); - matches - .args - .insert("version".to_string(), Default::default()); - Ok(matches) - } - _ => Err(e.into()), - }, - } -} - -fn get_matches_internal(config: &Config, matches: &ArgMatches) -> Matches { - let mut cli_matches = Matches::default(); - map_matches(config, matches, &mut cli_matches); - - if let Some((subcommand_name, subcommand_matches)) = matches.subcommand() { - if let Some(subcommand_config) = config - .subcommands - .as_ref() - .and_then(|s| s.get(subcommand_name)) - { - cli_matches.set_subcommand( - subcommand_name.to_string(), - get_matches_internal(subcommand_config, subcommand_matches), - ); - } - } - - cli_matches -} - -fn map_matches(config: &Config, matches: &ArgMatches, cli_matches: &mut Matches) { - if let Some(args) = config.args() { - for arg in args { - let (occurrences, value) = if arg.takes_value { - if arg.multiple { - matches - .get_many::(&arg.name) - .map(|v| { - let mut values = Vec::new(); - for value in v { - values.push(Value::String(value.into())); - } - (values.len() as u8, Value::Array(values)) - }) - .unwrap_or((0, Value::Null)) - } else { - matches - .get_one::(&arg.name) - .map(|v| (1, Value::String(v.clone()))) - .unwrap_or((0, Value::Null)) - } - } else { - let occurrences = matches.get_count(&arg.name); - (occurrences, Value::Bool(occurrences > 0)) - }; - - cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences }); - } - } -} - -fn get_app( - package_info: &PackageInfo, - version: String, - command_name: String, - about: Option<&String>, - config: &Config, -) -> Command { - let mut app = Command::new(command_name) - .author(package_info.authors) - .version(version.clone()); - - if let Some(about) = about { - app = app.about(about); - } - if let Some(long_description) = config.long_description() { - app = app.long_about(long_description); - } - if let Some(before_help) = config.before_help() { - app = app.before_help(before_help); - } - if let Some(after_help) = config.after_help() { - app = app.after_help(after_help); - } - - if let Some(args) = config.args() { - for arg in args { - app = app.arg(get_arg(arg.name.clone(), arg)); - } - } - - if let Some(subcommands) = config.subcommands() { - for (subcommand_name, subcommand) in subcommands { - let clap_subcommand = get_app( - package_info, - version.clone(), - subcommand_name.to_string(), - subcommand.description(), - subcommand, - ); - app = app.subcommand(clap_subcommand); - } - } - - app -} - -fn get_arg(arg_name: String, arg: &Arg) -> ClapArg { - let mut clap_arg = ClapArg::new(arg_name.clone()); - - if arg.index.is_none() { - clap_arg = clap_arg.long(arg_name); - if let Some(short) = arg.short { - clap_arg = clap_arg.short(short); - } - } - - clap_arg = bind_string_arg!(arg, clap_arg, description, help); - clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help); - - let action = if arg.multiple { - ArgAction::Append - } else if arg.takes_value { - ArgAction::Set - } else { - ArgAction::Count - }; - - clap_arg = clap_arg.action(action); - - clap_arg = bind_value_arg!(arg, clap_arg, number_of_values); - - if let Some(values) = &arg.possible_values { - clap_arg = clap_arg.value_parser(PossibleValuesParser::new( - values - .iter() - .map(PossibleValue::new) - .collect::>(), - )); - } - - clap_arg = match (arg.min_values, arg.max_values) { - (Some(min), Some(max)) => clap_arg.num_args(min..=max), - (Some(min), None) => clap_arg.num_args(min..), - (None, Some(max)) => clap_arg.num_args(0..max), - (None, None) => clap_arg, - }; - clap_arg = clap_arg.required(arg.required); - clap_arg = bind_string_arg!( - arg, - clap_arg, - required_unless_present, - required_unless_present - ); - clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all); - clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any); - clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with); - if let Some(value) = &arg.conflicts_with_all { - clap_arg = clap_arg.conflicts_with_all(value); - } - clap_arg = bind_string_arg!(arg, clap_arg, requires, requires); - if let Some(value) = &arg.requires_all { - clap_arg = clap_arg.requires_all(value); - } - clap_arg = bind_if_arg!(arg, clap_arg, requires_if); - clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq); - clap_arg = bind_value_arg!(arg, clap_arg, require_equals); - clap_arg = bind_value_arg!(arg, clap_arg, index); - - clap_arg -} diff --git a/src/parser/macros.rs b/src/parser/macros.rs deleted file mode 100644 index 2c3c66e..0000000 --- a/src/parser/macros.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -macro_rules! bind_string_arg { - ($arg:expr, $clap_arg:expr, $arg_name:ident, $clap_field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some(value) = &arg.$arg_name { - clap_arg = clap_arg.$clap_field(value); - } - clap_arg - }}; -} - -macro_rules! bind_value_arg { - ($arg:expr, $clap_arg:expr, $field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some(value) = arg.$field { - clap_arg = clap_arg.$field(value); - } - clap_arg - }}; -} - -macro_rules! bind_string_slice_arg { - ($arg:expr, $clap_arg:expr, $field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some(value) = &arg.$field { - clap_arg = clap_arg.$field(value); - } - clap_arg - }}; -} - -macro_rules! bind_if_arg { - ($arg:expr, $clap_arg:expr, $field:ident) => {{ - let arg = $arg; - let mut clap_arg = $clap_arg; - if let Some((value, arg)) = &arg.$field { - clap_arg = clap_arg.$field(value, arg); - } - clap_arg - }}; -}