From ffad76e7657250a02e7ad827e8ba8b2d42ac9fa0 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 28 Nov 2017 15:22:57 -0500 Subject: [PATCH 1/6] Moving error messages definitions to Error.h --- include/CLI/App.hpp | 29 ++++++++--------- include/CLI/Error.hpp | 74 +++++++++++++++++++++++++++++++++++++----- include/CLI/Ini.hpp | 2 +- include/CLI/Option.hpp | 34 ++++++++++++------- include/CLI/Split.hpp | 8 ++--- tests/IniTest.cpp | 6 ++-- 6 files changed, 111 insertions(+), 42 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 90b0d18fd..c829fe4b3 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -369,7 +369,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) - throw IncorrectConstruction("Flags cannot be positional"); + throw IncorrectConstruction::PositionalFlag(name); opt->set_custom_option("", 0); return opt; } @@ -389,7 +389,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) - throw IncorrectConstruction("Flags cannot be positional"); + throw IncorrectConstruction::PositionalFlag(name); opt->set_custom_option("", 0); return opt; } @@ -409,7 +409,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) - throw IncorrectConstruction("Flags cannot be positional"); + throw IncorrectConstruction::PositionalFlag(name); opt->set_custom_option("", 0); opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); return opt; @@ -428,7 +428,7 @@ class App { Option *opt = add_option(name, fun, description, false); if(opt->get_positional()) - throw IncorrectConstruction("Flags cannot be positional"); + throw IncorrectConstruction::PositionalFlag(name); opt->set_custom_option("", 0); return opt; } @@ -1100,7 +1100,7 @@ class App { std::vector values = detail::parse_ini(config_name_); while(!values.empty()) { if(!_parse_ini(values)) { - throw ExtrasINIError(values.back().fullname); + throw INIError::Extras(values.back().fullname); } } } catch(const FileError &) { @@ -1149,8 +1149,7 @@ class App { if(opt->get_required() || opt->count() != 0) { // Make sure enough -N arguments parsed (+N is already handled in parsing function) if(opt->get_expected() < 0 && opt->count() < static_cast(-opt->get_expected())) - throw ArgumentMismatch(opt->single_name() + ": At least " + std::to_string(-opt->get_expected()) + - " required"); + throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_expected()); // Required but empty if(opt->get_required() && opt->count() == 0) @@ -1167,10 +1166,8 @@ class App { } auto selected_subcommands = get_subcommands(); - if(require_subcommand_min_ > 0 && selected_subcommands.empty()) - throw RequiredError("Subcommand required"); - else if(require_subcommand_min_ > selected_subcommands.size()) - throw RequiredError("Requires at least " + std::to_string(require_subcommand_min_) + " subcommands"); + if(require_subcommand_min_ > selected_subcommands.size()) + throw RequiredError::Subcommand(require_subcommand_min_); // Convert missing (pairs) to extras (string only) if(!(allow_extras_ || prefix_command_)) { @@ -1210,6 +1207,9 @@ class App { // Let's not go crazy with pointer syntax Option_p &op = *op_ptr; + if(!op->get_configurable()) + throw INIError::NotConfigurable(current.fullname); + if(op->results_.empty()) { // Flag parsing if(op->get_expected() == 0) { @@ -1226,10 +1226,10 @@ class App { for(size_t i = 0; i < ui; i++) op->results_.emplace_back(""); } catch(const std::invalid_argument &) { - throw ConversionError(current.fullname + ": Should be true/false or a number"); + throw ConversionError::TrueFalse(current.fullname); } } else - throw ConversionError(current.fullname + ": too many inputs for a flag"); + throw ConversionError::TooManyInputsFlag(current.fullname); } else { op->results_ = current.inputs; op->run_callback(); @@ -1424,8 +1424,7 @@ class App { } if(num > 0) { - throw ArgumentMismatch(op->single_name() + ": " + std::to_string(num) + " required " + - op->get_type_name() + " missing"); + throw ArgumentMismatch::TypedAtLeast(op->single_name(), num, op->get_type_name()); } } diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp index c76cba7f6..4f763b44d 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -35,14 +35,14 @@ enum class ExitCodes { IncorrectConstruction = 100, BadNameString, OptionAlreadyAdded, - File, + FileError, ConversionError, ValidationError, RequiredError, RequiresError, ExcludesError, ExtrasError, - ExtrasINIError, + INIError, InvalidError, HorribleError, OptionNotFound, @@ -85,18 +85,50 @@ class ConstructionError : public Error { class IncorrectConstruction : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional");} + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");} + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");} + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction(name + ": You can't change expected arguments after you've changed the multi option policy!");} + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined");} + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and single value options");} + }; /// Thrown on construction of a bad name class BadNameString : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, BadNameString) CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { + return BadNameString("Invalid one char name: " + name); + } + static BadNameString BadLongName(std::string name) { + return BadNameString("Bad long name: " + name); + } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } }; /// Thrown when an option already exists class OptionAlreadyAdded : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) - CLI11_ERROR_SIMPLE(OptionAlreadyAdded) + OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } }; // Parsing errors @@ -129,7 +161,8 @@ class RuntimeError : public ParseError { /// Thrown when parsing an INI file and it is missing class FileError : public ParseError { CLI11_ERROR_DEF(ParseError, FileError) - FileError(std::string name) : FileError(name + " was not readable (missing?)", ExitCodes::File) {} + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) {return FileError(name + " was not readable (missing?)");} }; /// Thrown when conversion call back fails, such as when an int fails to coerce to a string @@ -138,6 +171,12 @@ class ConversionError : public ParseError { CLI11_ERROR_SIMPLE(ConversionError) ConversionError(std::string member, std::string name) : ConversionError("The value " + member + "is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag");} + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number");} }; /// Thrown when validation of results fails @@ -150,7 +189,15 @@ class ValidationError : public ParseError { /// Thrown when a required option is missing class RequiredError : public ParseError { CLI11_ERROR_DEF(ParseError, RequiredError) - CLI11_ERROR_SIMPLE(RequiredError) + RequiredError(std::string name) : RequiredError(name + " is required") {} + static RequiredError Subcommand(size_t min_subcom) { + if(min_subcom == 1) + return RequiredError("A subcommand"); + else + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError); + } + + }; /// Thrown when the wrong number of arguments has been received @@ -163,6 +210,13 @@ class ArgumentMismatch : public ParseError { : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + ", got " + std::to_string(recieved)), ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required");} + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");} + + }; /// Thrown when a requires option is missing @@ -190,9 +244,12 @@ class ExtrasError : public ParseError { }; /// Thrown when extra values are found in an INI file -class ExtrasINIError : public ParseError { - CLI11_ERROR_DEF(ParseError, ExtrasINIError) - ExtrasINIError(std::string item) : ExtrasINIError("INI was not able to parse " + item, ExitCodes::ExtrasINIError) {} +class INIError : public ParseError { + CLI11_ERROR_DEF(ParseError, INIError) + CLI11_ERROR_SIMPLE(INIError) + static INIError Extras(std::string item) {return INIError("INI was not able to parse " + item);} + static INIError NotConfigurable(std::string item) {return INIError(item + ": This option is not allowed in a configuration file");} + }; /// Thrown when validation fails before parsing @@ -204,6 +261,7 @@ class InvalidError : public ParseError { }; /// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. class HorribleError : public ParseError { CLI11_ERROR_DEF(ParseError, HorribleError) CLI11_ERROR_SIMPLE(HorribleError) diff --git a/include/CLI/Ini.hpp b/include/CLI/Ini.hpp index bab094d95..f9999fff3 100644 --- a/include/CLI/Ini.hpp +++ b/include/CLI/Ini.hpp @@ -106,7 +106,7 @@ inline std::vector parse_ini(const std::string &name) { std::ifstream input{name}; if(!input.good()) - throw FileError(name); + throw FileError::Missing(name); return parse_ini(input); } diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 6061b39a5..4bf563858 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -41,8 +41,12 @@ template class OptionBase { /// Ignore the case when matching (option, not value) bool ignore_case_{false}; + /// Allow this option to be given in a configuration file + bool configurable_{true}; + /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; + template void copy_to(T *other) const { other->group(group_); @@ -80,6 +84,9 @@ template class OptionBase { /// The status of ignore case bool get_ignore_case() const { return ignore_case_; } + + /// The status of configurable + bool get_configurable() const { return configurable_; } /// The status of the multi option policy MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } @@ -106,6 +113,12 @@ template class OptionBase { self->multi_option_policy(MultiOptionPolicy::Join); return self; } + + /// Allow in a configuration file + CRTP *configurable(bool value = true) { + configurable_ = value; + return static_cast(this); + } }; class OptionDefaults : public OptionBase { @@ -235,12 +248,11 @@ class Option : public OptionBase