1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

Moving error messages definitions to Error.h

This commit is contained in:
Henry Fredrick Schreiner 2017-11-28 15:22:57 -05:00 committed by Henry Schreiner
parent 04e01d2b70
commit c1fb53f00d
6 changed files with 111 additions and 42 deletions

View File

@ -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<detail::ini_ret_t> 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<size_t>(-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());
}
}

View File

@ -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<std::string> 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)

View File

@ -106,7 +106,7 @@ inline std::vector<ini_ret_t> parse_ini(const std::string &name) {
std::ifstream input{name};
if(!input.good())
throw FileError(name);
throw FileError::Missing(name);
return parse_ini(input);
}

View File

@ -41,8 +41,12 @@ template <typename CRTP> 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 <typename T> void copy_to(T *other) const {
other->group(group_);
@ -80,6 +84,9 @@ template <typename CRTP> 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 <typename CRTP> 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<CRTP *>(this);
}
};
class OptionDefaults : public OptionBase<OptionDefaults> {
@ -235,12 +248,11 @@ class Option : public OptionBase<Option> {
if(expected_ == value)
return this;
else if(value == 0)
throw IncorrectConstruction("Cannot set 0 expected, use a flag instead");
throw IncorrectConstruction::Set0Opt(single_name());
else if(!changeable_)
throw IncorrectConstruction("You can only change the expected arguments for vectors");
throw IncorrectConstruction::ChangeNotVector(single_name());
else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw)
throw IncorrectConstruction(
"You can't change expected arguments after you've changed the multi option policy!");
throw IncorrectConstruction::AfterMultiOpt(single_name());
expected_ = value;
return this;
@ -269,7 +281,7 @@ class Option : public OptionBase<Option> {
Option *requires(Option *opt) {
auto tup = requires_.insert(opt);
if(!tup.second)
throw OptionAlreadyAdded(get_name() + " requires " + opt->get_name());
throw OptionAlreadyAdded::Requires(get_name(), opt->get_name());
return this;
}
@ -278,7 +290,7 @@ class Option : public OptionBase<Option> {
for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_)
if(opt.get() != this && opt->check_name(opt_name))
return requires(opt.get());
throw IncorrectConstruction("Option " + opt_name + " is not defined");
throw IncorrectConstruction::MissingOption(opt_name);
}
/// Any number supported, any mix of string and Opt
@ -291,7 +303,7 @@ class Option : public OptionBase<Option> {
Option *excludes(Option *opt) {
auto tup = excludes_.insert(opt);
if(!tup.second)
throw OptionAlreadyAdded(get_name() + " excludes " + opt->get_name());
throw OptionAlreadyAdded::Excludes(get_name(), opt->get_name());
return this;
}
@ -300,7 +312,7 @@ class Option : public OptionBase<Option> {
for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_)
if(opt.get() != this && opt->check_name(opt_name))
return excludes(opt.get());
throw IncorrectConstruction("Option " + opt_name + " is not defined");
throw IncorrectConstruction::MissingOption(opt_name);
}
/// Any number supported, any mix of string and Opt
template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) {
@ -324,7 +336,7 @@ class Option : public OptionBase<Option> {
for(const Option_p &opt : parent->options_)
if(opt.get() != this && *opt == *this)
throw OptionAlreadyAdded(opt->get_name() + " is already added");
throw OptionAlreadyAdded(opt->get_name());
return this;
}
@ -332,7 +344,7 @@ class Option : public OptionBase<Option> {
/// Take the last argument if given multiple times
Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
if(get_expected() != 0 && get_expected() != 1)
throw IncorrectConstruction("multi_option_policy only works for flags and single value options!");
throw IncorrectConstruction::MultiOptionPolicy(single_name());
multi_option_policy_ = value;
return this;
}
@ -480,7 +492,7 @@ class Option : public OptionBase<Option> {
}
if(local_result)
throw ConversionError("Could not convert: " + get_name() + "=" + detail::join(results_));
throw ConversionError(get_name(), results_);
}
/// If options share any of the same names, they are equal (not counting positional)

View File

@ -66,18 +66,18 @@ get_names(const std::vector<std::string> &input) {
if(name.length() == 2 && valid_first_char(name[1]))
short_names.emplace_back(1, name[1]);
else
throw BadNameString("Invalid one char name: " + name);
throw BadNameString::OneCharName(name);
} else if(name.length() > 2 && name.substr(0, 2) == "--") {
name = name.substr(2);
if(valid_name_string(name))
long_names.push_back(name);
else
throw BadNameString("Bad long name: " + name);
throw BadNameString::BadLongName(name);
} else if(name == "-" || name == "--") {
throw BadNameString("Must have a name, not just dashes");
throw BadNameString::DashesOnly(name);
} else {
if(pos_name.length() > 0)
throw BadNameString("Only one positional name allowed, remove: " + name);
throw BadNameString::MultiPositionalNames(name);
pos_name = name;
}
}

View File

@ -376,7 +376,7 @@ TEST_F(TApp, IniFailure) {
out << "val=1" << std::endl;
}
EXPECT_THROW(run(), CLI::ExtrasINIError);
EXPECT_THROW(run(), CLI::INIError);
}
TEST_F(TApp, IniSubFailure) {
@ -392,7 +392,7 @@ TEST_F(TApp, IniSubFailure) {
out << "val=1" << std::endl;
}
EXPECT_THROW(run(), CLI::ExtrasINIError);
EXPECT_THROW(run(), CLI::INIError);
}
TEST_F(TApp, IniNoSubFailure) {
@ -407,7 +407,7 @@ TEST_F(TApp, IniNoSubFailure) {
out << "val=1" << std::endl;
}
EXPECT_THROW(run(), CLI::ExtrasINIError);
EXPECT_THROW(run(), CLI::INIError);
}
TEST_F(TApp, IniFlagConvertFailure) {