зеркало из https://github.com/microsoft/CCF.git
Update CLI11 from `2.2.0` to `2.3.2` (#5606)
This commit is contained in:
Родитель
25b6efc252
Коммит
c89ca65ef8
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -8,6 +8,7 @@
|
|||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
@ -23,374 +24,25 @@ namespace CLI {
|
|||
// [CLI11:config_hpp:verbatim]
|
||||
namespace detail {
|
||||
|
||||
inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') {
|
||||
if(arg.empty()) {
|
||||
return std::string(2, stringQuote);
|
||||
}
|
||||
// some specifically supported strings
|
||||
if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
|
||||
return arg;
|
||||
}
|
||||
// floating point conversion can convert some hex codes, but don't try that here
|
||||
if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
|
||||
double val;
|
||||
if(detail::lexical_cast(arg, val)) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
// just quote a single non numeric character
|
||||
if(arg.size() == 1) {
|
||||
return std::string(1, characterQuote) + arg + characterQuote;
|
||||
}
|
||||
// handle hex, binary or octal arguments
|
||||
if(arg.front() == '0') {
|
||||
if(arg[1] == 'x') {
|
||||
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
|
||||
return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
|
||||
})) {
|
||||
return arg;
|
||||
}
|
||||
} else if(arg[1] == 'o') {
|
||||
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
|
||||
return arg;
|
||||
}
|
||||
} else if(arg[1] == 'b') {
|
||||
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(arg.find_first_of(stringQuote) == std::string::npos) {
|
||||
return std::string(1, stringQuote) + arg + stringQuote;
|
||||
} else {
|
||||
return characterQuote + arg + characterQuote;
|
||||
}
|
||||
}
|
||||
std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'');
|
||||
|
||||
/// Comma separated join, adds quotes if needed
|
||||
inline std::string ini_join(const std::vector<std::string> &args,
|
||||
char sepChar = ',',
|
||||
char arrayStart = '[',
|
||||
char arrayEnd = ']',
|
||||
char stringQuote = '"',
|
||||
char characterQuote = '\'') {
|
||||
std::string joined;
|
||||
if(args.size() > 1 && arrayStart != '\0') {
|
||||
joined.push_back(arrayStart);
|
||||
}
|
||||
std::size_t start = 0;
|
||||
for(const auto &arg : args) {
|
||||
if(start++ > 0) {
|
||||
joined.push_back(sepChar);
|
||||
if(isspace(sepChar) == 0) {
|
||||
joined.push_back(' ');
|
||||
}
|
||||
}
|
||||
joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
|
||||
}
|
||||
if(args.size() > 1 && arrayEnd != '\0') {
|
||||
joined.push_back(arrayEnd);
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
std::string ini_join(const std::vector<std::string> &args,
|
||||
char sepChar = ',',
|
||||
char arrayStart = '[',
|
||||
char arrayEnd = ']',
|
||||
char stringQuote = '"',
|
||||
char characterQuote = '\'');
|
||||
|
||||
inline std::vector<std::string> generate_parents(const std::string §ion, std::string &name, char parentSeparator) {
|
||||
std::vector<std::string> parents;
|
||||
if(detail::to_lower(section) != "default") {
|
||||
if(section.find(parentSeparator) != std::string::npos) {
|
||||
parents = detail::split(section, parentSeparator);
|
||||
} else {
|
||||
parents = {section};
|
||||
}
|
||||
}
|
||||
if(name.find(parentSeparator) != std::string::npos) {
|
||||
std::vector<std::string> plist = detail::split(name, parentSeparator);
|
||||
name = plist.back();
|
||||
detail::remove_quotes(name);
|
||||
plist.pop_back();
|
||||
parents.insert(parents.end(), plist.begin(), plist.end());
|
||||
}
|
||||
|
||||
// clean up quotes on the parents
|
||||
for(auto &parent : parents) {
|
||||
detail::remove_quotes(parent);
|
||||
}
|
||||
return parents;
|
||||
}
|
||||
std::vector<std::string> generate_parents(const std::string §ion, std::string &name, char parentSeparator);
|
||||
|
||||
/// assuming non default segments do a check on the close and open of the segments in a configItem structure
|
||||
inline void
|
||||
checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator) {
|
||||
|
||||
std::string estring;
|
||||
auto parents = detail::generate_parents(currentSection, estring, parentSeparator);
|
||||
if(!output.empty() && output.back().name == "--") {
|
||||
std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
|
||||
while(output.back().parents.size() >= msize) {
|
||||
output.push_back(output.back());
|
||||
output.back().parents.pop_back();
|
||||
}
|
||||
|
||||
if(parents.size() > 1) {
|
||||
std::size_t common = 0;
|
||||
std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
|
||||
for(std::size_t ii = 0; ii < mpair; ++ii) {
|
||||
if(output.back().parents[ii] != parents[ii]) {
|
||||
break;
|
||||
}
|
||||
++common;
|
||||
}
|
||||
if(common == mpair) {
|
||||
output.pop_back();
|
||||
} else {
|
||||
while(output.back().parents.size() > common + 1) {
|
||||
output.push_back(output.back());
|
||||
output.back().parents.pop_back();
|
||||
}
|
||||
}
|
||||
for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
|
||||
output.emplace_back();
|
||||
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
|
||||
output.back().name = "++";
|
||||
}
|
||||
}
|
||||
} else if(parents.size() > 1) {
|
||||
for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
|
||||
output.emplace_back();
|
||||
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
|
||||
output.back().name = "++";
|
||||
}
|
||||
}
|
||||
|
||||
// insert a section end which is just an empty items_buffer
|
||||
output.emplace_back();
|
||||
output.back().parents = std::move(parents);
|
||||
output.back().name = "++";
|
||||
}
|
||||
void checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator);
|
||||
} // namespace detail
|
||||
|
||||
inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
|
||||
std::string line;
|
||||
std::string currentSection = "default";
|
||||
std::string previousSection = "default";
|
||||
std::vector<ConfigItem> output;
|
||||
bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
|
||||
bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
|
||||
bool inSection{false};
|
||||
char aStart = (isINIArray) ? '[' : arrayStart;
|
||||
char aEnd = (isINIArray) ? ']' : arrayEnd;
|
||||
char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
|
||||
int currentSectionIndex{0};
|
||||
while(getline(input, line)) {
|
||||
std::vector<std::string> items_buffer;
|
||||
std::string name;
|
||||
|
||||
detail::trim(line);
|
||||
std::size_t len = line.length();
|
||||
// lines have to be at least 3 characters to have any meaning to CLI just skip the rest
|
||||
if(len < 3) {
|
||||
continue;
|
||||
}
|
||||
if(line.front() == '[' && line.back() == ']') {
|
||||
if(currentSection != "default") {
|
||||
// insert a section end which is just an empty items_buffer
|
||||
output.emplace_back();
|
||||
output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
|
||||
output.back().name = "--";
|
||||
}
|
||||
currentSection = line.substr(1, len - 2);
|
||||
// deal with double brackets for TOML
|
||||
if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') {
|
||||
currentSection = currentSection.substr(1, currentSection.size() - 2);
|
||||
}
|
||||
if(detail::to_lower(currentSection) == "default") {
|
||||
currentSection = "default";
|
||||
} else {
|
||||
detail::checkParentSegments(output, currentSection, parentSeparatorChar);
|
||||
}
|
||||
inSection = false;
|
||||
if(currentSection == previousSection) {
|
||||
++currentSectionIndex;
|
||||
} else {
|
||||
currentSectionIndex = 0;
|
||||
previousSection = currentSection;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// comment lines
|
||||
if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find = in string, split and recombine
|
||||
auto pos = line.find(valueDelimiter);
|
||||
if(pos != std::string::npos) {
|
||||
name = detail::trim_copy(line.substr(0, pos));
|
||||
std::string item = detail::trim_copy(line.substr(pos + 1));
|
||||
auto cloc = item.find(commentChar);
|
||||
if(cloc != std::string::npos) {
|
||||
item.erase(cloc, std::string::npos);
|
||||
detail::trim(item);
|
||||
}
|
||||
if(item.size() > 1 && item.front() == aStart) {
|
||||
for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
|
||||
detail::trim(multiline);
|
||||
item += multiline;
|
||||
}
|
||||
items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
|
||||
} else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
|
||||
items_buffer = detail::split_up(item, aSep);
|
||||
} else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
|
||||
items_buffer = detail::split_up(item);
|
||||
} else {
|
||||
items_buffer = {item};
|
||||
}
|
||||
} else {
|
||||
name = detail::trim_copy(line);
|
||||
auto cloc = name.find(commentChar);
|
||||
if(cloc != std::string::npos) {
|
||||
name.erase(cloc, std::string::npos);
|
||||
detail::trim(name);
|
||||
}
|
||||
|
||||
items_buffer = {"true"};
|
||||
}
|
||||
if(name.find(parentSeparatorChar) == std::string::npos) {
|
||||
detail::remove_quotes(name);
|
||||
}
|
||||
// clean up quotes on the items
|
||||
for(auto &it : items_buffer) {
|
||||
detail::remove_quotes(it);
|
||||
}
|
||||
|
||||
std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
|
||||
if(parents.size() > maximumLayers) {
|
||||
continue;
|
||||
}
|
||||
if(!configSection.empty() && !inSection) {
|
||||
if(parents.empty() || parents.front() != configSection) {
|
||||
continue;
|
||||
}
|
||||
if(configIndex >= 0 && currentSectionIndex != configIndex) {
|
||||
continue;
|
||||
}
|
||||
parents.erase(parents.begin());
|
||||
inSection = true;
|
||||
}
|
||||
if(!output.empty() && name == output.back().name && parents == output.back().parents) {
|
||||
output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end());
|
||||
} else {
|
||||
output.emplace_back();
|
||||
output.back().parents = std::move(parents);
|
||||
output.back().name = std::move(name);
|
||||
output.back().inputs = std::move(items_buffer);
|
||||
}
|
||||
}
|
||||
if(currentSection != "default") {
|
||||
// insert a section end which is just an empty items_buffer
|
||||
std::string ename;
|
||||
output.emplace_back();
|
||||
output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar);
|
||||
output.back().name = "--";
|
||||
while(output.back().parents.size() > 1) {
|
||||
output.push_back(output.back());
|
||||
output.back().parents.pop_back();
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
inline std::string
|
||||
ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
|
||||
std::stringstream out;
|
||||
std::string commentLead;
|
||||
commentLead.push_back(commentChar);
|
||||
commentLead.push_back(' ');
|
||||
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
bool defaultUsed = false;
|
||||
groups.insert(groups.begin(), std::string("Options"));
|
||||
if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) {
|
||||
out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n';
|
||||
}
|
||||
for(auto &group : groups) {
|
||||
if(group == "Options" || group.empty()) {
|
||||
if(defaultUsed) {
|
||||
continue;
|
||||
}
|
||||
defaultUsed = true;
|
||||
}
|
||||
if(write_description && group != "Options" && !group.empty()) {
|
||||
out << '\n' << commentLead << group << " Options\n";
|
||||
}
|
||||
for(const Option *opt : app->get_options({})) {
|
||||
|
||||
// Only process options that are configurable
|
||||
if(opt->get_configurable()) {
|
||||
if(opt->get_group() != group) {
|
||||
if(!(group == "Options" && opt->get_group().empty())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::string name = prefix + opt->get_single_name();
|
||||
std::string value = detail::ini_join(
|
||||
opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
|
||||
|
||||
if(value.empty() && default_also) {
|
||||
if(!opt->get_default_str().empty()) {
|
||||
value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote);
|
||||
} else if(opt->get_expected_min() == 0) {
|
||||
value = "false";
|
||||
} else if(opt->get_run_callback_for_default()) {
|
||||
value = "\"\""; // empty string default value
|
||||
}
|
||||
}
|
||||
|
||||
if(!value.empty()) {
|
||||
if(write_description && opt->has_description()) {
|
||||
out << '\n';
|
||||
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
|
||||
}
|
||||
out << name << valueDelimiter << value << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto subcommands = app->get_subcommands({});
|
||||
for(const App *subcom : subcommands) {
|
||||
if(subcom->get_name().empty()) {
|
||||
if(write_description && !subcom->get_group().empty()) {
|
||||
out << '\n' << commentLead << subcom->get_group() << " Options\n";
|
||||
}
|
||||
out << to_config(subcom, default_also, write_description, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
for(const App *subcom : subcommands) {
|
||||
if(!subcom->get_name().empty()) {
|
||||
if(subcom->get_configurable() && app->got_subcommand(subcom)) {
|
||||
if(!prefix.empty() || app->get_parent() == nullptr) {
|
||||
out << '[' << prefix << subcom->get_name() << "]\n";
|
||||
} else {
|
||||
std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name();
|
||||
auto p = app->get_parent();
|
||||
while(p->get_parent() != nullptr) {
|
||||
subname = p->get_name() + parentSeparatorChar + subname;
|
||||
p = p->get_parent();
|
||||
}
|
||||
out << '[' << subname << "]\n";
|
||||
}
|
||||
out << to_config(subcom, default_also, write_description, "");
|
||||
} else {
|
||||
out << to_config(
|
||||
subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// [CLI11:config_hpp:end]
|
||||
} // namespace CLI
|
||||
|
||||
#ifndef CLI11_COMPILE
|
||||
#include "impl/Config_inl.hpp"
|
||||
#endif
|
||||
|
|
|
@ -34,7 +34,7 @@ struct ConfigItem {
|
|||
std::vector<std::string> inputs{};
|
||||
|
||||
/// The list of parents and name joined by "."
|
||||
std::string fullname() const {
|
||||
CLI11_NODISCARD std::string fullname() const {
|
||||
std::vector<std::string> tmp = parents;
|
||||
tmp.emplace_back(name);
|
||||
return detail::join(tmp, ".");
|
||||
|
@ -54,18 +54,18 @@ class Config {
|
|||
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
|
||||
|
||||
/// Get a flag value
|
||||
virtual std::string to_flag(const ConfigItem &item) const {
|
||||
CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const {
|
||||
if(item.inputs.size() == 1) {
|
||||
return item.inputs.at(0);
|
||||
}
|
||||
if(item.inputs.empty()) {
|
||||
return "{}";
|
||||
}
|
||||
throw ConversionError::TooManyInputsFlag(item.fullname());
|
||||
throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
|
||||
std::vector<ConfigItem> from_file(const std::string &name) {
|
||||
CLI11_NODISCARD std::vector<ConfigItem> from_file(const std::string &name) const {
|
||||
std::ifstream input{name};
|
||||
if(!input.good())
|
||||
throw FileError::Missing(name);
|
||||
|
@ -148,7 +148,7 @@ class ConfigBase : public Config {
|
|||
/// get a reference to the configuration section
|
||||
std::string §ionRef() { return configSection; }
|
||||
/// get the section
|
||||
const std::string §ion() const { return configSection; }
|
||||
CLI11_NODISCARD const std::string §ion() const { return configSection; }
|
||||
/// specify a particular section of the configuration file to use
|
||||
ConfigBase *section(const std::string §ionName) {
|
||||
configSection = sectionName;
|
||||
|
@ -158,7 +158,7 @@ class ConfigBase : public Config {
|
|||
/// get a reference to the configuration index
|
||||
int16_t &indexRef() { return configIndex; }
|
||||
/// get the section index
|
||||
int16_t index() const { return configIndex; }
|
||||
CLI11_NODISCARD int16_t index() const { return configIndex; }
|
||||
/// specify a particular index in the section to use (-1) for all sections to use
|
||||
ConfigBase *index(int16_t sectionIndex) {
|
||||
configIndex = sectionIndex;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
// [CLI11:public_includes:end]
|
||||
|
||||
// CLI library includes
|
||||
#include "Macros.hpp"
|
||||
#include "StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
@ -72,9 +73,9 @@ class Error : public std::runtime_error {
|
|||
std::string error_name{"Error"};
|
||||
|
||||
public:
|
||||
int get_exit_code() const { return actual_exit_code; }
|
||||
CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; }
|
||||
|
||||
std::string get_name() const { return error_name; }
|
||||
CLI11_NODISCARD std::string get_name() const { return error_name; }
|
||||
|
||||
Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
|
||||
: runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
|
||||
|
@ -137,10 +138,10 @@ class OptionAlreadyAdded : public ConstructionError {
|
|||
explicit 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);
|
||||
return {name + " requires " + other, ExitCodes::OptionAlreadyAdded};
|
||||
}
|
||||
static OptionAlreadyAdded Excludes(std::string name, std::string other) {
|
||||
return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
|
||||
return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -223,32 +224,30 @@ class RequiredError : public ParseError {
|
|||
if(min_subcom == 1) {
|
||||
return RequiredError("A subcommand");
|
||||
}
|
||||
return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
|
||||
ExitCodes::RequiredError);
|
||||
return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError};
|
||||
}
|
||||
static RequiredError
|
||||
Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
|
||||
if((min_option == 1) && (max_option == 1) && (used == 0))
|
||||
return RequiredError("Exactly 1 option from [" + option_list + "]");
|
||||
if((min_option == 1) && (max_option == 1) && (used > 1)) {
|
||||
return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
|
||||
" were given",
|
||||
ExitCodes::RequiredError);
|
||||
return {"Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
|
||||
" were given",
|
||||
ExitCodes::RequiredError};
|
||||
}
|
||||
if((min_option == 1) && (used == 0))
|
||||
return RequiredError("At least 1 option from [" + option_list + "]");
|
||||
if(used < min_option) {
|
||||
return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
|
||||
std::to_string(used) + "were given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError);
|
||||
return {"Requires at least " + std::to_string(min_option) + " options used and only " +
|
||||
std::to_string(used) + "were given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError};
|
||||
}
|
||||
if(max_option == 1)
|
||||
return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError);
|
||||
return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError};
|
||||
|
||||
return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
|
||||
std::to_string(used) + "were given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError);
|
||||
return {"Requires at most " + std::to_string(max_option) + " options be used and " + std::to_string(used) +
|
||||
"were given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -17,276 +17,9 @@
|
|||
|
||||
namespace CLI {
|
||||
// [CLI11:formatter_hpp:verbatim]
|
||||
|
||||
inline std::string
|
||||
Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
|
||||
std::stringstream out;
|
||||
|
||||
out << "\n" << group << ":\n";
|
||||
for(const Option *opt : opts) {
|
||||
out << make_option(opt, is_positional);
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_positionals(const App *app) const {
|
||||
std::vector<const Option *> opts =
|
||||
app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
|
||||
|
||||
if(opts.empty())
|
||||
return std::string();
|
||||
|
||||
return make_group(get_label("Positionals"), true, opts);
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
|
||||
// Options
|
||||
for(const std::string &group : groups) {
|
||||
std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
|
||||
return opt->get_group() == group // Must be in the right group
|
||||
&& opt->nonpositional() // Must not be a positional
|
||||
&& (mode != AppFormatMode::Sub // If mode is Sub, then
|
||||
|| (app->get_help_ptr() != opt // Ignore help pointer
|
||||
&& app->get_help_all_ptr() != opt)); // Ignore help all pointer
|
||||
});
|
||||
if(!group.empty() && !opts.empty()) {
|
||||
out << make_group(group, false, opts);
|
||||
|
||||
if(group != groups.back())
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_description(const App *app) const {
|
||||
std::string desc = app->get_description();
|
||||
auto min_options = app->get_require_option_min();
|
||||
auto max_options = app->get_require_option_max();
|
||||
if(app->get_required()) {
|
||||
desc += " REQUIRED ";
|
||||
}
|
||||
if((max_options == min_options) && (min_options > 0)) {
|
||||
if(min_options == 1) {
|
||||
desc += " \n[Exactly 1 of the following options is required]";
|
||||
} else {
|
||||
desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
|
||||
}
|
||||
} else if(max_options > 0) {
|
||||
if(min_options > 0) {
|
||||
desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
|
||||
" of the follow options are required]";
|
||||
} else {
|
||||
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
|
||||
}
|
||||
} else if(min_options > 0) {
|
||||
desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
|
||||
}
|
||||
return (!desc.empty()) ? desc + "\n" : std::string{};
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_usage(const App *app, std::string name) const {
|
||||
std::stringstream out;
|
||||
|
||||
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
|
||||
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
|
||||
// Print an Options badge if any options exist
|
||||
std::vector<const Option *> non_pos_options =
|
||||
app->get_options([](const Option *opt) { return opt->nonpositional(); });
|
||||
if(!non_pos_options.empty())
|
||||
out << " [" << get_label("OPTIONS") << "]";
|
||||
|
||||
// Positionals need to be listed here
|
||||
std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
|
||||
|
||||
// Print out positionals if any are left
|
||||
if(!positionals.empty()) {
|
||||
// Convert to help names
|
||||
std::vector<std::string> positional_names(positionals.size());
|
||||
std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
|
||||
return make_option_usage(opt);
|
||||
});
|
||||
|
||||
out << " " << detail::join(positional_names, " ");
|
||||
}
|
||||
|
||||
// Add a marker if subcommands are expected or optional
|
||||
if(!app->get_subcommands(
|
||||
[](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
|
||||
.empty()) {
|
||||
out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
|
||||
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
|
||||
: "SUBCOMMANDS")
|
||||
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
|
||||
}
|
||||
|
||||
out << std::endl;
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_footer(const App *app) const {
|
||||
std::string footer = app->get_footer();
|
||||
if(footer.empty()) {
|
||||
return std::string{};
|
||||
}
|
||||
return footer + "\n";
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
|
||||
|
||||
// This immediately forwards to the make_expanded method. This is done this way so that subcommands can
|
||||
// have overridden formatters
|
||||
if(mode == AppFormatMode::Sub)
|
||||
return make_expanded(app);
|
||||
|
||||
std::stringstream out;
|
||||
if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
|
||||
if(app->get_group() != "Subcommands") {
|
||||
out << app->get_group() << ':';
|
||||
}
|
||||
}
|
||||
|
||||
out << make_description(app);
|
||||
out << make_usage(app, name);
|
||||
out << make_positionals(app);
|
||||
out << make_groups(app, mode);
|
||||
out << make_subcommands(app, mode);
|
||||
out << '\n' << make_footer(app);
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
|
||||
std::vector<const App *> subcommands = app->get_subcommands({});
|
||||
|
||||
// Make a list in definition order of the groups seen
|
||||
std::vector<std::string> subcmd_groups_seen;
|
||||
for(const App *com : subcommands) {
|
||||
if(com->get_name().empty()) {
|
||||
if(!com->get_group().empty()) {
|
||||
out << make_expanded(com);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::string group_key = com->get_group();
|
||||
if(!group_key.empty() &&
|
||||
std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
|
||||
return detail::to_lower(a) == detail::to_lower(group_key);
|
||||
}) == subcmd_groups_seen.end())
|
||||
subcmd_groups_seen.push_back(group_key);
|
||||
}
|
||||
|
||||
// For each group, filter out and print subcommands
|
||||
for(const std::string &group : subcmd_groups_seen) {
|
||||
out << "\n" << group << ":\n";
|
||||
std::vector<const App *> subcommands_group = app->get_subcommands(
|
||||
[&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
|
||||
for(const App *new_com : subcommands_group) {
|
||||
if(new_com->get_name().empty())
|
||||
continue;
|
||||
if(mode != AppFormatMode::All) {
|
||||
out << make_subcommand(new_com);
|
||||
} else {
|
||||
out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_subcommand(const App *sub) const {
|
||||
std::stringstream out;
|
||||
detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_expanded(const App *sub) const {
|
||||
std::stringstream out;
|
||||
out << sub->get_display_name(true) << "\n";
|
||||
|
||||
out << make_description(sub);
|
||||
if(sub->get_name().empty() && !sub->get_aliases().empty()) {
|
||||
detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
|
||||
}
|
||||
out << make_positionals(sub);
|
||||
out << make_groups(sub, AppFormatMode::Sub);
|
||||
out << make_subcommands(sub, AppFormatMode::Sub);
|
||||
|
||||
// Drop blank spaces
|
||||
std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n");
|
||||
tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n'
|
||||
|
||||
// Indent all but the first line (the name)
|
||||
return detail::find_and_replace(tmp, "\n", "\n ") + "\n";
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
|
||||
if(is_positional)
|
||||
return opt->get_name(true, false);
|
||||
|
||||
return opt->get_name(false, true);
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_opts(const Option *opt) const {
|
||||
std::stringstream out;
|
||||
|
||||
if(!opt->get_option_text().empty()) {
|
||||
out << " " << opt->get_option_text();
|
||||
} else {
|
||||
if(opt->get_type_size() != 0) {
|
||||
if(!opt->get_type_name().empty())
|
||||
out << " " << get_label(opt->get_type_name());
|
||||
if(!opt->get_default_str().empty())
|
||||
out << " [" << opt->get_default_str() << "] ";
|
||||
if(opt->get_expected_max() == detail::expected_max_vector_size)
|
||||
out << " ...";
|
||||
else if(opt->get_expected_min() > 1)
|
||||
out << " x " << opt->get_expected();
|
||||
|
||||
if(opt->get_required())
|
||||
out << " " << get_label("REQUIRED");
|
||||
}
|
||||
if(!opt->get_envname().empty())
|
||||
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
|
||||
if(!opt->get_needs().empty()) {
|
||||
out << " " << get_label("Needs") << ":";
|
||||
for(const Option *op : opt->get_needs())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
if(!opt->get_excludes().empty()) {
|
||||
out << " " << get_label("Excludes") << ":";
|
||||
for(const Option *op : opt->get_excludes())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
|
||||
|
||||
inline std::string Formatter::make_option_usage(const Option *opt) const {
|
||||
// Note that these are positionals usages
|
||||
std::stringstream out;
|
||||
out << make_option_name(opt, true);
|
||||
if(opt->get_expected_max() >= detail::expected_max_vector_size)
|
||||
out << "...";
|
||||
else if(opt->get_expected_max() > 1)
|
||||
out << "(" << opt->get_expected() << "x)";
|
||||
|
||||
return opt->get_required() ? out.str() : "[" + out.str() + "]";
|
||||
}
|
||||
|
||||
// [CLI11:formatter_hpp:end]
|
||||
} // namespace CLI
|
||||
|
||||
#ifndef CLI11_COMPILE
|
||||
#include "impl/Formatter_inl.hpp"
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
@ -56,6 +57,8 @@ class FormatterBase {
|
|||
FormatterBase() = default;
|
||||
FormatterBase(const FormatterBase &) = default;
|
||||
FormatterBase(FormatterBase &&) = default;
|
||||
FormatterBase &operator=(const FormatterBase &) = default;
|
||||
FormatterBase &operator=(FormatterBase &&) = default;
|
||||
|
||||
/// Adding a destructor in this form to work around bug in GCC 4.7
|
||||
virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default)
|
||||
|
@ -78,15 +81,14 @@ class FormatterBase {
|
|||
///@{
|
||||
|
||||
/// Get the current value of a name (REQUIRED, etc.)
|
||||
std::string get_label(std::string key) const {
|
||||
CLI11_NODISCARD std::string get_label(std::string key) const {
|
||||
if(labels_.find(key) == labels_.end())
|
||||
return key;
|
||||
else
|
||||
return labels_.at(key);
|
||||
return labels_.at(key);
|
||||
}
|
||||
|
||||
/// Get the current column width
|
||||
std::size_t get_column_width() const { return column_width_; }
|
||||
CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; }
|
||||
|
||||
///@}
|
||||
};
|
||||
|
@ -118,13 +120,16 @@ class Formatter : public FormatterBase {
|
|||
Formatter() = default;
|
||||
Formatter(const Formatter &) = default;
|
||||
Formatter(Formatter &&) = default;
|
||||
Formatter &operator=(const Formatter &) = default;
|
||||
Formatter &operator=(Formatter &&) = default;
|
||||
|
||||
/// @name Overridables
|
||||
///@{
|
||||
|
||||
/// This prints out a group of options with title
|
||||
///
|
||||
virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
|
||||
CLI11_NODISCARD virtual std::string
|
||||
make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
|
||||
|
||||
/// This prints out just the positionals "group"
|
||||
virtual std::string make_positionals(const App *app) const;
|
||||
|
|
|
@ -41,6 +41,14 @@
|
|||
#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
|
||||
#endif
|
||||
|
||||
// GCC < 10 doesn't ignore this in unevaluated contexts
|
||||
#if !defined(CLI11_CPP17) || \
|
||||
(defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4)
|
||||
#define CLI11_NODISCARD
|
||||
#else
|
||||
#define CLI11_NODISCARD [[nodiscard]]
|
||||
#endif
|
||||
|
||||
/** detection of rtti */
|
||||
#ifndef CLI11_USE_STATIC_RTTI
|
||||
#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI)
|
||||
|
@ -57,4 +65,11 @@
|
|||
#define CLI11_USE_STATIC_RTTI 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/** Inline macro **/
|
||||
#ifdef CLI11_COMPILE
|
||||
#define CLI11_INLINE
|
||||
#else
|
||||
#define CLI11_INLINE inline
|
||||
#endif
|
||||
// [CLI11:macros_hpp:end]
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -13,8 +13,7 @@
|
|||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
#include "Error.hpp"
|
||||
#include "StringTools.hpp"
|
||||
#include "Macros.hpp"
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:split_hpp:verbatim]
|
||||
|
@ -22,122 +21,28 @@ namespace CLI {
|
|||
namespace detail {
|
||||
|
||||
// Returns false if not a short option. Otherwise, sets opt name and rest and returns true
|
||||
inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) {
|
||||
if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) {
|
||||
name = current.substr(1, 1);
|
||||
rest = current.substr(2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest);
|
||||
|
||||
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
|
||||
inline bool split_long(const std::string ¤t, std::string &name, std::string &value) {
|
||||
if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
|
||||
auto loc = current.find_first_of('=');
|
||||
if(loc != std::string::npos) {
|
||||
name = current.substr(2, loc - 2);
|
||||
value = current.substr(loc + 1);
|
||||
} else {
|
||||
name = current.substr(2);
|
||||
value = "";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value);
|
||||
|
||||
// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
|
||||
inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) {
|
||||
if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
|
||||
auto loc = current.find_first_of(':');
|
||||
if(loc != std::string::npos) {
|
||||
name = current.substr(1, loc - 1);
|
||||
value = current.substr(loc + 1);
|
||||
} else {
|
||||
name = current.substr(1);
|
||||
value = "";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value);
|
||||
|
||||
// Splits a string into multiple long and short names
|
||||
inline std::vector<std::string> split_names(std::string current) {
|
||||
std::vector<std::string> output;
|
||||
std::size_t val;
|
||||
while((val = current.find(",")) != std::string::npos) {
|
||||
output.push_back(trim_copy(current.substr(0, val)));
|
||||
current = current.substr(val + 1);
|
||||
}
|
||||
output.push_back(trim_copy(current));
|
||||
return output;
|
||||
}
|
||||
CLI11_INLINE std::vector<std::string> split_names(std::string current);
|
||||
|
||||
/// extract default flag values either {def} or starting with a !
|
||||
inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
|
||||
std::vector<std::string> flags = split_names(str);
|
||||
flags.erase(std::remove_if(flags.begin(),
|
||||
flags.end(),
|
||||
[](const std::string &name) {
|
||||
return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
|
||||
(name.back() == '}')) ||
|
||||
(name[0] == '!'))));
|
||||
}),
|
||||
flags.end());
|
||||
std::vector<std::pair<std::string, std::string>> output;
|
||||
output.reserve(flags.size());
|
||||
for(auto &flag : flags) {
|
||||
auto def_start = flag.find_first_of('{');
|
||||
std::string defval = "false";
|
||||
if((def_start != std::string::npos) && (flag.back() == '}')) {
|
||||
defval = flag.substr(def_start + 1);
|
||||
defval.pop_back();
|
||||
flag.erase(def_start, std::string::npos);
|
||||
}
|
||||
flag.erase(0, flag.find_first_not_of("-!"));
|
||||
output.emplace_back(flag, defval);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
CLI11_INLINE std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str);
|
||||
|
||||
/// Get a vector of short names, one of long names, and a single name
|
||||
inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
|
||||
get_names(const std::vector<std::string> &input) {
|
||||
|
||||
std::vector<std::string> short_names;
|
||||
std::vector<std::string> long_names;
|
||||
std::string pos_name;
|
||||
|
||||
for(std::string name : input) {
|
||||
if(name.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
|
||||
if(name.length() == 2 && valid_first_char(name[1]))
|
||||
short_names.emplace_back(1, name[1]);
|
||||
else
|
||||
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::BadLongName(name);
|
||||
} else if(name == "-" || name == "--") {
|
||||
throw BadNameString::DashesOnly(name);
|
||||
} else {
|
||||
if(pos_name.length() > 0)
|
||||
throw BadNameString::MultiPositionalNames(name);
|
||||
pos_name = name;
|
||||
}
|
||||
}
|
||||
|
||||
return std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>(
|
||||
short_names, long_names, pos_name);
|
||||
}
|
||||
CLI11_INLINE std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
|
||||
get_names(const std::vector<std::string> &input);
|
||||
|
||||
} // namespace detail
|
||||
// [CLI11:split_hpp:end]
|
||||
} // namespace CLI
|
||||
|
||||
#ifndef CLI11_COMPILE
|
||||
#include "impl/Split_inl.hpp"
|
||||
#endif
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
#include "Macros.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
// [CLI11:string_tools_hpp:verbatim]
|
||||
|
@ -43,21 +45,7 @@ namespace detail {
|
|||
constexpr int expected_max_vector_size{1 << 29};
|
||||
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
|
||||
/// Split a string by a delim
|
||||
inline std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::vector<std::string> elems;
|
||||
// Check to see if empty string, give consistent result
|
||||
if(s.empty()) {
|
||||
elems.emplace_back();
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss.str(s);
|
||||
std::string item;
|
||||
while(std::getline(ss, item, delim)) {
|
||||
elems.push_back(item);
|
||||
}
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim);
|
||||
|
||||
/// Simple function to join a string
|
||||
template <typename T> std::string join(const T &v, std::string delim = ",") {
|
||||
|
@ -106,33 +94,16 @@ template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
|
|||
// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string
|
||||
|
||||
/// Trim whitespace from left of string
|
||||
inline std::string <rim(std::string &str) {
|
||||
auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
|
||||
str.erase(str.begin(), it);
|
||||
return str;
|
||||
}
|
||||
CLI11_INLINE std::string <rim(std::string &str);
|
||||
|
||||
/// Trim anything from left of string
|
||||
inline std::string <rim(std::string &str, const std::string &filter) {
|
||||
auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
|
||||
str.erase(str.begin(), it);
|
||||
return str;
|
||||
}
|
||||
CLI11_INLINE std::string <rim(std::string &str, const std::string &filter);
|
||||
|
||||
/// Trim whitespace from right of string
|
||||
inline std::string &rtrim(std::string &str) {
|
||||
auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
|
||||
str.erase(it.base(), str.end());
|
||||
return str;
|
||||
}
|
||||
CLI11_INLINE std::string &rtrim(std::string &str);
|
||||
|
||||
/// Trim anything from right of string
|
||||
inline std::string &rtrim(std::string &str, const std::string &filter) {
|
||||
auto it =
|
||||
std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
|
||||
str.erase(it.base(), str.end());
|
||||
return str;
|
||||
}
|
||||
CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter);
|
||||
|
||||
/// Trim whitespace from string
|
||||
inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
|
||||
|
@ -147,31 +118,13 @@ inline std::string trim_copy(const std::string &str) {
|
|||
}
|
||||
|
||||
/// remove quotes at the front and back of a string either '"' or '\''
|
||||
inline std::string &remove_quotes(std::string &str) {
|
||||
if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
|
||||
if(str.front() == str.back()) {
|
||||
str.pop_back();
|
||||
str.erase(str.begin(), str.begin() + 1);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
CLI11_INLINE std::string &remove_quotes(std::string &str);
|
||||
|
||||
/// Add a leader to the beginning of all new lines (nothing is added
|
||||
/// at the start of the first line). `"; "` would be for ini files
|
||||
///
|
||||
/// Can't use Regex, or this would be a subs.
|
||||
inline std::string fix_newlines(const std::string &leader, std::string input) {
|
||||
std::string::size_type n = 0;
|
||||
while(n != std::string::npos && n < input.size()) {
|
||||
n = input.find('\n', n);
|
||||
if(n != std::string::npos) {
|
||||
input = input.substr(0, n + 1) + leader + input.substr(n + 1);
|
||||
n += leader.size();
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input);
|
||||
|
||||
/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
|
||||
inline std::string trim_copy(const std::string &str, const std::string &filter) {
|
||||
|
@ -179,40 +132,11 @@ inline std::string trim_copy(const std::string &str, const std::string &filter)
|
|||
return trim(s, filter);
|
||||
}
|
||||
/// Print a two part "help" string
|
||||
inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
|
||||
name = " " + name;
|
||||
out << std::setw(static_cast<int>(wid)) << std::left << name;
|
||||
if(!description.empty()) {
|
||||
if(name.length() >= wid)
|
||||
out << "\n" << std::setw(static_cast<int>(wid)) << "";
|
||||
for(const char c : description) {
|
||||
out.put(c);
|
||||
if(c == '\n') {
|
||||
out << std::setw(static_cast<int>(wid)) << "";
|
||||
}
|
||||
}
|
||||
}
|
||||
out << "\n";
|
||||
return out;
|
||||
}
|
||||
CLI11_INLINE std::ostream &
|
||||
format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid);
|
||||
|
||||
/// Print subcommand aliases
|
||||
inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
|
||||
if(!aliases.empty()) {
|
||||
out << std::setw(static_cast<int>(wid)) << " aliases: ";
|
||||
bool front = true;
|
||||
for(const auto &alias : aliases) {
|
||||
if(!front) {
|
||||
out << ", ";
|
||||
} else {
|
||||
front = false;
|
||||
}
|
||||
out << detail::fix_newlines(" ", alias);
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid);
|
||||
|
||||
/// Verify the first character of an option
|
||||
/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
|
||||
|
@ -227,16 +151,7 @@ template <typename T> bool valid_later_char(T c) {
|
|||
}
|
||||
|
||||
/// Verify an option/subcommand name
|
||||
inline bool valid_name_string(const std::string &str) {
|
||||
if(str.empty() || !valid_first_char(str[0])) {
|
||||
return false;
|
||||
}
|
||||
auto e = str.end();
|
||||
for(auto c = str.begin() + 1; c != e; ++c)
|
||||
if(!valid_later_char(*c))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
CLI11_INLINE bool valid_name_string(const std::string &str);
|
||||
|
||||
/// Verify an app name
|
||||
inline bool valid_alias_name_string(const std::string &str) {
|
||||
|
@ -270,66 +185,20 @@ inline std::string remove_underscore(std::string str) {
|
|||
}
|
||||
|
||||
/// Find and replace a substring with another substring
|
||||
inline std::string find_and_replace(std::string str, std::string from, std::string to) {
|
||||
|
||||
std::size_t start_pos = 0;
|
||||
|
||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to);
|
||||
|
||||
/// check if the flag definitions has possible false flags
|
||||
inline bool has_default_flag_values(const std::string &flags) {
|
||||
return (flags.find_first_of("{!") != std::string::npos);
|
||||
}
|
||||
|
||||
inline void remove_default_flag_values(std::string &flags) {
|
||||
auto loc = flags.find_first_of('{', 2);
|
||||
while(loc != std::string::npos) {
|
||||
auto finish = flags.find_first_of("},", loc + 1);
|
||||
if((finish != std::string::npos) && (flags[finish] == '}')) {
|
||||
flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
|
||||
flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
|
||||
}
|
||||
loc = flags.find_first_of('{', loc + 1);
|
||||
}
|
||||
flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
|
||||
}
|
||||
CLI11_INLINE void remove_default_flag_values(std::string &flags);
|
||||
|
||||
/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
|
||||
inline std::ptrdiff_t find_member(std::string name,
|
||||
const std::vector<std::string> names,
|
||||
bool ignore_case = false,
|
||||
bool ignore_underscore = false) {
|
||||
auto it = std::end(names);
|
||||
if(ignore_case) {
|
||||
if(ignore_underscore) {
|
||||
name = detail::to_lower(detail::remove_underscore(name));
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::to_lower(detail::remove_underscore(local_name)) == name;
|
||||
});
|
||||
} else {
|
||||
name = detail::to_lower(name);
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::to_lower(local_name) == name;
|
||||
});
|
||||
}
|
||||
|
||||
} else if(ignore_underscore) {
|
||||
name = detail::remove_underscore(name);
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::remove_underscore(local_name) == name;
|
||||
});
|
||||
} else {
|
||||
it = std::find(std::begin(names), std::end(names), name);
|
||||
}
|
||||
|
||||
return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
|
||||
}
|
||||
CLI11_INLINE std::ptrdiff_t find_member(std::string name,
|
||||
const std::vector<std::string> names,
|
||||
bool ignore_case = false,
|
||||
bool ignore_underscore = false);
|
||||
|
||||
/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
|
||||
/// trigger and returns the position in the string to search for the next trigger string
|
||||
|
@ -343,88 +212,23 @@ template <typename Callable> inline std::string find_and_modify(std::string str,
|
|||
|
||||
/// Split a string '"one two" "three"' into 'one two', 'three'
|
||||
/// Quote characters can be ` ' or "
|
||||
inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
|
||||
|
||||
const std::string delims("\'\"`");
|
||||
auto find_ws = [delimiter](char ch) {
|
||||
return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
|
||||
};
|
||||
trim(str);
|
||||
|
||||
std::vector<std::string> output;
|
||||
bool embeddedQuote = false;
|
||||
char keyChar = ' ';
|
||||
while(!str.empty()) {
|
||||
if(delims.find_first_of(str[0]) != std::string::npos) {
|
||||
keyChar = str[0];
|
||||
auto end = str.find_first_of(keyChar, 1);
|
||||
while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
|
||||
end = str.find_first_of(keyChar, end + 1);
|
||||
embeddedQuote = true;
|
||||
}
|
||||
if(end != std::string::npos) {
|
||||
output.push_back(str.substr(1, end - 1));
|
||||
if(end + 2 < str.size()) {
|
||||
str = str.substr(end + 2);
|
||||
} else {
|
||||
str.clear();
|
||||
}
|
||||
|
||||
} else {
|
||||
output.push_back(str.substr(1));
|
||||
str = "";
|
||||
}
|
||||
} else {
|
||||
auto it = std::find_if(std::begin(str), std::end(str), find_ws);
|
||||
if(it != std::end(str)) {
|
||||
std::string value = std::string(str.begin(), it);
|
||||
output.push_back(value);
|
||||
str = std::string(it + 1, str.end());
|
||||
} else {
|
||||
output.push_back(str);
|
||||
str = "";
|
||||
}
|
||||
}
|
||||
// transform any embedded quotes into the regular character
|
||||
if(embeddedQuote) {
|
||||
output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
|
||||
embeddedQuote = false;
|
||||
}
|
||||
trim(str);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0');
|
||||
|
||||
/// This function detects an equal or colon followed by an escaped quote after an argument
|
||||
/// then modifies the string to replace the equality with a space. This is needed
|
||||
/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
|
||||
/// the return value is the offset+1 which is required by the find_and_modify function.
|
||||
inline std::size_t escape_detect(std::string &str, std::size_t offset) {
|
||||
auto next = str[offset + 1];
|
||||
if((next == '\"') || (next == '\'') || (next == '`')) {
|
||||
auto astart = str.find_last_of("-/ \"\'`", offset - 1);
|
||||
if(astart != std::string::npos) {
|
||||
if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
|
||||
str[offset] = ' '; // interpret this as a space so the split_up works properly
|
||||
}
|
||||
}
|
||||
return offset + 1;
|
||||
}
|
||||
CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset);
|
||||
|
||||
/// Add quotes if the string contains spaces
|
||||
inline std::string &add_quotes_if_needed(std::string &str) {
|
||||
if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
|
||||
char quote = str.find('"') < str.find('\'') ? '\'' : '"';
|
||||
if(str.find(' ') != std::string::npos) {
|
||||
str.insert(0, 1, quote);
|
||||
str.append(1, quote);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
CLI11_INLINE std::string &add_quotes_if_needed(std::string &str);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// [CLI11:string_tools_hpp:end]
|
||||
|
||||
} // namespace CLI
|
||||
|
||||
#ifndef CLI11_COMPILE
|
||||
#include "impl/StringTools_inl.hpp"
|
||||
#endif
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#define _GLIBCXX_USE_NANOSLEEP
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
@ -63,7 +65,7 @@ class Timer {
|
|||
/// Time a function by running it multiple times. Target time is the len to target.
|
||||
std::string time_it(std::function<void()> f, double target_time = 1) {
|
||||
time_point start = start_;
|
||||
double total_time;
|
||||
double total_time = NAN;
|
||||
|
||||
start_ = clock::now();
|
||||
std::size_t n = 0;
|
||||
|
@ -79,7 +81,7 @@ class Timer {
|
|||
}
|
||||
|
||||
/// This formats the numerical value for the time string
|
||||
std::string make_time_str() const {
|
||||
std::string make_time_str() const { // NOLINT(modernize-use-nodiscard)
|
||||
time_point stop = clock::now();
|
||||
std::chrono::duration<double> elapsed = stop - start_;
|
||||
double time = elapsed.count() / static_cast<double>(cycles);
|
||||
|
@ -88,7 +90,7 @@ class Timer {
|
|||
|
||||
// LCOV_EXCL_START
|
||||
/// This prints out a time string from a time
|
||||
std::string make_time_str(double time) const {
|
||||
std::string make_time_str(double time) const { // NOLINT(modernize-use-nodiscard)
|
||||
auto print_it = [](double x, std::string unit) {
|
||||
const unsigned int buffer_length = 50;
|
||||
std::array<char, buffer_length> buffer;
|
||||
|
@ -98,17 +100,16 @@ class Timer {
|
|||
|
||||
if(time < .000001)
|
||||
return print_it(time * 1000000000, "ns");
|
||||
else if(time < .001)
|
||||
if(time < .001)
|
||||
return print_it(time * 1000000, "us");
|
||||
else if(time < 1)
|
||||
if(time < 1)
|
||||
return print_it(time * 1000, "ms");
|
||||
else
|
||||
return print_it(time, "s");
|
||||
return print_it(time, "s");
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
/// This is the main function, it creates a string
|
||||
std::string to_string() const { return time_print_(title_, make_time_str()); }
|
||||
std::string to_string() const { return time_print_(title_, make_time_str()); } // NOLINT(modernize-use-nodiscard)
|
||||
|
||||
/// Division sets the number of cycles to divide by (no graphical change)
|
||||
Timer &operator/(std::size_t val) {
|
||||
|
|
|
@ -149,7 +149,7 @@ template <typename T, typename C> class is_direct_constructible {
|
|||
#ifdef __CUDACC__
|
||||
#pragma diag_suppress 2361
|
||||
#endif
|
||||
TT { std::declval<CC>() }
|
||||
TT{std::declval<CC>()}
|
||||
#ifdef __CUDACC__
|
||||
#pragma diag_default 2361
|
||||
#endif
|
||||
|
@ -276,7 +276,7 @@ template <typename T,
|
|||
enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
std::string to_string(const T &value) {
|
||||
return std::string(value);
|
||||
return std::string(value); // NOLINT(google-readability-casting)
|
||||
}
|
||||
|
||||
/// Convert an object to a string (streaming must be supported for that type)
|
||||
|
@ -296,7 +296,7 @@ template <typename T,
|
|||
!is_readable_container<typename std::remove_const<T>::type>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
std::string to_string(T &&) {
|
||||
return std::string{};
|
||||
return {};
|
||||
}
|
||||
|
||||
/// convert a readable container to a string
|
||||
|
@ -308,14 +308,14 @@ std::string to_string(T &&variable) {
|
|||
auto cval = variable.begin();
|
||||
auto end = variable.end();
|
||||
if(cval == end) {
|
||||
return std::string("{}");
|
||||
return {"{}"};
|
||||
}
|
||||
std::vector<std::string> defaults;
|
||||
while(cval != end) {
|
||||
defaults.emplace_back(CLI::detail::to_string(*cval));
|
||||
++cval;
|
||||
}
|
||||
return std::string("[" + detail::join(defaults) + "]");
|
||||
return {"[" + detail::join(defaults) + "]"};
|
||||
}
|
||||
|
||||
/// special template overload
|
||||
|
@ -655,7 +655,8 @@ struct classify_object<
|
|||
typename std::enable_if<is_tuple_like<T>::value &&
|
||||
((type_count<T>::value >= 2 && !is_wrapper<T>::value) ||
|
||||
(uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
|
||||
!is_direct_constructible<T, int>::value))>::type> {
|
||||
!is_direct_constructible<T, int>::value) ||
|
||||
(uncommon_type<T>::value && type_count<T>::value >= 2))>::type> {
|
||||
static constexpr object_category value{object_category::tuple_value};
|
||||
// the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be
|
||||
// constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which
|
||||
|
@ -763,8 +764,8 @@ inline typename std::enable_if<I == type_count_base<T>::value, std::string>::typ
|
|||
/// Recursively generate the tuple type name
|
||||
template <typename T, std::size_t I>
|
||||
inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() {
|
||||
std::string str = std::string(type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()) +
|
||||
',' + tuple_name<T, I + 1>();
|
||||
auto str = std::string{type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()} + ',' +
|
||||
tuple_name<T, I + 1>();
|
||||
if(str.back() == ',')
|
||||
str.pop_back();
|
||||
return str;
|
||||
|
@ -843,7 +844,7 @@ inline std::int64_t to_flag_value(std::string val) {
|
|||
return -1;
|
||||
}
|
||||
val = detail::to_lower(val);
|
||||
std::int64_t ret;
|
||||
std::int64_t ret = 0;
|
||||
if(val.size() == 1) {
|
||||
if(val[0] >= '1' && val[0] <= '9') {
|
||||
return (static_cast<std::int64_t>(val[0]) - '0');
|
||||
|
@ -1019,17 +1020,18 @@ template <
|
|||
typename T,
|
||||
enable_if_t<classify_object<T>::value == object_category::number_constructible, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(const std::string &input, T &output) {
|
||||
int val;
|
||||
int val = 0;
|
||||
if(integral_conversion(input, val)) {
|
||||
output = T(val);
|
||||
return true;
|
||||
} else {
|
||||
double dval;
|
||||
if(lexical_cast(input, dval)) {
|
||||
output = T{dval};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
double dval = 0.0;
|
||||
if(lexical_cast(input, dval)) {
|
||||
output = T{dval};
|
||||
return true;
|
||||
}
|
||||
|
||||
return from_stream(input, output);
|
||||
}
|
||||
|
||||
|
@ -1038,7 +1040,7 @@ template <
|
|||
typename T,
|
||||
enable_if_t<classify_object<T>::value == object_category::integer_constructible, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(const std::string &input, T &output) {
|
||||
int val;
|
||||
int val = 0;
|
||||
if(integral_conversion(input, val)) {
|
||||
output = T(val);
|
||||
return true;
|
||||
|
@ -1051,7 +1053,7 @@ template <
|
|||
typename T,
|
||||
enable_if_t<classify_object<T>::value == object_category::double_constructible, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(const std::string &input, T &output) {
|
||||
double val;
|
||||
double val = 0.0;
|
||||
if(lexical_cast(input, val)) {
|
||||
output = T{val};
|
||||
return true;
|
||||
|
@ -1064,7 +1066,7 @@ template <typename T,
|
|||
enable_if_t<classify_object<T>::value == object_category::other && std::is_assignable<T &, int>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(const std::string &input, T &output) {
|
||||
int val;
|
||||
int val = 0;
|
||||
if(integral_conversion(input, val)) {
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
|
@ -1152,7 +1154,7 @@ bool lexical_assign(const std::string &input, AssignTo &output) {
|
|||
output = 0;
|
||||
return true;
|
||||
}
|
||||
int val;
|
||||
int val = 0;
|
||||
if(lexical_cast(input, val)) {
|
||||
output = val;
|
||||
return true;
|
||||
|
@ -1266,9 +1268,8 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu
|
|||
output = ConvertTo{x, y};
|
||||
}
|
||||
return worked;
|
||||
} else {
|
||||
return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
|
||||
}
|
||||
return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
|
||||
}
|
||||
|
||||
/// Conversion to a vector type using a particular single type as the conversion type
|
||||
|
@ -1542,8 +1543,9 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) {
|
|||
output.append(arg);
|
||||
}
|
||||
} else {
|
||||
if(val <= static_cast<double>(std::numeric_limits<std::int64_t>::min()) ||
|
||||
val >= static_cast<double>(std::numeric_limits<std::int64_t>::max()) ||
|
||||
if(val <= static_cast<double>((std::numeric_limits<std::int64_t>::min)()) ||
|
||||
val >= static_cast<double>((std::numeric_limits<std::int64_t>::max)()) ||
|
||||
// NOLINTNEXTLINE(clang-diagnostic-float-equal,bugprone-narrowing-conversions)
|
||||
val == static_cast<std::int64_t>(val)) {
|
||||
output = detail::value_string(static_cast<int64_t>(val));
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Error.hpp"
|
||||
#include "Macros.hpp"
|
||||
#include "StringTools.hpp"
|
||||
#include "TypeTools.hpp"
|
||||
|
@ -94,6 +95,9 @@ class Validator {
|
|||
/// specify that a validator should not modify the input
|
||||
bool non_modifying_{false};
|
||||
|
||||
Validator(std::string validator_desc, std::function<std::string(std::string &)> func)
|
||||
: desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {}
|
||||
|
||||
public:
|
||||
Validator() = default;
|
||||
/// Construct a Validator with just the description string
|
||||
|
@ -109,18 +113,7 @@ class Validator {
|
|||
}
|
||||
/// This is the required operator for a Validator - provided to help
|
||||
/// users (CLI11 uses the member `func` directly)
|
||||
std::string operator()(std::string &str) const {
|
||||
std::string retstring;
|
||||
if(active_) {
|
||||
if(non_modifying_) {
|
||||
std::string value = str;
|
||||
retstring = func_(value);
|
||||
} else {
|
||||
retstring = func_(str);
|
||||
}
|
||||
}
|
||||
return retstring;
|
||||
}
|
||||
std::string operator()(std::string &str) const;
|
||||
|
||||
/// This is the required operator for a Validator - provided to help
|
||||
/// users (CLI11 uses the member `func` directly)
|
||||
|
@ -135,13 +128,10 @@ class Validator {
|
|||
return *this;
|
||||
}
|
||||
/// Specify the type string
|
||||
Validator description(std::string validator_desc) const {
|
||||
Validator newval(*this);
|
||||
newval.desc_function_ = [validator_desc]() { return validator_desc; };
|
||||
return newval;
|
||||
}
|
||||
CLI11_NODISCARD Validator description(std::string validator_desc) const;
|
||||
|
||||
/// Generate type description information for the Validator
|
||||
std::string get_description() const {
|
||||
CLI11_NODISCARD std::string get_description() const {
|
||||
if(active_) {
|
||||
return desc_function_();
|
||||
}
|
||||
|
@ -153,20 +143,20 @@ class Validator {
|
|||
return *this;
|
||||
}
|
||||
/// Specify the type string
|
||||
Validator name(std::string validator_name) const {
|
||||
CLI11_NODISCARD Validator name(std::string validator_name) const {
|
||||
Validator newval(*this);
|
||||
newval.name_ = std::move(validator_name);
|
||||
return newval;
|
||||
}
|
||||
/// Get the name of the Validator
|
||||
const std::string &get_name() const { return name_; }
|
||||
CLI11_NODISCARD const std::string &get_name() const { return name_; }
|
||||
/// Specify whether the Validator is active or not
|
||||
Validator &active(bool active_val = true) {
|
||||
active_ = active_val;
|
||||
return *this;
|
||||
}
|
||||
/// Specify whether the Validator is active or not
|
||||
Validator active(bool active_val = true) const {
|
||||
CLI11_NODISCARD Validator active(bool active_val = true) const {
|
||||
Validator newval(*this);
|
||||
newval.active_ = active_val;
|
||||
return newval;
|
||||
|
@ -183,107 +173,33 @@ class Validator {
|
|||
return *this;
|
||||
}
|
||||
/// Specify the application index of a validator
|
||||
Validator application_index(int app_index) const {
|
||||
CLI11_NODISCARD Validator application_index(int app_index) const {
|
||||
Validator newval(*this);
|
||||
newval.application_index_ = app_index;
|
||||
return newval;
|
||||
}
|
||||
/// Get the current value of the application index
|
||||
int get_application_index() const { return application_index_; }
|
||||
CLI11_NODISCARD int get_application_index() const { return application_index_; }
|
||||
/// Get a boolean if the validator is active
|
||||
bool get_active() const { return active_; }
|
||||
CLI11_NODISCARD bool get_active() const { return active_; }
|
||||
|
||||
/// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input
|
||||
bool get_modifying() const { return !non_modifying_; }
|
||||
CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; }
|
||||
|
||||
/// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
|
||||
/// same.
|
||||
Validator operator&(const Validator &other) const {
|
||||
Validator newval;
|
||||
|
||||
newval._merge_description(*this, other, " AND ");
|
||||
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string & filename)> &f1 = func_;
|
||||
const std::function<std::string(std::string & filename)> &f2 = other.func_;
|
||||
|
||||
newval.func_ = [f1, f2](std::string &input) {
|
||||
std::string s1 = f1(input);
|
||||
std::string s2 = f2(input);
|
||||
if(!s1.empty() && !s2.empty())
|
||||
return std::string("(") + s1 + ") AND (" + s2 + ")";
|
||||
else
|
||||
return s1 + s2;
|
||||
};
|
||||
|
||||
newval.active_ = (active_ & other.active_);
|
||||
newval.application_index_ = application_index_;
|
||||
return newval;
|
||||
}
|
||||
Validator operator&(const Validator &other) const;
|
||||
|
||||
/// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
|
||||
/// same.
|
||||
Validator operator|(const Validator &other) const {
|
||||
Validator newval;
|
||||
|
||||
newval._merge_description(*this, other, " OR ");
|
||||
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string &)> &f1 = func_;
|
||||
const std::function<std::string(std::string &)> &f2 = other.func_;
|
||||
|
||||
newval.func_ = [f1, f2](std::string &input) {
|
||||
std::string s1 = f1(input);
|
||||
std::string s2 = f2(input);
|
||||
if(s1.empty() || s2.empty())
|
||||
return std::string();
|
||||
|
||||
return std::string("(") + s1 + ") OR (" + s2 + ")";
|
||||
};
|
||||
newval.active_ = (active_ & other.active_);
|
||||
newval.application_index_ = application_index_;
|
||||
return newval;
|
||||
}
|
||||
Validator operator|(const Validator &other) const;
|
||||
|
||||
/// Create a validator that fails when a given validator succeeds
|
||||
Validator operator!() const {
|
||||
Validator newval;
|
||||
const std::function<std::string()> &dfunc1 = desc_function_;
|
||||
newval.desc_function_ = [dfunc1]() {
|
||||
auto str = dfunc1();
|
||||
return (!str.empty()) ? std::string("NOT ") + str : std::string{};
|
||||
};
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string & res)> &f1 = func_;
|
||||
|
||||
newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
|
||||
std::string s1 = f1(test);
|
||||
if(s1.empty()) {
|
||||
return std::string("check ") + dfunc1() + " succeeded improperly";
|
||||
}
|
||||
return std::string{};
|
||||
};
|
||||
newval.active_ = active_;
|
||||
newval.application_index_ = application_index_;
|
||||
return newval;
|
||||
}
|
||||
Validator operator!() const;
|
||||
|
||||
private:
|
||||
void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
|
||||
|
||||
const std::function<std::string()> &dfunc1 = val1.desc_function_;
|
||||
const std::function<std::string()> &dfunc2 = val2.desc_function_;
|
||||
|
||||
desc_function_ = [=]() {
|
||||
std::string f1 = dfunc1();
|
||||
std::string f2 = dfunc2();
|
||||
if((f1.empty()) || (f2.empty())) {
|
||||
return f1 + f2;
|
||||
}
|
||||
return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
|
||||
};
|
||||
}
|
||||
}; // namespace CLI
|
||||
void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger);
|
||||
};
|
||||
|
||||
/// Class wrapping some of the accessors of Validator
|
||||
class CustomValidator : public Validator {
|
||||
|
@ -297,132 +213,37 @@ namespace detail {
|
|||
/// CLI enumeration of different file types
|
||||
enum class path_type { nonexistent, file, directory };
|
||||
|
||||
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
|
||||
/// get the type of the path from a file name
|
||||
inline path_type check_path(const char *file) noexcept {
|
||||
std::error_code ec;
|
||||
auto stat = std::filesystem::status(file, ec);
|
||||
if(ec) {
|
||||
return path_type::nonexistent;
|
||||
}
|
||||
switch(stat.type()) {
|
||||
case std::filesystem::file_type::none:
|
||||
case std::filesystem::file_type::not_found:
|
||||
return path_type::nonexistent;
|
||||
case std::filesystem::file_type::directory:
|
||||
return path_type::directory;
|
||||
case std::filesystem::file_type::symlink:
|
||||
case std::filesystem::file_type::block:
|
||||
case std::filesystem::file_type::character:
|
||||
case std::filesystem::file_type::fifo:
|
||||
case std::filesystem::file_type::socket:
|
||||
case std::filesystem::file_type::regular:
|
||||
case std::filesystem::file_type::unknown:
|
||||
default:
|
||||
return path_type::file;
|
||||
}
|
||||
}
|
||||
#else
|
||||
/// get the type of the path from a file name
|
||||
inline path_type check_path(const char *file) noexcept {
|
||||
#if defined(_MSC_VER)
|
||||
struct __stat64 buffer;
|
||||
if(_stat64(file, &buffer) == 0) {
|
||||
return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
|
||||
}
|
||||
#else
|
||||
struct stat buffer;
|
||||
if(stat(file, &buffer) == 0) {
|
||||
return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
|
||||
}
|
||||
#endif
|
||||
return path_type::nonexistent;
|
||||
}
|
||||
#endif
|
||||
CLI11_INLINE path_type check_path(const char *file) noexcept;
|
||||
|
||||
/// Check for an existing file (returns error message if check fails)
|
||||
class ExistingFileValidator : public Validator {
|
||||
public:
|
||||
ExistingFileValidator() : Validator("FILE") {
|
||||
func_ = [](std::string &filename) {
|
||||
auto path_result = check_path(filename.c_str());
|
||||
if(path_result == path_type::nonexistent) {
|
||||
return "File does not exist: " + filename;
|
||||
}
|
||||
if(path_result == path_type::directory) {
|
||||
return "File is actually a directory: " + filename;
|
||||
}
|
||||
return std::string();
|
||||
};
|
||||
}
|
||||
ExistingFileValidator();
|
||||
};
|
||||
|
||||
/// Check for an existing directory (returns error message if check fails)
|
||||
class ExistingDirectoryValidator : public Validator {
|
||||
public:
|
||||
ExistingDirectoryValidator() : Validator("DIR") {
|
||||
func_ = [](std::string &filename) {
|
||||
auto path_result = check_path(filename.c_str());
|
||||
if(path_result == path_type::nonexistent) {
|
||||
return "Directory does not exist: " + filename;
|
||||
}
|
||||
if(path_result == path_type::file) {
|
||||
return "Directory is actually a file: " + filename;
|
||||
}
|
||||
return std::string();
|
||||
};
|
||||
}
|
||||
ExistingDirectoryValidator();
|
||||
};
|
||||
|
||||
/// Check for an existing path
|
||||
class ExistingPathValidator : public Validator {
|
||||
public:
|
||||
ExistingPathValidator() : Validator("PATH(existing)") {
|
||||
func_ = [](std::string &filename) {
|
||||
auto path_result = check_path(filename.c_str());
|
||||
if(path_result == path_type::nonexistent) {
|
||||
return "Path does not exist: " + filename;
|
||||
}
|
||||
return std::string();
|
||||
};
|
||||
}
|
||||
ExistingPathValidator();
|
||||
};
|
||||
|
||||
/// Check for an non-existing path
|
||||
class NonexistentPathValidator : public Validator {
|
||||
public:
|
||||
NonexistentPathValidator() : Validator("PATH(non-existing)") {
|
||||
func_ = [](std::string &filename) {
|
||||
auto path_result = check_path(filename.c_str());
|
||||
if(path_result != path_type::nonexistent) {
|
||||
return "Path already exists: " + filename;
|
||||
}
|
||||
return std::string();
|
||||
};
|
||||
}
|
||||
NonexistentPathValidator();
|
||||
};
|
||||
|
||||
/// Validate the given string is a legal ipv4 address
|
||||
class IPV4Validator : public Validator {
|
||||
public:
|
||||
IPV4Validator() : Validator("IPV4") {
|
||||
func_ = [](std::string &ip_addr) {
|
||||
auto result = CLI::detail::split(ip_addr, '.');
|
||||
if(result.size() != 4) {
|
||||
return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')';
|
||||
}
|
||||
int num;
|
||||
for(const auto &var : result) {
|
||||
bool retval = detail::lexical_cast(var, num);
|
||||
if(!retval) {
|
||||
return std::string("Failed parsing number (") + var + ')';
|
||||
}
|
||||
if(num < 0 || num > 255) {
|
||||
return std::string("Each IP number must be between 0 and 255 ") + var;
|
||||
}
|
||||
}
|
||||
return std::string();
|
||||
};
|
||||
}
|
||||
IPV4Validator();
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
@ -447,15 +268,14 @@ const detail::IPV4Validator ValidIPV4;
|
|||
/// Validate the input as a particular type
|
||||
template <typename DesiredType> class TypeValidator : public Validator {
|
||||
public:
|
||||
explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) {
|
||||
func_ = [](std::string &input_string) {
|
||||
auto val = DesiredType();
|
||||
if(!detail::lexical_cast(input_string, val)) {
|
||||
return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
|
||||
}
|
||||
return std::string();
|
||||
};
|
||||
}
|
||||
explicit TypeValidator(const std::string &validator_name)
|
||||
: Validator(validator_name, [](std::string &input_string) {
|
||||
auto val = DesiredType();
|
||||
if(!detail::lexical_cast(input_string, val)) {
|
||||
return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
|
||||
}
|
||||
return std::string();
|
||||
}) {}
|
||||
TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
|
||||
};
|
||||
|
||||
|
@ -466,28 +286,7 @@ const TypeValidator<double> Number("NUMBER");
|
|||
/// with the error return optionally disabled
|
||||
class FileOnDefaultPath : public Validator {
|
||||
public:
|
||||
explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true) : Validator("FILE") {
|
||||
func_ = [default_path, enableErrorReturn](std::string &filename) {
|
||||
auto path_result = detail::check_path(filename.c_str());
|
||||
if(path_result == detail::path_type::nonexistent) {
|
||||
std::string test_file_path = default_path;
|
||||
if(default_path.back() != '/' && default_path.back() != '\\') {
|
||||
// Add folder separator
|
||||
test_file_path += '/';
|
||||
}
|
||||
test_file_path.append(filename);
|
||||
path_result = detail::check_path(test_file_path.c_str());
|
||||
if(path_result == detail::path_type::file) {
|
||||
filename = test_file_path;
|
||||
} else {
|
||||
if(enableErrorReturn) {
|
||||
return "File does not exist: " + filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::string{};
|
||||
};
|
||||
}
|
||||
explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true);
|
||||
};
|
||||
|
||||
/// Produce a range (factory). Min and max are inclusive.
|
||||
|
@ -527,7 +326,7 @@ class Range : public Validator {
|
|||
/// Check for a non negative number
|
||||
const Range NonNegativeNumber((std::numeric_limits<double>::max)(), "NONNEGATIVE");
|
||||
|
||||
/// Check for a positive valued number (val>0.0), min() her is the smallest positive number
|
||||
/// Check for a positive valued number (val>0.0), <double>::min here is the smallest positive number
|
||||
const Range PositiveNumber((std::numeric_limits<double>::min)(), (std::numeric_limits<double>::max)(), "POSITIVE");
|
||||
|
||||
/// Produce a bounded range (factory). Min and max are inclusive.
|
||||
|
@ -664,9 +463,8 @@ template <typename T>
|
|||
inline typename std::enable_if<std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
|
||||
if((a > 0) == (b > 0)) {
|
||||
return ((std::numeric_limits<T>::max)() / (std::abs)(a) < (std::abs)(b));
|
||||
} else {
|
||||
return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
|
||||
}
|
||||
return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
|
||||
}
|
||||
/// Do a check for overflow on unsigned numbers
|
||||
template <typename T>
|
||||
|
@ -952,7 +750,7 @@ class AsNumberWithUnit : public Validator {
|
|||
|
||||
// transform function
|
||||
func_ = [mapping, opts](std::string &input) -> std::string {
|
||||
Number num;
|
||||
Number num{};
|
||||
|
||||
detail::rtrim(input);
|
||||
if(input.empty()) {
|
||||
|
@ -1056,6 +854,10 @@ class AsNumberWithUnit : public Validator {
|
|||
}
|
||||
};
|
||||
|
||||
inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) {
|
||||
return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
|
||||
/// Converts a human-readable size string (with unit literal) to uin64_t size.
|
||||
/// Example:
|
||||
/// "100" => 100
|
||||
|
@ -1078,44 +880,14 @@ class AsSizeValue : public AsNumberWithUnit {
|
|||
/// The first option is formally correct, but
|
||||
/// the second interpretation is more wide-spread
|
||||
/// (see https://en.wikipedia.org/wiki/Binary_prefix).
|
||||
explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
|
||||
if(kb_is_1000) {
|
||||
description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
|
||||
} else {
|
||||
description("SIZE [b, kb(=1024b), ...]");
|
||||
}
|
||||
}
|
||||
explicit AsSizeValue(bool kb_is_1000);
|
||||
|
||||
private:
|
||||
/// Get <size unit, factor> mapping
|
||||
static std::map<std::string, result_t> init_mapping(bool kb_is_1000) {
|
||||
std::map<std::string, result_t> m;
|
||||
result_t k_factor = kb_is_1000 ? 1000 : 1024;
|
||||
result_t ki_factor = 1024;
|
||||
result_t k = 1;
|
||||
result_t ki = 1;
|
||||
m["b"] = 1;
|
||||
for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
|
||||
k *= k_factor;
|
||||
ki *= ki_factor;
|
||||
m[p] = k;
|
||||
m[p + "b"] = k;
|
||||
m[p + "i"] = ki;
|
||||
m[p + "ib"] = ki;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
static std::map<std::string, result_t> init_mapping(bool kb_is_1000);
|
||||
|
||||
/// Cache calculated mapping
|
||||
static std::map<std::string, result_t> get_mapping(bool kb_is_1000) {
|
||||
if(kb_is_1000) {
|
||||
static auto m = init_mapping(true);
|
||||
return m;
|
||||
} else {
|
||||
static auto m = init_mapping(false);
|
||||
return m;
|
||||
}
|
||||
}
|
||||
static std::map<std::string, result_t> get_mapping(bool kb_is_1000);
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
@ -1123,53 +895,14 @@ namespace detail {
|
|||
/// the string is assumed to contain a file name followed by other arguments
|
||||
/// the return value contains is a pair with the first argument containing the program name and the second
|
||||
/// everything else.
|
||||
inline std::pair<std::string, std::string> split_program_name(std::string commandline) {
|
||||
// try to determine the programName
|
||||
std::pair<std::string, std::string> vals;
|
||||
trim(commandline);
|
||||
auto esp = commandline.find_first_of(' ', 1);
|
||||
while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) {
|
||||
esp = commandline.find_first_of(' ', esp + 1);
|
||||
if(esp == std::string::npos) {
|
||||
// if we have reached the end and haven't found a valid file just assume the first argument is the
|
||||
// program name
|
||||
if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') {
|
||||
bool embeddedQuote = false;
|
||||
auto keyChar = commandline[0];
|
||||
auto end = commandline.find_first_of(keyChar, 1);
|
||||
while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes
|
||||
end = commandline.find_first_of(keyChar, end + 1);
|
||||
embeddedQuote = true;
|
||||
}
|
||||
if(end != std::string::npos) {
|
||||
vals.first = commandline.substr(1, end - 1);
|
||||
esp = end + 1;
|
||||
if(embeddedQuote) {
|
||||
vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar));
|
||||
}
|
||||
} else {
|
||||
esp = commandline.find_first_of(' ', 1);
|
||||
}
|
||||
} else {
|
||||
esp = commandline.find_first_of(' ', 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(vals.first.empty()) {
|
||||
vals.first = commandline.substr(0, esp);
|
||||
rtrim(vals.first);
|
||||
}
|
||||
|
||||
// strip the program name
|
||||
vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{};
|
||||
ltrim(vals.second);
|
||||
return vals;
|
||||
}
|
||||
CLI11_INLINE std::pair<std::string, std::string> split_program_name(std::string commandline);
|
||||
|
||||
} // namespace detail
|
||||
/// @}
|
||||
|
||||
// [CLI11:validators_hpp:end]
|
||||
} // namespace CLI
|
||||
|
||||
#ifndef CLI11_COMPILE
|
||||
#include "impl/Validators_inl.hpp"
|
||||
#endif
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
// [CLI11:version_hpp:verbatim]
|
||||
|
||||
#define CLI11_VERSION_MAJOR 2
|
||||
#define CLI11_VERSION_MINOR 2
|
||||
#define CLI11_VERSION_PATCH 0
|
||||
#define CLI11_VERSION "2.2.0"
|
||||
#define CLI11_VERSION_MINOR 3
|
||||
#define CLI11_VERSION_PATCH 1
|
||||
#define CLI11_VERSION "2.3.1"
|
||||
|
||||
// [CLI11:version_hpp:end]
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/CLIUtils/CLI11.git",
|
||||
"commitHash": "b9be5b9444772324459989177108a6a65b8b2769"
|
||||
"commitHash": "291c58789c031208f08f4f261a858b5b7083e8e2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче