// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of OpenEthereum. // OpenEthereum 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. // OpenEthereum 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 OpenEthereum. If not, see . 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>, 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>) => { 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 for ArgsError { fn from(e: ClapError) -> Self { ArgsError::Clap(e) } } impl From 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>(command: &[S]) -> Result { 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>(command: &[S]) -> Result { Self::parse_with_config(command, Config::default()) } #[cfg(test)] fn parse_with_config>(command: &[S], config: Config) -> Result { RawArgs::parse(command).map(|raw| raw.into_args(config)).map_err(ArgsError::Clap) } fn parse_config(config: &str) -> Result { 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("openethereum [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!( "openethereum [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!( "openethereum [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>(command: &[S]) -> Result { 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 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("OpenEthereum") .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::>()) $( .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::>()) $( .setting(AppSettings::SubcommandRequired) // prevent from running `openethereum 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::>()) ) )* ) )* .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)); } } } }