f22326ef81
* Update copyright noticed 2020 * Update copyright in two overlooked files
775 lines
20 KiB
Rust
775 lines
20 KiB
Rust
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
|
// This file is part of Parity Ethereum.
|
|
|
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
macro_rules! return_if_parse_error {
|
|
($e:expr) => (
|
|
match $e {
|
|
Err(clap_error @ ClapError { kind: ClapErrorKind::ValueValidation, .. }) => {
|
|
return Err(clap_error);
|
|
},
|
|
|
|
// Otherwise, if $e is ClapErrorKind::ArgumentNotFound or Ok(),
|
|
// then convert to Option
|
|
_ => $e.ok()
|
|
}
|
|
)
|
|
}
|
|
|
|
macro_rules! if_option {
|
|
(Option<$type:ty>, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
|
$($then)*
|
|
);
|
|
($type:ty, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
|
$($otherwise)*
|
|
);
|
|
}
|
|
|
|
macro_rules! if_vec {
|
|
(Vec<$type:ty>, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
|
$($then)*
|
|
);
|
|
($type:ty, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
|
$($otherwise)*
|
|
);
|
|
}
|
|
|
|
macro_rules! if_option_vec {
|
|
(Option<Vec<String>>, THEN {$then:expr} ELSE {$otherwise:expr}) => (
|
|
$then
|
|
);
|
|
(Option<$type:ty>, THEN {$then:expr} ELSE {$otherwise:expr}) => (
|
|
$otherwise
|
|
);
|
|
}
|
|
|
|
macro_rules! inner_option_type {
|
|
(Option<$type:ty>) => (
|
|
$type
|
|
)
|
|
}
|
|
|
|
macro_rules! inner_vec_type {
|
|
(Vec<$type:ty>) => (
|
|
$type
|
|
)
|
|
}
|
|
|
|
macro_rules! inner_option_vec_type {
|
|
(Option<Vec<String>>) => (
|
|
String
|
|
)
|
|
}
|
|
|
|
macro_rules! usage_with_ident {
|
|
($name:expr, $usage:expr, $help:expr) => (
|
|
if $usage.contains("<") {
|
|
format!("<{}> {} '{}'",$name, $usage, $help)
|
|
} else {
|
|
format!("[{}] {} '{}'",$name, $usage, $help)
|
|
}
|
|
);
|
|
}
|
|
|
|
macro_rules! underscore_to_hyphen {
|
|
($e:expr) => (
|
|
str::replace($e, "_", "-")
|
|
)
|
|
}
|
|
|
|
macro_rules! usage {
|
|
(
|
|
{
|
|
$(
|
|
CMD $subc:ident
|
|
{
|
|
$subc_help:expr,
|
|
|
|
$(
|
|
CMD $subc_subc:ident
|
|
{
|
|
$subc_subc_help:expr,
|
|
$(
|
|
FLAG $subc_subc_flag:ident : (bool) = false, $subc_subc_flag_usage:expr, $subc_subc_flag_help:expr,
|
|
)*
|
|
$(
|
|
ARG $subc_subc_arg:ident : ($($subc_subc_arg_type_tt:tt)+) = $subc_subc_arg_default:expr, $subc_subc_arg_usage:expr, $subc_subc_arg_help:expr,
|
|
)*
|
|
}
|
|
)*
|
|
|
|
$(
|
|
FLAG $subc_flag:ident : (bool) = false, $subc_flag_usage:expr, $subc_flag_help:expr,
|
|
)*
|
|
$(
|
|
ARG $subc_arg:ident : ($($subc_arg_type_tt:tt)+) = $subc_arg_default:expr, $subc_arg_usage:expr, $subc_arg_help:expr,
|
|
)*
|
|
}
|
|
)*
|
|
}
|
|
{
|
|
$(
|
|
[$group_name:expr]
|
|
$(
|
|
FLAG $flag:ident : (bool) = false, or $flag_from_config:expr, $flag_usage:expr, $flag_help:expr,
|
|
)*
|
|
$(
|
|
ARG $arg:ident : ($($arg_type_tt:tt)+) = $arg_default:expr, or $arg_from_config:expr, $arg_usage:expr, $arg_help:expr,
|
|
)*
|
|
$(
|
|
CHECK $check:expr,
|
|
)*
|
|
)*
|
|
}
|
|
) => {
|
|
use toml;
|
|
use std::{fs, io, process, cmp};
|
|
use std::io::Read;
|
|
use parity_version::version;
|
|
use clap::{Arg, App, SubCommand, AppSettings, ArgSettings, Error as ClapError, ErrorKind as ClapErrorKind};
|
|
use dir::helpers::replace_home;
|
|
use std::ffi::OsStr;
|
|
use std::collections::HashMap;
|
|
|
|
extern crate textwrap;
|
|
extern crate term_size;
|
|
use self::textwrap::{Wrapper};
|
|
|
|
const MAX_TERM_WIDTH: usize = 120;
|
|
|
|
#[cfg(test)]
|
|
use regex::Regex;
|
|
|
|
#[derive(Debug)]
|
|
pub enum ArgsError {
|
|
Clap(ClapError),
|
|
Decode(toml::de::Error),
|
|
Config(String, io::Error),
|
|
PeerConfiguration,
|
|
}
|
|
|
|
impl ArgsError {
|
|
pub fn exit(self) -> ! {
|
|
match self {
|
|
ArgsError::Clap(e) => e.exit(),
|
|
ArgsError::Decode(e) => {
|
|
eprintln!("You might have supplied invalid parameters in config file.");
|
|
eprintln!("{}", e);
|
|
process::exit(2)
|
|
},
|
|
ArgsError::Config(path, e) => {
|
|
eprintln!("There was an error reading your config file at: {}", path);
|
|
eprintln!("{}", e);
|
|
process::exit(2)
|
|
},
|
|
ArgsError::PeerConfiguration => {
|
|
eprintln!("You have supplied `min_peers` > `max_peers`");
|
|
process::exit(2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ClapError> for ArgsError {
|
|
fn from(e: ClapError) -> Self {
|
|
ArgsError::Clap(e)
|
|
}
|
|
}
|
|
|
|
impl From<toml::de::Error> for ArgsError {
|
|
fn from(e: toml::de::Error) -> Self {
|
|
ArgsError::Decode(e)
|
|
}
|
|
}
|
|
|
|
/// Parsed command line arguments.
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct Args {
|
|
$(
|
|
pub $subc: bool,
|
|
|
|
$(
|
|
pub $subc_subc: bool,
|
|
$(
|
|
pub $subc_subc_flag: bool,
|
|
)*
|
|
$(
|
|
pub $subc_subc_arg: $($subc_subc_arg_type_tt)+,
|
|
)*
|
|
)*
|
|
|
|
$(
|
|
pub $subc_flag: bool,
|
|
)*
|
|
$(
|
|
pub $subc_arg: $($subc_arg_type_tt)+,
|
|
)*
|
|
)*
|
|
|
|
$(
|
|
$(
|
|
pub $flag: bool,
|
|
)*
|
|
$(
|
|
pub $arg: $($arg_type_tt)+,
|
|
)*
|
|
)*
|
|
}
|
|
|
|
impl Default for Args {
|
|
fn default() -> Self {
|
|
Args {
|
|
$(
|
|
$subc: Default::default(),
|
|
$(
|
|
$subc_subc: Default::default(),
|
|
$(
|
|
$subc_subc_flag: Default::default(),
|
|
)*
|
|
$(
|
|
$subc_subc_arg: Default::default(),
|
|
)*
|
|
)*
|
|
|
|
$(
|
|
$subc_flag: Default::default(),
|
|
)*
|
|
$(
|
|
$subc_arg: Default::default(),
|
|
)*
|
|
)*
|
|
|
|
$(
|
|
$(
|
|
$flag: Default::default(),
|
|
)*
|
|
$(
|
|
$arg: Default::default(),
|
|
)*
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, PartialEq, Clone, Deserialize)]
|
|
struct RawArgs {
|
|
$(
|
|
$subc: bool,
|
|
|
|
$(
|
|
$subc_subc: bool,
|
|
$(
|
|
$subc_subc_flag: bool,
|
|
)*
|
|
$(
|
|
$subc_subc_arg: if_option!(
|
|
$($subc_subc_arg_type_tt)+,
|
|
THEN { $($subc_subc_arg_type_tt)+ }
|
|
ELSE { Option<$($subc_subc_arg_type_tt)+> }
|
|
),
|
|
)*
|
|
)*
|
|
|
|
$(
|
|
$subc_flag: bool,
|
|
)*
|
|
$(
|
|
$subc_arg: if_option!(
|
|
$($subc_arg_type_tt)+,
|
|
THEN { $($subc_arg_type_tt)+ }
|
|
ELSE { Option<$($subc_arg_type_tt)+> }
|
|
),
|
|
)*
|
|
|
|
)*
|
|
$(
|
|
$(
|
|
$flag: bool,
|
|
)*
|
|
|
|
$(
|
|
$arg: if_option!(
|
|
$($arg_type_tt)+,
|
|
THEN { $($arg_type_tt)+ }
|
|
ELSE { Option<$($arg_type_tt)+> }
|
|
),
|
|
)*
|
|
)*
|
|
}
|
|
|
|
impl Args {
|
|
|
|
pub fn parse<S: AsRef<str>>(command: &[S]) -> Result<Self, ArgsError> {
|
|
let raw_args = RawArgs::parse(command)?;
|
|
|
|
// Skip loading config file if no_config flag is specified
|
|
if raw_args.flag_no_config {
|
|
return Ok(raw_args.into_args(Config::default()));
|
|
}
|
|
|
|
let config_file = raw_args.arg_config.clone().unwrap_or_else(|| raw_args.clone().into_args(Config::default()).arg_config);
|
|
let config_file = replace_home(&::dir::default_data_path(), &config_file);
|
|
|
|
let args = match (fs::File::open(&config_file), raw_args.arg_config.clone()) {
|
|
// Load config file
|
|
(Ok(mut file), _) => {
|
|
eprintln!("Loading config file from {}", &config_file);
|
|
let mut config = String::new();
|
|
file.read_to_string(&mut config).map_err(|e| ArgsError::Config(config_file, e))?;
|
|
Ok(raw_args.into_args(Self::parse_config(&config)?))
|
|
},
|
|
// Don't display error in case default config cannot be loaded.
|
|
(Err(_), None) => Ok(raw_args.into_args(Config::default())),
|
|
// Config set from CLI (fail with error)
|
|
(Err(_), Some(ref config_arg)) => {
|
|
match presets::preset_config_string(config_arg) {
|
|
Ok(s) => Ok(raw_args.into_args(Self::parse_config(&s)?)),
|
|
Err(e) => Err(ArgsError::Config(config_file, e))
|
|
}
|
|
},
|
|
}?;
|
|
|
|
$(
|
|
$(
|
|
$check(&args)?;
|
|
)*
|
|
)*
|
|
|
|
Ok(args)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn parse_without_config<S: AsRef<str>>(command: &[S]) -> Result<Self, ArgsError> {
|
|
Self::parse_with_config(command, Config::default())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn parse_with_config<S: AsRef<str>>(command: &[S], config: Config) -> Result<Self, ArgsError> {
|
|
RawArgs::parse(command).map(|raw| raw.into_args(config)).map_err(ArgsError::Clap)
|
|
}
|
|
|
|
fn parse_config(config: &str) -> Result<Config, ArgsError> {
|
|
Ok(toml::from_str(config)?)
|
|
}
|
|
|
|
pub fn print_version() -> String {
|
|
format!(include_str!("./version.txt"), version())
|
|
}
|
|
|
|
#[allow(unused_mut)] // subc_subc_exist may be assigned true by the macro
|
|
#[allow(unused_assignments)] // Rust issue #22630
|
|
pub fn print_help() -> String {
|
|
|
|
const TAB: &str = " ";
|
|
const TAB_TAB: &str = " ";
|
|
|
|
let term_width = match term_size::dimensions() {
|
|
None => MAX_TERM_WIDTH,
|
|
Some((w, _)) => {
|
|
cmp::min(w, MAX_TERM_WIDTH)
|
|
}
|
|
};
|
|
|
|
let mut help : String = include_str!("./usage_header.txt").to_owned();
|
|
|
|
help.push_str("\n");
|
|
|
|
// Subcommands
|
|
let mut subcommands_wrapper = Wrapper::new(term_width).subsequent_indent(TAB);
|
|
help.push_str("parity [options]\n");
|
|
$(
|
|
{
|
|
let mut subc_subc_exist = false;
|
|
|
|
$(
|
|
subc_subc_exist = true;
|
|
let subc_subc_usages : Vec<&str> = vec![
|
|
$(
|
|
concat!("[",$subc_subc_flag_usage,"]"),
|
|
)*
|
|
$(
|
|
$subc_subc_arg_usage,
|
|
)*
|
|
];
|
|
|
|
help.push_str(&subcommands_wrapper.fill(
|
|
format!(
|
|
"parity [options] {} {} {}\n",
|
|
underscore_to_hyphen!(&stringify!($subc)[4..]),
|
|
underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]),
|
|
subc_subc_usages.join(" ")
|
|
).as_ref())
|
|
);
|
|
)*
|
|
|
|
// Print the subcommand on its own only if it has no subsubcommands
|
|
if !subc_subc_exist {
|
|
let subc_usages : Vec<&str> = vec![
|
|
$(
|
|
concat!("[",$subc_flag_usage,"]"),
|
|
)*
|
|
$(
|
|
$subc_arg_usage,
|
|
)*
|
|
];
|
|
|
|
help.push_str(&subcommands_wrapper.fill(
|
|
format!(
|
|
"parity [options] {} {}\n",
|
|
underscore_to_hyphen!(&stringify!($subc)[4..]),
|
|
subc_usages.join(" ")
|
|
).as_ref())
|
|
);
|
|
}
|
|
}
|
|
)*
|
|
|
|
help.push_str("\n");
|
|
|
|
// Arguments and flags
|
|
let args_wrapper = Wrapper::new(term_width).initial_indent(TAB_TAB).subsequent_indent(TAB_TAB);
|
|
$(
|
|
if $group_name != "Legacy Options" {
|
|
help.push_str($group_name); help.push_str(":\n");
|
|
$(
|
|
help.push_str(&format!("{}{}\n{}\n",
|
|
TAB, $flag_usage,
|
|
args_wrapper.fill($flag_help)
|
|
));
|
|
help.push_str("\n");
|
|
)*
|
|
$(
|
|
if_option!(
|
|
$($arg_type_tt)+,
|
|
THEN {
|
|
if_option_vec!(
|
|
$($arg_type_tt)+,
|
|
THEN {
|
|
help.push_str(&format!("{}{}\n{}\n",
|
|
TAB, $arg_usage,
|
|
args_wrapper.fill(format!(
|
|
"{} (default: {:?})",
|
|
$arg_help,
|
|
{let x : inner_option_type!($($arg_type_tt)+)> = $arg_default; x}
|
|
).as_ref())
|
|
))
|
|
}
|
|
ELSE {
|
|
help.push_str(&format!("{}{}\n{}\n",
|
|
TAB, $arg_usage,
|
|
args_wrapper.fill(format!(
|
|
"{}{}",
|
|
$arg_help,
|
|
$arg_default.map(|x: inner_option_type!($($arg_type_tt)+)| format!(" (default: {})",x)).unwrap_or("".to_owned())
|
|
).as_ref())
|
|
))
|
|
}
|
|
)
|
|
}
|
|
ELSE {
|
|
if_vec!(
|
|
$($arg_type_tt)+,
|
|
THEN {
|
|
help.push_str(&format!("{}{}\n{}\n", TAB, $arg_usage,
|
|
args_wrapper.fill(format!(
|
|
"{} (default: {:?})",
|
|
$arg_help,
|
|
{let x : $($arg_type_tt)+ = $arg_default; x}
|
|
).as_ref())
|
|
))
|
|
}
|
|
ELSE {
|
|
help.push_str(&format!("{}{}\n{}\n", TAB, $arg_usage,
|
|
args_wrapper.fill(format!(
|
|
"{} (default: {})",
|
|
$arg_help,
|
|
$arg_default
|
|
).as_ref())
|
|
))
|
|
}
|
|
)
|
|
}
|
|
);
|
|
help.push_str("\n");
|
|
)*
|
|
}
|
|
)*
|
|
help
|
|
}
|
|
}
|
|
|
|
impl RawArgs {
|
|
fn into_args(self, config: Config) -> Args {
|
|
let mut args = Args::default();
|
|
$(
|
|
args.$subc = self.$subc;
|
|
|
|
$(
|
|
args.$subc_subc = self.$subc_subc;
|
|
$(
|
|
args.$subc_subc_flag = self.$subc_subc_flag;
|
|
)*
|
|
$(
|
|
args.$subc_subc_arg = if_option!(
|
|
$($subc_subc_arg_type_tt)+,
|
|
THEN { self.$subc_subc_arg.or($subc_subc_arg_default) }
|
|
ELSE { self.$subc_subc_arg.unwrap_or($subc_subc_arg_default.into()) }
|
|
);
|
|
)*
|
|
)*
|
|
|
|
$(
|
|
args.$subc_flag = self.$subc_flag;
|
|
)*
|
|
$(
|
|
args.$subc_arg = if_option!(
|
|
$($subc_arg_type_tt)+,
|
|
THEN { self.$subc_arg.or($subc_arg_default) }
|
|
ELSE { self.$subc_arg.unwrap_or($subc_arg_default.into()) }
|
|
);
|
|
)*
|
|
)*
|
|
|
|
$(
|
|
$(
|
|
args.$flag = self.$flag || $flag_from_config(&config).unwrap_or(false);
|
|
)*
|
|
$(
|
|
args.$arg = if_option!(
|
|
$($arg_type_tt)+,
|
|
THEN { self.$arg.or_else(|| $arg_from_config(&config)).or_else(|| $arg_default.into()) }
|
|
ELSE { self.$arg.or_else(|| $arg_from_config(&config)).unwrap_or_else(|| $arg_default.into()) }
|
|
);
|
|
)*
|
|
)*
|
|
args
|
|
}
|
|
|
|
#[allow(unused_variables)] // the submatches of arg-less subcommands aren't used
|
|
pub fn parse<S: AsRef<str>>(command: &[S]) -> Result<Self, ClapError> {
|
|
|
|
let usages = vec![
|
|
$(
|
|
$(
|
|
usage_with_ident!(stringify!($arg), $arg_usage, $arg_help),
|
|
)*
|
|
$(
|
|
usage_with_ident!(stringify!($flag), $flag_usage, $flag_help),
|
|
)*
|
|
)*
|
|
];
|
|
|
|
// Hash of subc|subc_subc => Vec<String>
|
|
let mut subc_usages = HashMap::new();
|
|
$(
|
|
{
|
|
let this_subc_usages = vec![
|
|
$(
|
|
usage_with_ident!(stringify!($subc_flag), $subc_flag_usage, $subc_flag_help),
|
|
)*
|
|
$(
|
|
usage_with_ident!(stringify!($subc_arg), $subc_arg_usage, $subc_arg_help),
|
|
)*
|
|
];
|
|
|
|
subc_usages.insert(stringify!($subc),this_subc_usages);
|
|
|
|
$(
|
|
{
|
|
let this_subc_subc_usages = vec![
|
|
$(
|
|
usage_with_ident!(stringify!($subc_subc_flag), $subc_subc_flag_usage, $subc_subc_flag_help),
|
|
)*
|
|
$(
|
|
usage_with_ident!(stringify!($subc_subc_arg), $subc_subc_arg_usage, $subc_subc_arg_help),
|
|
)*
|
|
];
|
|
|
|
subc_usages.insert(stringify!($subc_subc), this_subc_subc_usages);
|
|
}
|
|
)*
|
|
}
|
|
)*
|
|
|
|
let matches = App::new("Parity")
|
|
.global_setting(AppSettings::VersionlessSubcommands)
|
|
.global_setting(AppSettings::DisableHelpSubcommand)
|
|
.max_term_width(MAX_TERM_WIDTH)
|
|
.help(Args::print_help().as_ref())
|
|
.args(&usages.iter().map(|u| {
|
|
let mut arg = Arg::from_usage(u)
|
|
.allow_hyphen_values(true) // Allow for example --allow-ips -10.0.0.0/8
|
|
.global(true) // Argument doesn't have to come before the first subcommand
|
|
.hidden(true); // Hide global arguments from the (subcommand) help messages generated by Clap
|
|
|
|
if arg.is_set(ArgSettings::Multiple) {
|
|
arg = arg.require_delimiter(true); // Multiple values can only be separated by commas, not spaces (#7428)
|
|
}
|
|
|
|
arg
|
|
}).collect::<Vec<Arg>>())
|
|
$(
|
|
.subcommand(
|
|
SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc)[4..]))
|
|
.about($subc_help)
|
|
.args(&subc_usages.get(stringify!($subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>())
|
|
$(
|
|
.setting(AppSettings::SubcommandRequired) // prevent from running `parity account`
|
|
.subcommand(
|
|
SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]))
|
|
.about($subc_subc_help)
|
|
.args(&subc_usages.get(stringify!($subc_subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>())
|
|
)
|
|
)*
|
|
)
|
|
)*
|
|
.get_matches_from_safe(command.iter().map(|x| OsStr::new(x.as_ref())))?;
|
|
|
|
let mut raw_args : RawArgs = Default::default();
|
|
|
|
// Globals
|
|
$(
|
|
$(
|
|
raw_args.$flag = raw_args.$flag || matches.is_present(stringify!($flag));
|
|
)*
|
|
$(
|
|
if let some @ Some(_) = return_if_parse_error!(if_option!(
|
|
$($arg_type_tt)+,
|
|
THEN {
|
|
if_option_vec!(
|
|
$($arg_type_tt)+,
|
|
THEN { values_t!(matches, stringify!($arg), inner_option_vec_type!($($arg_type_tt)+)) }
|
|
ELSE { value_t!(matches, stringify!($arg), inner_option_type!($($arg_type_tt)+)) }
|
|
)
|
|
}
|
|
ELSE {
|
|
if_vec!(
|
|
$($arg_type_tt)+,
|
|
THEN { values_t!(matches, stringify!($arg), inner_vec_type!($($arg_type_tt)+)) }
|
|
ELSE { value_t!(matches, stringify!($arg), $($arg_type_tt)+) }
|
|
)
|
|
}
|
|
)) {
|
|
raw_args.$arg = some;
|
|
}
|
|
)*
|
|
)*
|
|
|
|
// Subcommands
|
|
$(
|
|
if let Some(submatches) = matches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc)[4..])) {
|
|
raw_args.$subc = true;
|
|
|
|
// Subcommand flags
|
|
$(
|
|
raw_args.$subc_flag = submatches.is_present(&stringify!($subc_flag));
|
|
)*
|
|
// Subcommand arguments
|
|
$(
|
|
raw_args.$subc_arg = return_if_parse_error!(if_option!(
|
|
$($subc_arg_type_tt)+,
|
|
THEN {
|
|
if_option_vec!(
|
|
$($subc_arg_type_tt)+,
|
|
THEN { values_t!(submatches, stringify!($subc_arg), inner_option_vec_type!($($subc_arg_type_tt)+)) }
|
|
ELSE { value_t!(submatches, stringify!($subc_arg), inner_option_type!($($subc_arg_type_tt)+)) }
|
|
)
|
|
}
|
|
ELSE {
|
|
if_vec!(
|
|
$($subc_arg_type_tt)+,
|
|
THEN { values_t!(submatches, stringify!($subc_arg), inner_vec_type!($($subc_arg_type_tt)+)) }
|
|
ELSE { value_t!(submatches, stringify!($subc_arg), $($subc_arg_type_tt)+) }
|
|
)
|
|
}
|
|
));
|
|
)*
|
|
// Sub-subcommands
|
|
$(
|
|
if let Some(subsubmatches) = submatches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..])) {
|
|
raw_args.$subc_subc = true;
|
|
|
|
// Sub-subcommand flags
|
|
$(
|
|
raw_args.$subc_subc_flag = subsubmatches.is_present(&stringify!($subc_subc_flag));
|
|
)*
|
|
// Sub-subcommand arguments
|
|
$(
|
|
raw_args.$subc_subc_arg = return_if_parse_error!(if_option!(
|
|
$($subc_subc_arg_type_tt)+,
|
|
THEN {
|
|
if_option_vec!(
|
|
$($subc_subc_arg_type_tt)+,
|
|
THEN { values_t!(subsubmatches, stringify!($subc_subc_arg), inner_option_vec_type!($($subc_subc_arg_type_tt)+)) }
|
|
ELSE { value_t!(subsubmatches, stringify!($subc_subc_arg), inner_option_type!($($subc_subc_arg_type_tt)+)) }
|
|
)
|
|
}
|
|
ELSE {
|
|
if_vec!(
|
|
$($subc_subc_arg_type_tt)+,
|
|
THEN { values_t!(subsubmatches, stringify!($subc_subc_arg), inner_vec_type!($($subc_subc_arg_type_tt)+)) }
|
|
ELSE { value_t!(subsubmatches, stringify!($subc_subc_arg), $($subc_subc_arg_type_tt)+) }
|
|
)
|
|
}
|
|
));
|
|
)*
|
|
}
|
|
else {
|
|
raw_args.$subc_subc = false;
|
|
}
|
|
)*
|
|
}
|
|
else {
|
|
raw_args.$subc = false;
|
|
}
|
|
)*
|
|
|
|
Ok(raw_args)
|
|
}
|
|
|
|
}
|
|
|
|
#[test]
|
|
fn usages_valid() {
|
|
let re = Regex::new(r"^(?:(-[a-zA-Z-]+, )?--[a-z-]+(=\[[a-zA-Z]+\](\.\.\.)?|=<[a-zA-Z]+>(\.\.\.)?)?)|(?:\[[a-zA-Z-]+\])(\.\.\.)?|(?:<[a-zA-Z-]+>)(\.\.\.)?$").unwrap();
|
|
|
|
let usages = vec![
|
|
$(
|
|
$(
|
|
$(
|
|
$subc_subc_arg_usage,
|
|
)*
|
|
)*
|
|
$(
|
|
$subc_arg_usage,
|
|
)*
|
|
)*
|
|
$(
|
|
$(
|
|
$flag_usage,
|
|
)*
|
|
$(
|
|
$arg_usage,
|
|
)*
|
|
)*
|
|
];
|
|
|
|
for usage in &usages {
|
|
assert!(re.is_match(usage));
|
|
}
|
|
}
|
|
}
|
|
}
|