Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving error messages definitions to Error.h #60

Merged
merged 6 commits into from
Nov 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* Added `CLI::ArgumentMismatch` [#56](https://github.com/CLIUtils/CLI11/pull/56) and fixed missing failure if one arg expected [#55](https://github.com/CLIUtils/CLI11/issues/55)
* Support for minimum unlimited expected arguments [#56](https://github.com/CLIUtils/CLI11/pull/56)
* Single internal arg parse function [#56](https://github.com/CLIUtils/CLI11/pull/56)
* Allow options to be disabled from INI file, rename `add_config` to `set_config` [#60](https://github.com/CLIUtils/CLI11/pull/60)

## Version 1.2

Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
[![License: BSD][license-badge]](./LICENSE)
[![DOI][DOI-badge]][DOI-link]

[API Docs][api-docs] • [Tutorial series][GitBook] • [What's new](./CHANGELOG.md)
[Documentation][GitBook] •
[API Reference][api-docs] •
[What's new](./CHANGELOG.md)

# CLI11: Command line parser for C++11

Expand Down Expand Up @@ -173,6 +175,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t
* `->check(CLI::NonexistentPath)`: Requires that the path does not exist.
* `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
* `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.
* `->configurable(false)`: Disable this option from being in an ini configuration file.

These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.

Expand Down Expand Up @@ -237,13 +240,13 @@ There are several options that are supported on the main app and subcommands. Th
## Configuration file

```cpp
app.add_config(option_name,
app.set_config(option_name="",
default_file_name="",
help_string="Read an ini file",
required=false)
```

Adding a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in `ini` format. An example of a file:
If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in `ini` format. An example of a file:

```ini
; Commments are supported, using a ;
Expand Down
44 changes: 24 additions & 20 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -578,18 +578,23 @@ class App {
return opt;
}

/// Add a configuration ini file option
Option *add_config(std::string name = "--config",
/// Set a configuration ini file option, or clear it if no name passed
Option *set_config(std::string name = "",
std::string default_filename = "",
std::string help = "Read an ini file",
bool required = false) {

// Remove existing config if present
if(config_ptr_ != nullptr)
remove_option(config_ptr_);
config_name_ = default_filename;
config_required_ = required;
config_ptr_ = add_option(name, config_name_, help, !default_filename.empty());

// Only add config if option passed
if(!name.empty()) {
config_name_ = default_filename;
config_required_ = required;
config_ptr_ = add_option(name, config_name_, help, !default_filename.empty());
}

return config_ptr_;
}

Expand Down Expand Up @@ -1100,7 +1105,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 &) {
Expand Down Expand Up @@ -1149,8 +1154,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)
Expand All @@ -1167,10 +1171,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_)) {
Expand Down Expand Up @@ -1210,6 +1212,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) {
Expand All @@ -1226,10 +1231,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();
Expand Down Expand Up @@ -1424,8 +1429,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());
}
}

Expand Down
78 changes: 70 additions & 8 deletions include/CLI/Error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ enum class ExitCodes {
IncorrectConstruction = 100,
BadNameString,
OptionAlreadyAdded,
File,
FileError,
ConversionError,
ValidationError,
RequiredError,
RequiresError,
ExcludesError,
ExtrasError,
ExtrasINIError,
INIError,
InvalidError,
HorribleError,
OptionNotFound,
Expand Down Expand Up @@ -85,18 +85,52 @@ 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
Expand Down Expand Up @@ -129,7 +163,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
Expand All @@ -138,6 +173,14 @@ 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
Expand All @@ -150,7 +193,14 @@ 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", ExitCodes::RequiredError) {}
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
Expand All @@ -163,6 +213,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
Expand Down Expand Up @@ -190,9 +247,13 @@ 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
Expand All @@ -204,6 +265,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)
Expand Down
2 changes: 1 addition & 1 deletion include/CLI/Ini.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading