Squashed 'libs/cli11/' content from commit dcbcb47
git-subtree-dir: libs/cli11 git-subtree-split: dcbcb4721dda5dab0a56d9faaaee50e6a30f7758
This commit is contained in:
2072
include/CLI/impl/App_inl.hpp
Normal file
2072
include/CLI/impl/App_inl.hpp
Normal file
File diff suppressed because it is too large
Load Diff
394
include/CLI/impl/Config_inl.hpp
Normal file
394
include/CLI/impl/Config_inl.hpp
Normal file
@@ -0,0 +1,394 @@
|
||||
// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// This include is only needed for IDEs to discover symbols
|
||||
#include <CLI/Config.hpp>
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:config_inl_hpp:verbatim]
|
||||
|
||||
namespace detail {
|
||||
|
||||
CLI11_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 = 0.0;
|
||||
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;
|
||||
}
|
||||
return characterQuote + arg + characterQuote;
|
||||
}
|
||||
|
||||
CLI11_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(!std::isspace<char>(sepChar, std::locale())) {
|
||||
joined.push_back(' ');
|
||||
}
|
||||
}
|
||||
joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
|
||||
}
|
||||
if(args.size() > 1 && arrayEnd != '\0') {
|
||||
joined.push_back(arrayEnd);
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
|
||||
CLI11_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;
|
||||
}
|
||||
|
||||
CLI11_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 = "++";
|
||||
}
|
||||
} // 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); // NOLINT(readability-suspicious-call-argument)
|
||||
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); // NOLINT(readability-suspicious-call-argument)
|
||||
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;
|
||||
}
|
||||
|
||||
CLI11_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(!opt->get_fnames().empty()) {
|
||||
value = opt->get_flag_value(name, value);
|
||||
}
|
||||
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();
|
||||
const 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_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
291
include/CLI/impl/Formatter_inl.hpp
Normal file
291
include/CLI/impl/Formatter_inl.hpp
Normal file
@@ -0,0 +1,291 @@
|
||||
// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// This include is only needed for IDEs to discover symbols
|
||||
#include <CLI/Formatter.hpp>
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:formatter_inl_hpp:verbatim]
|
||||
CLI11_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();
|
||||
}
|
||||
|
||||
CLI11_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 {};
|
||||
|
||||
return make_group(get_label("Positionals"), true, opts);
|
||||
}
|
||||
|
||||
CLI11_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();
|
||||
}
|
||||
|
||||
CLI11_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{};
|
||||
}
|
||||
|
||||
CLI11_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();
|
||||
}
|
||||
|
||||
CLI11_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";
|
||||
}
|
||||
|
||||
CLI11_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();
|
||||
}
|
||||
|
||||
CLI11_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();
|
||||
}
|
||||
|
||||
CLI11_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();
|
||||
}
|
||||
|
||||
CLI11_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";
|
||||
}
|
||||
|
||||
CLI11_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);
|
||||
}
|
||||
|
||||
CLI11_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();
|
||||
}
|
||||
|
||||
CLI11_INLINE std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
|
||||
|
||||
CLI11_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_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
653
include/CLI/impl/Option_inl.hpp
Normal file
653
include/CLI/impl/Option_inl.hpp
Normal file
@@ -0,0 +1,653 @@
|
||||
// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// This include is only needed for IDEs to discover symbols
|
||||
#include <CLI/Option.hpp>
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:option_inl_hpp:verbatim]
|
||||
|
||||
template <typename CRTP> template <typename T> void OptionBase<CRTP>::copy_to(T *other) const {
|
||||
other->group(group_);
|
||||
other->required(required_);
|
||||
other->ignore_case(ignore_case_);
|
||||
other->ignore_underscore(ignore_underscore_);
|
||||
other->configurable(configurable_);
|
||||
other->disable_flag_override(disable_flag_override_);
|
||||
other->delimiter(delimiter_);
|
||||
other->always_capture_default(always_capture_default_);
|
||||
other->multi_option_policy(multi_option_policy_);
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::expected(int value) {
|
||||
if(value < 0) {
|
||||
expected_min_ = -value;
|
||||
if(expected_max_ < expected_min_) {
|
||||
expected_max_ = expected_min_;
|
||||
}
|
||||
allow_extra_args_ = true;
|
||||
flag_like_ = false;
|
||||
} else if(value == detail::expected_max_vector_size) {
|
||||
expected_min_ = 1;
|
||||
expected_max_ = detail::expected_max_vector_size;
|
||||
allow_extra_args_ = true;
|
||||
flag_like_ = false;
|
||||
} else {
|
||||
expected_min_ = value;
|
||||
expected_max_ = value;
|
||||
flag_like_ = (expected_min_ == 0);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::expected(int value_min, int value_max) {
|
||||
if(value_min < 0) {
|
||||
value_min = -value_min;
|
||||
}
|
||||
|
||||
if(value_max < 0) {
|
||||
value_max = detail::expected_max_vector_size;
|
||||
}
|
||||
if(value_max < value_min) {
|
||||
expected_min_ = value_max;
|
||||
expected_max_ = value_min;
|
||||
} else {
|
||||
expected_max_ = value_max;
|
||||
expected_min_ = value_min;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::check(Validator validator, const std::string &validator_name) {
|
||||
validator.non_modifying();
|
||||
validators_.push_back(std::move(validator));
|
||||
if(!validator_name.empty())
|
||||
validators_.back().name(validator_name);
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::check(std::function<std::string(const std::string &)> Validator,
|
||||
std::string Validator_description,
|
||||
std::string Validator_name) {
|
||||
validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
|
||||
validators_.back().non_modifying();
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::transform(Validator Validator, const std::string &Validator_name) {
|
||||
validators_.insert(validators_.begin(), std::move(Validator));
|
||||
if(!Validator_name.empty())
|
||||
validators_.front().name(Validator_name);
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::transform(const std::function<std::string(std::string)> &func,
|
||||
std::string transform_description,
|
||||
std::string transform_name) {
|
||||
validators_.insert(validators_.begin(),
|
||||
Validator(
|
||||
[func](std::string &val) {
|
||||
val = func(val);
|
||||
return std::string{};
|
||||
},
|
||||
std::move(transform_description),
|
||||
std::move(transform_name)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::each(const std::function<void(std::string)> &func) {
|
||||
validators_.emplace_back(
|
||||
[func](std::string &inout) {
|
||||
func(inout);
|
||||
return std::string{};
|
||||
},
|
||||
std::string{});
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Validator *Option::get_validator(const std::string &Validator_name) {
|
||||
for(auto &Validator : validators_) {
|
||||
if(Validator_name == Validator.get_name()) {
|
||||
return &Validator;
|
||||
}
|
||||
}
|
||||
if((Validator_name.empty()) && (!validators_.empty())) {
|
||||
return &(validators_.front());
|
||||
}
|
||||
throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
|
||||
}
|
||||
|
||||
CLI11_INLINE Validator *Option::get_validator(int index) {
|
||||
// This is an signed int so that it is not equivalent to a pointer.
|
||||
if(index >= 0 && index < static_cast<int>(validators_.size())) {
|
||||
return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
|
||||
}
|
||||
throw OptionNotFound("Validator index is not valid");
|
||||
}
|
||||
|
||||
CLI11_INLINE bool Option::remove_needs(Option *opt) {
|
||||
auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);
|
||||
|
||||
if(iterator == std::end(needs_)) {
|
||||
return false;
|
||||
}
|
||||
needs_.erase(iterator);
|
||||
return true;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::excludes(Option *opt) {
|
||||
if(opt == this) {
|
||||
throw(IncorrectConstruction("and option cannot exclude itself"));
|
||||
}
|
||||
excludes_.insert(opt);
|
||||
|
||||
// Help text should be symmetric - excluding a should exclude b
|
||||
opt->excludes_.insert(this);
|
||||
|
||||
// Ignoring the insert return value, excluding twice is now allowed.
|
||||
// (Mostly to allow both directions to be excluded by user, even though the library does it for you.)
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE bool Option::remove_excludes(Option *opt) {
|
||||
auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);
|
||||
|
||||
if(iterator == std::end(excludes_)) {
|
||||
return false;
|
||||
}
|
||||
excludes_.erase(iterator);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> Option *Option::ignore_case(bool value) {
|
||||
if(!ignore_case_ && value) {
|
||||
ignore_case_ = value;
|
||||
auto *parent = static_cast<T *>(parent_);
|
||||
for(const Option_p &opt : parent->options_) {
|
||||
if(opt.get() == this) {
|
||||
continue;
|
||||
}
|
||||
const auto &omatch = opt->matching_name(*this);
|
||||
if(!omatch.empty()) {
|
||||
ignore_case_ = false;
|
||||
throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ignore_case_ = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
template <typename T> Option *Option::ignore_underscore(bool value) {
|
||||
|
||||
if(!ignore_underscore_ && value) {
|
||||
ignore_underscore_ = value;
|
||||
auto *parent = static_cast<T *>(parent_);
|
||||
for(const Option_p &opt : parent->options_) {
|
||||
if(opt.get() == this) {
|
||||
continue;
|
||||
}
|
||||
const auto &omatch = opt->matching_name(*this);
|
||||
if(!omatch.empty()) {
|
||||
ignore_underscore_ = false;
|
||||
throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ignore_underscore_ = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::multi_option_policy(MultiOptionPolicy value) {
|
||||
if(value != multi_option_policy_) {
|
||||
if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
|
||||
expected_min_ > 1) { // this bizarre condition is to maintain backwards compatibility
|
||||
// with the previous behavior of expected_ with vectors
|
||||
expected_max_ = expected_min_;
|
||||
}
|
||||
multi_option_policy_ = value;
|
||||
current_option_state_ = option_state::parsing;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_NODISCARD CLI11_INLINE std::string Option::get_name(bool positional, bool all_options) const {
|
||||
if(get_group().empty())
|
||||
return {}; // Hidden
|
||||
|
||||
if(all_options) {
|
||||
|
||||
std::vector<std::string> name_list;
|
||||
|
||||
/// The all list will never include a positional unless asked or that's the only name.
|
||||
if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) {
|
||||
name_list.push_back(pname_);
|
||||
}
|
||||
if((get_items_expected() == 0) && (!fnames_.empty())) {
|
||||
for(const std::string &sname : snames_) {
|
||||
name_list.push_back("-" + sname);
|
||||
if(check_fname(sname)) {
|
||||
name_list.back() += "{" + get_flag_value(sname, "") + "}";
|
||||
}
|
||||
}
|
||||
|
||||
for(const std::string &lname : lnames_) {
|
||||
name_list.push_back("--" + lname);
|
||||
if(check_fname(lname)) {
|
||||
name_list.back() += "{" + get_flag_value(lname, "") + "}";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(const std::string &sname : snames_)
|
||||
name_list.push_back("-" + sname);
|
||||
|
||||
for(const std::string &lname : lnames_)
|
||||
name_list.push_back("--" + lname);
|
||||
}
|
||||
|
||||
return detail::join(name_list);
|
||||
}
|
||||
|
||||
// This returns the positional name no matter what
|
||||
if(positional)
|
||||
return pname_;
|
||||
|
||||
// Prefer long name
|
||||
if(!lnames_.empty())
|
||||
return std::string(2, '-') + lnames_[0];
|
||||
|
||||
// Or short name if no long name
|
||||
if(!snames_.empty())
|
||||
return std::string(1, '-') + snames_[0];
|
||||
|
||||
// If positional is the only name, it's okay to use that
|
||||
return pname_;
|
||||
}
|
||||
|
||||
CLI11_INLINE void Option::run_callback() {
|
||||
if(force_callback_ && results_.empty()) {
|
||||
add_result(default_str_);
|
||||
}
|
||||
if(current_option_state_ == option_state::parsing) {
|
||||
_validate_results(results_);
|
||||
current_option_state_ = option_state::validated;
|
||||
}
|
||||
|
||||
if(current_option_state_ < option_state::reduced) {
|
||||
_reduce_results(proc_results_, results_);
|
||||
current_option_state_ = option_state::reduced;
|
||||
}
|
||||
if(current_option_state_ >= option_state::reduced) {
|
||||
current_option_state_ = option_state::callback_run;
|
||||
if(!(callback_)) {
|
||||
return;
|
||||
}
|
||||
const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
|
||||
bool local_result = callback_(send_results);
|
||||
|
||||
if(!local_result)
|
||||
throw ConversionError(get_name(), results_);
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_NODISCARD CLI11_INLINE const std::string &Option::matching_name(const Option &other) const {
|
||||
static const std::string estring;
|
||||
for(const std::string &sname : snames_)
|
||||
if(other.check_sname(sname))
|
||||
return sname;
|
||||
for(const std::string &lname : lnames_)
|
||||
if(other.check_lname(lname))
|
||||
return lname;
|
||||
|
||||
if(ignore_case_ ||
|
||||
ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore
|
||||
for(const std::string &sname : other.snames_)
|
||||
if(check_sname(sname))
|
||||
return sname;
|
||||
for(const std::string &lname : other.lnames_)
|
||||
if(check_lname(lname))
|
||||
return lname;
|
||||
}
|
||||
return estring;
|
||||
}
|
||||
|
||||
CLI11_NODISCARD CLI11_INLINE bool Option::check_name(const std::string &name) const {
|
||||
|
||||
if(name.length() > 2 && name[0] == '-' && name[1] == '-')
|
||||
return check_lname(name.substr(2));
|
||||
if(name.length() > 1 && name.front() == '-')
|
||||
return check_sname(name.substr(1));
|
||||
if(!pname_.empty()) {
|
||||
std::string local_pname = pname_;
|
||||
std::string local_name = name;
|
||||
if(ignore_underscore_) {
|
||||
local_pname = detail::remove_underscore(local_pname);
|
||||
local_name = detail::remove_underscore(local_name);
|
||||
}
|
||||
if(ignore_case_) {
|
||||
local_pname = detail::to_lower(local_pname);
|
||||
local_name = detail::to_lower(local_name);
|
||||
}
|
||||
if(local_name == local_pname) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!envname_.empty()) {
|
||||
// this needs to be the original since envname_ shouldn't match on case insensitivity
|
||||
return (name == envname_);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::string &name,
|
||||
std::string input_value) const {
|
||||
static const std::string trueString{"true"};
|
||||
static const std::string falseString{"false"};
|
||||
static const std::string emptyString{"{}"};
|
||||
// check for disable flag override_
|
||||
if(disable_flag_override_) {
|
||||
if(!((input_value.empty()) || (input_value == emptyString))) {
|
||||
auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
|
||||
if(default_ind >= 0) {
|
||||
// We can static cast this to std::size_t because it is more than 0 in this block
|
||||
if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
|
||||
throw(ArgumentMismatch::FlagOverride(name));
|
||||
}
|
||||
} else {
|
||||
if(input_value != trueString) {
|
||||
throw(ArgumentMismatch::FlagOverride(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
|
||||
if((input_value.empty()) || (input_value == emptyString)) {
|
||||
if(flag_like_) {
|
||||
return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second;
|
||||
}
|
||||
return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second;
|
||||
}
|
||||
if(ind < 0) {
|
||||
return input_value;
|
||||
}
|
||||
if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
|
||||
try {
|
||||
auto val = detail::to_flag_value(input_value);
|
||||
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
|
||||
} catch(const std::invalid_argument &) {
|
||||
return input_value;
|
||||
}
|
||||
} else {
|
||||
return input_value;
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::add_result(std::string s) {
|
||||
_add_result(std::move(s), results_);
|
||||
current_option_state_ = option_state::parsing;
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::add_result(std::string s, int &results_added) {
|
||||
results_added = _add_result(std::move(s), results_);
|
||||
current_option_state_ = option_state::parsing;
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::add_result(std::vector<std::string> s) {
|
||||
current_option_state_ = option_state::parsing;
|
||||
for(auto &str : s) {
|
||||
_add_result(std::move(str), results_);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_NODISCARD CLI11_INLINE results_t Option::reduced_results() const {
|
||||
results_t res = proc_results_.empty() ? results_ : proc_results_;
|
||||
if(current_option_state_ < option_state::reduced) {
|
||||
if(current_option_state_ == option_state::parsing) {
|
||||
res = results_;
|
||||
_validate_results(res);
|
||||
}
|
||||
if(!res.empty()) {
|
||||
results_t extra;
|
||||
_reduce_results(extra, res);
|
||||
if(!extra.empty()) {
|
||||
res = std::move(extra);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::type_size(int option_type_size) {
|
||||
if(option_type_size < 0) {
|
||||
// this section is included for backwards compatibility
|
||||
type_size_max_ = -option_type_size;
|
||||
type_size_min_ = -option_type_size;
|
||||
expected_max_ = detail::expected_max_vector_size;
|
||||
} else {
|
||||
type_size_max_ = option_type_size;
|
||||
if(type_size_max_ < detail::expected_max_vector_size) {
|
||||
type_size_min_ = option_type_size;
|
||||
} else {
|
||||
inject_separator_ = true;
|
||||
}
|
||||
if(type_size_max_ == 0)
|
||||
required_ = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::type_size(int option_type_size_min, int option_type_size_max) {
|
||||
if(option_type_size_min < 0 || option_type_size_max < 0) {
|
||||
// this section is included for backwards compatibility
|
||||
expected_max_ = detail::expected_max_vector_size;
|
||||
option_type_size_min = (std::abs)(option_type_size_min);
|
||||
option_type_size_max = (std::abs)(option_type_size_max);
|
||||
}
|
||||
|
||||
if(option_type_size_min > option_type_size_max) {
|
||||
type_size_max_ = option_type_size_min;
|
||||
type_size_min_ = option_type_size_max;
|
||||
} else {
|
||||
type_size_min_ = option_type_size_min;
|
||||
type_size_max_ = option_type_size_max;
|
||||
}
|
||||
if(type_size_max_ == 0) {
|
||||
required_ = false;
|
||||
}
|
||||
if(type_size_max_ >= detail::expected_max_vector_size) {
|
||||
inject_separator_ = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_NODISCARD CLI11_INLINE std::string Option::get_type_name() const {
|
||||
std::string full_type_name = type_name_();
|
||||
if(!validators_.empty()) {
|
||||
for(const auto &Validator : validators_) {
|
||||
std::string vtype = Validator.get_description();
|
||||
if(!vtype.empty()) {
|
||||
full_type_name += ":" + vtype;
|
||||
}
|
||||
}
|
||||
}
|
||||
return full_type_name;
|
||||
}
|
||||
|
||||
CLI11_INLINE void Option::_validate_results(results_t &res) const {
|
||||
// Run the Validators (can change the string)
|
||||
if(!validators_.empty()) {
|
||||
if(type_size_max_ > 1) { // in this context index refers to the index in the type
|
||||
int index = 0;
|
||||
if(get_items_expected_max() < static_cast<int>(res.size()) &&
|
||||
multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
|
||||
// create a negative index for the earliest ones
|
||||
index = get_items_expected_max() - static_cast<int>(res.size());
|
||||
}
|
||||
|
||||
for(std::string &result : res) {
|
||||
if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) {
|
||||
index = 0; // reset index for variable size chunks
|
||||
continue;
|
||||
}
|
||||
auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
|
||||
if(!err_msg.empty())
|
||||
throw ValidationError(get_name(), err_msg);
|
||||
++index;
|
||||
}
|
||||
} else {
|
||||
int index = 0;
|
||||
if(expected_max_ < static_cast<int>(res.size()) &&
|
||||
multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
|
||||
// create a negative index for the earliest ones
|
||||
index = expected_max_ - static_cast<int>(res.size());
|
||||
}
|
||||
for(std::string &result : res) {
|
||||
auto err_msg = _validate(result, index);
|
||||
++index;
|
||||
if(!err_msg.empty())
|
||||
throw ValidationError(get_name(), err_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &original) const {
|
||||
|
||||
// max num items expected or length of vector, always at least 1
|
||||
// Only valid for a trimming policy
|
||||
|
||||
out.clear();
|
||||
// Operation depends on the policy setting
|
||||
switch(multi_option_policy_) {
|
||||
case MultiOptionPolicy::TakeAll:
|
||||
break;
|
||||
case MultiOptionPolicy::TakeLast: {
|
||||
// Allow multi-option sizes (including 0)
|
||||
std::size_t trim_size = std::min<std::size_t>(
|
||||
static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
|
||||
if(original.size() != trim_size) {
|
||||
out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
|
||||
}
|
||||
} break;
|
||||
case MultiOptionPolicy::TakeFirst: {
|
||||
std::size_t trim_size = std::min<std::size_t>(
|
||||
static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
|
||||
if(original.size() != trim_size) {
|
||||
out.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size));
|
||||
}
|
||||
} break;
|
||||
case MultiOptionPolicy::Join:
|
||||
if(results_.size() > 1) {
|
||||
out.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
|
||||
}
|
||||
break;
|
||||
case MultiOptionPolicy::Sum:
|
||||
out.push_back(detail::sum_string_vector(original));
|
||||
break;
|
||||
case MultiOptionPolicy::Throw:
|
||||
default: {
|
||||
auto num_min = static_cast<std::size_t>(get_items_expected_min());
|
||||
auto num_max = static_cast<std::size_t>(get_items_expected_max());
|
||||
if(num_min == 0) {
|
||||
num_min = 1;
|
||||
}
|
||||
if(num_max == 0) {
|
||||
num_max = 1;
|
||||
}
|
||||
if(original.size() < num_min) {
|
||||
throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
|
||||
}
|
||||
if(original.size() > num_max) {
|
||||
throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// this check is to allow an empty vector in certain circumstances but not if expected is not zero.
|
||||
// {} is the indicator for an empty container
|
||||
if(out.empty()) {
|
||||
if(original.size() == 1 && original[0] == "{}" && get_items_expected_min() > 0) {
|
||||
out.push_back("{}");
|
||||
out.push_back("%%");
|
||||
}
|
||||
} else if(out.size() == 1 && out[0] == "{}" && get_items_expected_min() > 0) {
|
||||
out.push_back("%%");
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE std::string Option::_validate(std::string &result, int index) const {
|
||||
std::string err_msg;
|
||||
if(result.empty() && expected_min_ == 0) {
|
||||
// an empty with nothing expected is allowed
|
||||
return err_msg;
|
||||
}
|
||||
for(const auto &vali : validators_) {
|
||||
auto v = vali.get_application_index();
|
||||
if(v == -1 || v == index) {
|
||||
try {
|
||||
err_msg = vali(result);
|
||||
} catch(const ValidationError &err) {
|
||||
err_msg = err.what();
|
||||
}
|
||||
if(!err_msg.empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return err_msg;
|
||||
}
|
||||
|
||||
CLI11_INLINE int Option::_add_result(std::string &&result, std::vector<std::string> &res) const {
|
||||
int result_count = 0;
|
||||
if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
|
||||
result.back() == ']') { // this is now a vector string likely from the default or user entry
|
||||
result.pop_back();
|
||||
|
||||
for(auto &var : CLI::detail::split(result.substr(1), ',')) {
|
||||
if(!var.empty()) {
|
||||
result_count += _add_result(std::move(var), res);
|
||||
}
|
||||
}
|
||||
return result_count;
|
||||
}
|
||||
if(delimiter_ == '\0') {
|
||||
res.push_back(std::move(result));
|
||||
++result_count;
|
||||
} else {
|
||||
if((result.find_first_of(delimiter_) != std::string::npos)) {
|
||||
for(const auto &var : CLI::detail::split(result, delimiter_)) {
|
||||
if(!var.empty()) {
|
||||
res.push_back(var);
|
||||
++result_count;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.push_back(std::move(result));
|
||||
++result_count;
|
||||
}
|
||||
}
|
||||
return result_count;
|
||||
}
|
||||
// [CLI11:option_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
139
include/CLI/impl/Split_inl.hpp
Normal file
139
include/CLI/impl/Split_inl.hpp
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// This include is only needed for IDEs to discover symbols
|
||||
#include <CLI/Split.hpp>
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
#include <CLI/Error.hpp>
|
||||
#include <CLI/StringTools.hpp>
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:split_inl_hpp:verbatim]
|
||||
|
||||
namespace detail {
|
||||
|
||||
CLI11_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_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_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 std::vector<std::string> split_names(std::string current) {
|
||||
std::vector<std::string> output;
|
||||
std::size_t val = 0;
|
||||
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::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); // NOLINT(readability-suspicious-call-argument)
|
||||
}
|
||||
flag.erase(0, flag.find_first_not_of("-!"));
|
||||
output.emplace_back(flag, defval);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
CLI11_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::make_tuple(short_names, long_names, pos_name);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
// [CLI11:split_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
260
include/CLI/impl/StringTools_inl.hpp
Normal file
260
include/CLI/impl/StringTools_inl.hpp
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// This include is only needed for IDEs to discover symbols
|
||||
#include <CLI/StringTools.hpp>
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <string>
|
||||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:string_tools_inl_hpp:verbatim]
|
||||
|
||||
namespace detail {
|
||||
CLI11_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::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, 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 &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, 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 &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 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::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_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 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 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 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 std::ptrdiff_t
|
||||
find_member(std::string name, const std::vector<std::string> names, bool ignore_case, bool ignore_underscore) {
|
||||
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::vector<std::string> split_up(std::string str, char delimiter) {
|
||||
|
||||
const std::string delims("\'\"`");
|
||||
auto find_ws = [delimiter](char ch) {
|
||||
return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (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::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::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;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
// [CLI11:string_tools_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
346
include/CLI/impl/Validators_inl.hpp
Normal file
346
include/CLI/impl/Validators_inl.hpp
Normal file
@@ -0,0 +1,346 @@
|
||||
// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CLI/Validators.hpp>
|
||||
|
||||
#include <CLI/Macros.hpp>
|
||||
#include <CLI/StringTools.hpp>
|
||||
#include <CLI/TypeTools.hpp>
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:validators_inl_hpp:verbatim]
|
||||
|
||||
CLI11_INLINE std::string Validator::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;
|
||||
}
|
||||
|
||||
CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const {
|
||||
Validator newval(*this);
|
||||
newval.desc_function_ = [validator_desc]() { return validator_desc; };
|
||||
return newval;
|
||||
}
|
||||
|
||||
CLI11_INLINE Validator 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 + ")";
|
||||
return s1 + s2;
|
||||
};
|
||||
|
||||
newval.active_ = active_ && other.active_;
|
||||
newval.application_index_ = application_index_;
|
||||
return newval;
|
||||
}
|
||||
|
||||
CLI11_INLINE Validator 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;
|
||||
}
|
||||
|
||||
CLI11_INLINE Validator 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;
|
||||
}
|
||||
|
||||
CLI11_INLINE void
|
||||
Validator::_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 detail {
|
||||
|
||||
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
|
||||
CLI11_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
|
||||
CLI11_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 ExistingFileValidator::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();
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE ExistingDirectoryValidator::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();
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE ExistingPathValidator::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();
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE NonexistentPathValidator::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();
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE IPV4Validator::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 = 0;
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn)
|
||||
: 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{};
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE AsSizeValue::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), ...]");
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::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;
|
||||
}
|
||||
|
||||
CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::get_mapping(bool kb_is_1000) {
|
||||
if(kb_is_1000) {
|
||||
static auto m = init_mapping(true);
|
||||
return m;
|
||||
}
|
||||
static auto m = init_mapping(false);
|
||||
return m;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
CLI11_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 < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{};
|
||||
ltrim(vals.second);
|
||||
return vals;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
/// @}
|
||||
|
||||
// [CLI11:validators_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
Reference in New Issue
Block a user