OpenFPGA/libs/EXTERNAL/libargparse/src/argparse.cpp

589 lines
21 KiB
C++

#include <algorithm>
#include <array>
#include <list>
#include <cassert>
#include <string>
#include <set>
#include "argparse.hpp"
#include "argparse_util.hpp"
namespace argparse {
/*
* ArgumentParser
*/
ArgumentParser::ArgumentParser(std::string prog_name, std::string description_str, std::ostream& os)
: description_(description_str)
, formatter_(new DefaultFormatter())
, os_(os)
{
prog(prog_name);
argument_groups_.push_back(ArgumentGroup("arguments"));
}
ArgumentParser& ArgumentParser::prog(std::string prog_name, bool basename_only) {
if (basename_only) {
prog_ = basename(prog_name);
} else {
prog_ = prog_name;
}
return *this;
}
ArgumentParser& ArgumentParser::version(std::string version_str) {
version_ = version_str;
return *this;
}
ArgumentParser& ArgumentParser::epilog(std::string epilog_str) {
epilog_ = epilog_str;
return *this;
}
ArgumentGroup& ArgumentParser::add_argument_group(std::string description_str) {
argument_groups_.push_back(ArgumentGroup(description_str));
return argument_groups_[argument_groups_.size() - 1];
}
void ArgumentParser::parse_args(int argc, const char* const* argv, int error_exit_code, int help_exit_code, int version_exit_code) {
try {
parse_args_throw(argc, argv);
} catch (const argparse::ArgParseHelp&) {
//Help requested
print_help();
std::exit(help_exit_code);
} catch (const argparse::ArgParseVersion&) {
print_version();
std::exit(version_exit_code);
} catch (const argparse::ArgParseError& e) {
//Failed to parse
std::cout << e.what() << "\n";
std::cout << "\n";
print_usage();
std::exit(error_exit_code);
}
}
void ArgumentParser::parse_args_throw(int argc, const char* const* argv) {
std::vector<std::string> arg_strs;
for (int i = 1; i < argc; ++i) {
arg_strs.push_back(argv[i]);
}
parse_args_throw(arg_strs);
}
void ArgumentParser::parse_args_throw(std::vector<std::string> arg_strs) {
add_help_option_if_unspecified();
//Reset all the defaults
for (const auto& group : argument_groups()) {
for (const auto& arg : group.arguments()) {
if (arg->default_set()) {
arg->set_dest_to_default();
}
}
}
//Create a look-up of expected argument strings and positional arguments
std::map<std::string,std::shared_ptr<Argument>> str_to_option_arg;
std::list<std::shared_ptr<Argument>> positional_args;
for (const auto& group : argument_groups()) {
for (const auto& arg : group.arguments()) {
if (arg->positional()) {
positional_args.push_back(arg);
} else {
for (const auto& opt : {arg->long_option(), arg->short_option()}) {
if (opt.empty()) continue;
auto ret = str_to_option_arg.insert(std::make_pair(opt, arg));
if (!ret.second) {
//Option string already specified
std::stringstream ss;
ss << "Option string '" << opt << "' maps to multiple options";
throw ArgParseError(ss.str());
}
}
}
}
}
std::set<std::shared_ptr<Argument>> specified_arguments;
//Process the arguments
for (size_t i = 0; i < arg_strs.size(); i++) {
ShortArgInfo short_arg_info = no_space_short_arg(arg_strs[i], str_to_option_arg);
std::shared_ptr<Argument> arg;
if (short_arg_info.is_no_space_short_arg) {
//Short argument with no space between value
arg = short_arg_info.arg;
} else { //Full argument
auto iter = str_to_option_arg.find(arg_strs[i]);
if (iter != str_to_option_arg.end()) {
arg = iter->second;
}
}
if (arg) {
//Start of an argument
specified_arguments.insert(arg);
if (arg->action() == Action::STORE_TRUE) {
arg->set_dest_to_true();
} else if (arg->action() == Action::STORE_FALSE) {
arg->set_dest_to_false();
} else if (arg->action() == Action::HELP) {
arg->set_dest_to_true();
throw ArgParseHelp();
} else if (arg->action() == Action::VERSION) {
arg->set_dest_to_true();
throw ArgParseVersion();
} else {
assert(arg->action() == Action::STORE);
size_t max_values_to_read = 0;
size_t min_values_to_read = 0;
if (arg->nargs() == '1') {
max_values_to_read = 1;
min_values_to_read = 1;
} else if (arg->nargs() == '?') {
max_values_to_read = 1;
min_values_to_read = 0;
} else if (arg->nargs() == '*') {
max_values_to_read = std::numeric_limits<size_t>::max();
min_values_to_read = 0;
} else {
assert (arg->nargs() == '+');
max_values_to_read = std::numeric_limits<size_t>::max();
min_values_to_read = 1;
}
std::vector<std::string> values;
size_t nargs_read = 0;
if (short_arg_info.is_no_space_short_arg) {
//It is a short argument, we already have the first value
if (!short_arg_info.value.empty()) {
values.push_back(short_arg_info.value);
++nargs_read;
}
}
for (; nargs_read < max_values_to_read; ++nargs_read) {
size_t next_idx = i + 1 + nargs_read;
if (next_idx >= arg_strs.size()) {
break;
}
std::string str = arg_strs[next_idx];
if (is_argument(str, str_to_option_arg)) break;
if (!arg->is_valid_value(str)) break;
values.push_back(str);
}
if (nargs_read < min_values_to_read) {
if (arg->nargs() == '1') {
std::stringstream msg;
msg << "Missing expected argument for " << arg_strs[i] << "";
throw ArgParseError(msg.str());
} else {
std::stringstream msg;
msg << "Expected at least " << min_values_to_read << " value";
if (min_values_to_read > 1) {
msg << "s";
}
msg << " for argument '" << arg_strs[i] << "'";
msg << " (found " << values.size() << ")";
throw ArgParseError(msg.str());
}
}
assert (nargs_read <= max_values_to_read);
for (auto val : values) {
if (!is_valid_choice(val, arg->choices())) {
std::stringstream msg;
msg << "Unexpected option value '" << values[0] << "' (expected one of: " << join(arg->choices(), ", ");
msg << ") for " << arg->name();
throw ArgParseError(msg.str());
}
}
//Set the option values appropriately
if (arg->nargs() == '1') {
assert(nargs_read == 1);
assert(values.size() == 1);
try {
arg->set_dest_to_value(values[0]);
} catch (const ArgParseConversionError& e) {
std::stringstream msg;
msg << e.what() << " for " << arg->long_option();
auto short_opt = arg->short_option();
if (!short_opt.empty()) {
msg << "/" << short_opt;
}
throw ArgParseConversionError(msg.str());
}
} else if (arg->nargs() == '+' || arg->nargs() == '*') {
if (arg->nargs() == '+') {
assert(nargs_read >= 1);
assert(values.size() >= 1);
}
for (auto value : values) {
try {
arg->add_value_to_dest(value);
} catch (const ArgParseConversionError& e) {
std::stringstream msg;
msg << e.what() << " for " << arg->long_option();
auto short_opt = arg->short_option();
if (!short_opt.empty()) {
msg << "/" << short_opt;
}
throw ArgParseConversionError(msg.str());
}
}
} else {
std::stringstream msg;
msg << "Unsupport nargs value '" << arg->nargs() << "'";
throw ArgParseError(msg.str());
}
if (!short_arg_info.is_no_space_short_arg) {
i += nargs_read; //Skip over the values (don't need to for short args)
}
}
} else {
if (positional_args.empty()) {
//Unrecognized
std::stringstream ss;
ss << "Unexpected command-line argument '" << arg_strs[i] << "'";
throw ArgParseError(ss.str());
} else {
//Positional argument
auto pos_arg = positional_args.front();
positional_args.pop_front();
try {
pos_arg->set_dest_to_value(arg_strs[i]);
} catch (const ArgParseConversionError& e) {
std::stringstream msg;
msg << e.what() << " for positional argument " << pos_arg->long_option();
throw ArgParseConversionError(msg.str());
}
auto value = arg_strs[i];
specified_arguments.insert(pos_arg);
}
}
}
//Missing positionals?
for(const auto& remaining_positional : positional_args) {
std::stringstream ss;
ss << "Missing required positional argument: " << remaining_positional->long_option();
throw ArgParseError(ss.str());
}
//Missing required?
for (const auto& group : argument_groups()) {
for (const auto& arg : group.arguments()) {
if (arg->required()) {
//potentially slow...
if (!specified_arguments.count(arg)) {
std::stringstream msg;
msg << "Missing required argument: " << arg->long_option();
auto short_opt = arg->short_option();
if (!short_opt.empty()) {
msg << "/" << short_opt;
}
throw ArgParseError(msg.str());
}
}
}
}
}
void ArgumentParser::reset_destinations() {
for (const auto& group : argument_groups()) {
for (const auto& arg : group.arguments()) {
arg->reset_dest();
}
}
}
void ArgumentParser::print_usage() {
formatter_->set_parser(this);
os_ << formatter_->format_usage();
}
void ArgumentParser::print_help() {
formatter_->set_parser(this);
os_ << formatter_->format_usage();
os_ << formatter_->format_description();
os_ << formatter_->format_arguments();
os_ << formatter_->format_epilog();
}
void ArgumentParser::print_version() {
formatter_->set_parser(this);
os_ << formatter_->format_version();
}
std::string ArgumentParser::prog() const { return prog_; }
std::string ArgumentParser::version() const { return version_; }
std::string ArgumentParser::description() const { return description_; }
std::string ArgumentParser::epilog() const { return epilog_; }
std::vector<ArgumentGroup> ArgumentParser::argument_groups() const { return argument_groups_; }
void ArgumentParser::add_help_option_if_unspecified() {
//Has a help already been specified
bool found_help = false;
for(auto& grp : argument_groups_) {
for(auto& arg : grp.arguments()) {
if(arg->action() == Action::HELP) {
found_help = true;
break;
}
}
}
if (!found_help) {
auto& grp = argument_groups_[0];
grp.add_argument(show_help_dummy_, "--help", "-h")
.help("Shows this help message")
.action(Action::HELP);
}
}
ArgumentParser::ShortArgInfo ArgumentParser::no_space_short_arg(std::string str, const std::map<std::string, std::shared_ptr<Argument>>& str_to_option_arg) const {
ShortArgInfo short_arg_info;
for(auto kv : str_to_option_arg) {
if (kv.first.size() == 2) {
//Is a short arg
//String starts with short arg
bool match = true;
for (size_t i = 0; i < kv.first.size(); ++i) {
if (kv.first[i] != str[i]) {
match = false;
break;
}
}
bool no_space_between_short_arg_and_value = str.size() > kv.first.size();
if (match && no_space_between_short_arg_and_value) {
//Only handles cases where there is no space between short arg and value
short_arg_info.is_no_space_short_arg = true;
short_arg_info.arg = kv.second;
short_arg_info.value = std::string(str.begin() + kv.first.size(), str.end());
return short_arg_info;
}
}
}
assert(!short_arg_info.is_no_space_short_arg);
return short_arg_info;
}
/*
* ArgumentGroup
*/
ArgumentGroup::ArgumentGroup(std::string name_str)
: name_(name_str)
{}
ArgumentGroup& ArgumentGroup::epilog(std::string str) {
epilog_ = str;
return *this;
}
std::string ArgumentGroup::name() const { return name_; }
std::string ArgumentGroup::epilog() const { return epilog_; }
const std::vector<std::shared_ptr<Argument>>& ArgumentGroup::arguments() const { return arguments_; }
/*
* Argument
*/
Argument::Argument(std::string long_opt, std::string short_opt)
: long_opt_(long_opt)
, short_opt_(short_opt) {
if (long_opt_.size() < 1) {
throw ArgParseError("Argument must be at least one character long");
}
auto dashes_name = split_leading_dashes(long_opt_);
if (dashes_name[0].size() == 1 && !short_opt_.empty()) {
throw ArgParseError("Long option must be specified before short option");
} else if (dashes_name[0].size() > 2) {
throw ArgParseError("More than two dashes in argument name");
}
//Set defaults
metavar_ = toupper(dashes_name[1]);
}
Argument& Argument::help(std::string help_str) {
help_ = help_str;
return *this;
}
Argument& Argument::nargs(char nargs_type) {
//TODO: nargs > 1 support: '?', '*', '+'
auto valid_nargs = {'0', '1', '+', '*'};
auto iter = std::find(valid_nargs.begin(), valid_nargs.end(), nargs_type);
if (iter == valid_nargs.end()) {
throw ArgParseError("Invalid argument to nargs (must be one of: " + join(valid_nargs, ", ") + ")");
}
//Ensure nargs is consistent with the action
if (action() == Action::STORE_FALSE && nargs_type != '0') {
throw ArgParseError("STORE_FALSE action requires nargs to be '0'");
} else if (action() == Action::STORE_TRUE && nargs_type != '0') {
throw ArgParseError("STORE_TRUE action requires nargs to be '0'");
} else if (action() == Action::HELP && nargs_type != '0') {
throw ArgParseError("HELP action requires nargs to be '0'");
} else if (action() == Action::STORE && (nargs_type != '1' && nargs_type != '+' && nargs_type != '*')) {
throw ArgParseError("STORE action requires nargs to be '1', '+' or '*'");
}
nargs_ = nargs_type;
valid_action();
return *this;
}
Argument& Argument::metavar(std::string metavar_str) {
metavar_ = metavar_str;
return *this;
}
Argument& Argument::choices(std::vector<std::string> choice_values) {
choices_ = choice_values;
return *this;
}
Argument& Argument::action(Action action_type) {
action_ = action_type;
if ( action_ == Action::STORE_FALSE
|| action_ == Action::STORE_TRUE
|| action_ == Action::HELP
|| action_ == Action::VERSION) {
this->nargs('0');
} else if (action_ == Action::STORE) {
this->nargs('1');
} else {
throw ArgParseError("Unrecognized argparse action");
}
return *this;
}
Argument& Argument::required(bool is_required) {
required_ = is_required;
return *this;
}
Argument& Argument::default_value(const std::string& value) {
if (nargs() != '0' && nargs() != '1' && nargs() != '?') {
std::stringstream msg;
msg << "Scalar default value not allowed for nargs='" << nargs() << "'";
throw ArgParseError(msg.str());
}
default_value_.clear();
default_value_.push_back(value);
default_set_ = true;
return *this;
}
Argument& Argument::default_value(const std::vector<std::string>& values) {
if (nargs() != '+' && nargs() != '*') {
std::stringstream msg;
msg << "Multiple default value not allowed for nargs='" << nargs() << "'";
throw ArgParseError(msg.str());
}
default_value_ = values;
default_set_ = true;
return *this;
}
Argument& Argument::default_value(const std::initializer_list<std::string>& values) {
//Convert to vector and process as usual
return default_value(std::vector<std::string>(values.begin(), values.end()));
}
Argument& Argument::group_name(std::string grp) {
group_name_ = grp;
return *this;
}
Argument& Argument::show_in(ShowIn show) {
show_in_ = show;
return *this;
}
std::string Argument::name() const {
std::string name_str = long_option();
if (!short_option().empty()) {
name_str += "/" + short_option();
}
return name_str;
}
std::string Argument::long_option() const { return long_opt_; }
std::string Argument::short_option() const { return short_opt_; }
std::string Argument::help() const { return help_; }
char Argument::nargs() const { return nargs_; }
std::string Argument::metavar() const { return metavar_; }
std::vector<std::string> Argument::choices() const { return choices_; }
Action Argument::action() const { return action_; }
std::string Argument::default_value() const {
if (default_value_.size() > 1) {
std::stringstream msg;
msg << "{" << join(default_value_, ", ") << "}";
return msg.str();
} else if (default_value_.size() == 1) {
return default_value_[0];
} else {
return "";
}
}
std::string Argument::group_name() const { return group_name_; }
ShowIn Argument::show_in() const { return show_in_; }
bool Argument::default_set() const { return default_set_; }
bool Argument::required() const {
if(positional()) {
//Positional arguments are always required
return true;
}
return required_;
}
bool Argument::positional() const {
assert(long_option().size() > 1);
return long_option()[0] != '-';
}
} //namespace