diff --git a/src/build/app/mod.rs b/src/build/app.rs similarity index 99% rename from src/build/app/mod.rs rename to src/build/app.rs index b63621eb250..77c87ebd413 100644 --- a/src/build/app/mod.rs +++ b/src/build/app.rs @@ -1,11 +1,3 @@ -#[cfg(debug_assertions)] -mod debug_asserts; -mod settings; -#[cfg(test)] -mod tests; - -pub use self::settings::{AppFlags, AppSettings}; - // Std use std::{ collections::HashMap, @@ -23,6 +15,7 @@ use os_str_bytes::RawOsStr; use yaml_rust::Yaml; // Internal +use crate::build::app_settings::{AppFlags, AppSettings}; use crate::build::{arg::ArgProvider, Arg, ArgGroup, ArgPredicate}; use crate::error::ErrorKind; use crate::error::Result as ClapResult; @@ -32,6 +25,9 @@ use crate::parse::{ArgMatcher, ArgMatches, Input, Parser}; use crate::util::{color::ColorChoice, Id, Key}; use crate::{Error, INTERNAL_ERROR_MSG}; +#[cfg(debug_assertions)] +use crate::build::debug_asserts::assert_app; + /// Build a command-line interface. /// /// This includes defining arguments, subcommands, parser behavior, and help output. @@ -2802,14 +2798,14 @@ impl<'help> App<'help> { self.args._build(); #[cfg(debug_assertions)] - self::debug_asserts::assert_app(self); + assert_app(self); self.settings.set(AppSettings::Built); } else { debug!("App::_build: already built"); } } - fn _panic_on_missing_help(&self, help_required_globally: bool) { + pub(crate) fn _panic_on_missing_help(&self, help_required_globally: bool) { if self.is_set(AppSettings::HelpExpected) || help_required_globally { let args_missing_help: Vec = self .args @@ -2831,7 +2827,7 @@ impl<'help> App<'help> { } #[cfg(debug_assertions)] - fn two_args_of(&self, condition: F) -> Option<(&Arg<'help>, &Arg<'help>)> + pub(crate) fn two_args_of(&self, condition: F) -> Option<(&Arg<'help>, &Arg<'help>)> where F: Fn(&Arg) -> bool, { diff --git a/src/build/app/settings.rs b/src/build/app_settings.rs similarity index 100% rename from src/build/app/settings.rs rename to src/build/app_settings.rs diff --git a/src/build/app/tests.rs b/src/build/app_tests.rs similarity index 100% rename from src/build/app/tests.rs rename to src/build/app_tests.rs diff --git a/src/build/arg/mod.rs b/src/build/arg.rs similarity index 99% rename from src/build/arg/mod.rs rename to src/build/arg.rs index 0de3dba9a5c..fd0f81a60d1 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg.rs @@ -1,17 +1,3 @@ -mod arg_predicate; -#[cfg(debug_assertions)] -pub mod debug_asserts; -mod possible_value; -mod settings; -#[cfg(test)] -mod tests; -mod value_hint; - -pub use self::possible_value::PossibleValue; -pub use self::settings::{ArgFlags, ArgSettings}; -pub use self::value_hint::ValueHint; -pub(crate) use arg_predicate::ArgPredicate; - // Std use std::{ borrow::Cow, @@ -29,17 +15,16 @@ use std::{env, ffi::OsString}; use yaml_rust::Yaml; // Internal -use crate::{ - build::usage_parser::UsageParser, - util::{Id, Key}, - INTERNAL_ERROR_MSG, -}; - -#[cfg(feature = "regex")] -mod regex; +use crate::build::usage_parser::UsageParser; +use crate::build::ArgPredicate; +use crate::util::{Id, Key}; +use crate::PossibleValue; +use crate::ValueHint; +use crate::INTERNAL_ERROR_MSG; +use crate::{ArgFlags, ArgSettings}; #[cfg(feature = "regex")] -pub use self::regex::RegexRef; +use crate::build::RegexRef; /// The abstract representation of a command line argument. Used to set all the options and /// relationships that define a valid argument for the program. diff --git a/src/build/arg/debug_asserts.rs b/src/build/arg/debug_asserts.rs deleted file mode 100644 index e4e1e843153..00000000000 --- a/src/build/arg/debug_asserts.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{Arg, ValueHint}; - -pub(crate) fn assert_arg(arg: &Arg) { - debug!("Arg::_debug_asserts:{}", arg.name); - - // Self conflict - // TODO: this check should be recursive - assert!( - !arg.blacklist.iter().any(|x| *x == arg.id), - "Argument '{}' cannot conflict with itself", - arg.name, - ); - - if arg.value_hint != ValueHint::Unknown { - assert!( - arg.is_takes_value_set(), - "Argument '{}' has value hint but takes no value", - arg.name - ); - - if arg.value_hint == ValueHint::CommandWithArguments { - assert!( - arg.is_multiple_values_set(), - "Argument '{}' uses hint CommandWithArguments and must accept multiple values", - arg.name - ) - } - } - - if arg.index.is_some() { - assert!( - arg.is_positional(), - "Argument '{}' is a positional argument and can't have short or long name versions", - arg.name - ); - } - - if arg.is_required_set() { - assert!( - arg.default_vals.is_empty(), - "Argument '{}' is required and can't have a default value", - arg.name - ); - } - - assert_arg_flags(arg); - - assert_defaults(arg, "default_value", arg.default_vals.iter().copied()); - assert_defaults( - arg, - "default_missing_value", - arg.default_missing_vals.iter().copied(), - ); - assert_defaults( - arg, - "default_value_if", - arg.default_vals_ifs - .iter() - .filter_map(|(_, _, default)| *default), - ); -} - -fn assert_arg_flags(arg: &Arg) { - macro_rules! checker { - ($a:ident requires $($b:ident)|+) => { - if arg.$a() { - let mut s = String::new(); - - $( - if !arg.$b() { - s.push_str(&format!(" Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a))); - } - )+ - - if !s.is_empty() { - panic!("Argument {:?}\n{}", arg.get_name(), s) - } - } - } - } - - checker!(is_forbid_empty_values_set requires is_takes_value_set); - checker!(is_require_value_delimiter_set requires is_takes_value_set); - checker!(is_require_value_delimiter_set requires is_use_value_delimiter_set); - checker!(is_hide_possible_values_set requires is_takes_value_set); - checker!(is_allow_hyphen_values_set requires is_takes_value_set); - checker!(is_require_equals_set requires is_takes_value_set); - checker!(is_last_set requires is_takes_value_set); - checker!(is_hide_default_value_set requires is_takes_value_set); - checker!(is_multiple_values_set requires is_takes_value_set); - checker!(is_ignore_case_set requires is_takes_value_set); - checker!(is_allow_invalid_utf8_set requires is_takes_value_set); -} - -fn assert_defaults<'d>( - arg: &Arg, - field: &'static str, - defaults: impl IntoIterator, -) { - for default_os in defaults { - if let Some(default_s) = default_os.to_str() { - if !arg.possible_vals.is_empty() { - assert!( - arg.possible_vals.iter().any(|possible_val| { - possible_val.matches(default_s, arg.is_ignore_case_set()) - }), - "Argument `{}`'s {}={} doesn't match possible values", - arg.name, - field, - default_s - ); - } - - if let Some(validator) = arg.validator.as_ref() { - let mut validator = validator.lock().unwrap(); - if let Err(err) = validator(default_s) { - panic!( - "Argument `{}`'s {}={} failed validation: {}", - arg.name, field, default_s, err - ); - } - } - } - - if let Some(validator) = arg.validator_os.as_ref() { - let mut validator = validator.lock().unwrap(); - if let Err(err) = validator(default_os) { - panic!( - "Argument `{}`'s {}={:?} failed validation: {}", - arg.name, field, default_os, err - ); - } - } - } -} diff --git a/src/build/arg/arg_predicate.rs b/src/build/arg_predicate.rs similarity index 100% rename from src/build/arg/arg_predicate.rs rename to src/build/arg_predicate.rs diff --git a/src/build/arg/settings.rs b/src/build/arg_settings.rs similarity index 100% rename from src/build/arg/settings.rs rename to src/build/arg_settings.rs diff --git a/src/build/arg/tests.rs b/src/build/arg_tests.rs similarity index 100% rename from src/build/arg/tests.rs rename to src/build/arg_tests.rs diff --git a/src/build/app/debug_asserts.rs b/src/build/debug_asserts.rs similarity index 81% rename from src/build/app/debug_asserts.rs rename to src/build/debug_asserts.rs index c3d7a5654d8..185037caea7 100644 --- a/src/build/app/debug_asserts.rs +++ b/src/build/debug_asserts.rs @@ -1,11 +1,10 @@ -use crate::{ - build::arg::{debug_asserts::assert_arg, ArgProvider}, - mkeymap::KeyType, - util::Id, - App, AppSettings, Arg, ValueHint, -}; use std::cmp::Ordering; +use crate::build::arg::ArgProvider; +use crate::mkeymap::KeyType; +use crate::util::Id; +use crate::{App, AppSettings, Arg, ValueHint}; + pub(crate) fn assert_app(app: &App) { debug!("App::_debug_asserts"); @@ -577,3 +576,137 @@ fn _verify_positionals(app: &App) -> bool { true } + +fn assert_arg(arg: &Arg) { + debug!("Arg::_debug_asserts:{}", arg.name); + + // Self conflict + // TODO: this check should be recursive + assert!( + !arg.blacklist.iter().any(|x| *x == arg.id), + "Argument '{}' cannot conflict with itself", + arg.name, + ); + + if arg.value_hint != ValueHint::Unknown { + assert!( + arg.is_takes_value_set(), + "Argument '{}' has value hint but takes no value", + arg.name + ); + + if arg.value_hint == ValueHint::CommandWithArguments { + assert!( + arg.is_multiple_values_set(), + "Argument '{}' uses hint CommandWithArguments and must accept multiple values", + arg.name + ) + } + } + + if arg.index.is_some() { + assert!( + arg.is_positional(), + "Argument '{}' is a positional argument and can't have short or long name versions", + arg.name + ); + } + + if arg.is_required_set() { + assert!( + arg.default_vals.is_empty(), + "Argument '{}' is required and can't have a default value", + arg.name + ); + } + + assert_arg_flags(arg); + + assert_defaults(arg, "default_value", arg.default_vals.iter().copied()); + assert_defaults( + arg, + "default_missing_value", + arg.default_missing_vals.iter().copied(), + ); + assert_defaults( + arg, + "default_value_if", + arg.default_vals_ifs + .iter() + .filter_map(|(_, _, default)| *default), + ); +} + +fn assert_arg_flags(arg: &Arg) { + macro_rules! checker { + ($a:ident requires $($b:ident)|+) => { + if arg.$a() { + let mut s = String::new(); + + $( + if !arg.$b() { + s.push_str(&format!(" Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a))); + } + )+ + + if !s.is_empty() { + panic!("Argument {:?}\n{}", arg.get_name(), s) + } + } + } + } + + checker!(is_forbid_empty_values_set requires is_takes_value_set); + checker!(is_require_value_delimiter_set requires is_takes_value_set); + checker!(is_require_value_delimiter_set requires is_use_value_delimiter_set); + checker!(is_hide_possible_values_set requires is_takes_value_set); + checker!(is_allow_hyphen_values_set requires is_takes_value_set); + checker!(is_require_equals_set requires is_takes_value_set); + checker!(is_last_set requires is_takes_value_set); + checker!(is_hide_default_value_set requires is_takes_value_set); + checker!(is_multiple_values_set requires is_takes_value_set); + checker!(is_ignore_case_set requires is_takes_value_set); + checker!(is_allow_invalid_utf8_set requires is_takes_value_set); +} + +fn assert_defaults<'d>( + arg: &Arg, + field: &'static str, + defaults: impl IntoIterator, +) { + for default_os in defaults { + if let Some(default_s) = default_os.to_str() { + if !arg.possible_vals.is_empty() { + assert!( + arg.possible_vals.iter().any(|possible_val| { + possible_val.matches(default_s, arg.is_ignore_case_set()) + }), + "Argument `{}`'s {}={} doesn't match possible values", + arg.name, + field, + default_s + ); + } + + if let Some(validator) = arg.validator.as_ref() { + let mut validator = validator.lock().unwrap(); + if let Err(err) = validator(default_s) { + panic!( + "Argument `{}`'s {}={} failed validation: {}", + arg.name, field, default_s, err + ); + } + } + } + + if let Some(validator) = arg.validator_os.as_ref() { + let mut validator = validator.lock().unwrap(); + if let Err(err) = validator(default_os) { + panic!( + "Argument `{}`'s {}={:?} failed validation: {}", + arg.name, field, default_os, err + ); + } + } + } +} diff --git a/src/build/mod.rs b/src/build/mod.rs index 3750281069f..06ccb7a8930 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -4,12 +4,33 @@ mod macros; pub mod app; pub mod arg; +mod app_settings; mod arg_group; +mod arg_predicate; +mod arg_settings; +mod possible_value; mod usage_parser; +mod value_hint; -pub use self::{ - app::{App, AppFlags, AppSettings}, - arg::{Arg, ArgFlags, ArgSettings, PossibleValue, ValueHint}, - arg_group::ArgGroup, -}; -pub(crate) use arg::ArgPredicate; +#[cfg(feature = "regex")] +mod regex; + +#[cfg(debug_assertions)] +mod debug_asserts; + +#[cfg(test)] +mod app_tests; +#[cfg(test)] +mod arg_tests; + +pub use app::App; +pub use app_settings::{AppFlags, AppSettings}; +pub use arg::Arg; +pub use arg_group::ArgGroup; +pub(crate) use arg_predicate::ArgPredicate; +pub use arg_settings::{ArgFlags, ArgSettings}; +pub use possible_value::PossibleValue; +pub use value_hint::ValueHint; + +#[cfg(feature = "regex")] +pub use self::regex::RegexRef; diff --git a/src/build/arg/possible_value.rs b/src/build/possible_value.rs similarity index 100% rename from src/build/arg/possible_value.rs rename to src/build/possible_value.rs diff --git a/src/build/arg/regex.rs b/src/build/regex.rs similarity index 100% rename from src/build/arg/regex.rs rename to src/build/regex.rs diff --git a/src/build/arg/value_hint.rs b/src/build/value_hint.rs similarity index 100% rename from src/build/arg/value_hint.rs rename to src/build/value_hint.rs diff --git a/src/lib.rs b/src/lib.rs index 792614fd796..9a48ae8629b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,7 +65,7 @@ mod macros; mod derive; #[cfg(feature = "regex")] -pub use crate::build::arg::RegexRef; +pub use crate::build::RegexRef; pub mod error; diff --git a/src/parse/validator.rs b/src/parse/validator.rs index 4a23c5e8aee..c2694ba4377 100644 --- a/src/parse/validator.rs +++ b/src/parse/validator.rs @@ -1,5 +1,5 @@ // Internal -use crate::build::{arg::PossibleValue, App, AppSettings as AS, Arg, ArgPredicate}; +use crate::build::{App, AppSettings as AS, Arg, ArgPredicate, PossibleValue}; use crate::error::{Error, Result as ClapResult}; use crate::output::Usage; use crate::parse::{ArgMatcher, MatchedArg, ParseState, Parser};