diff --git a/cppargparse.cc b/cppargparse.cc index 31be787..b6544aa 100644 --- a/cppargparse.cc +++ b/cppargparse.cc @@ -1,461 +1,471 @@ /** * @file cppargparse.cc * * @author Nicolas Richart * * @date creation: Thu Apr 03 2014 * @date last modification: Mon Sep 15 2014 * * @brief implementation of the ArgumentParser * * @section LICENSE * * Copyright (©) 2014 EPFL (Ecole Polytechnique Fédérale de Lausanne) * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * Akantu is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * Akantu is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with Akantu. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "cppargparse.hh" #include #include #include #include #include #include #include #include #include #include #include +#include namespace cppargparse { /* -------------------------------------------------------------------------- */ static inline std::string to_upper(const std::string & str) { std::string lstr = str; std::transform(lstr.begin(), lstr.end(), lstr.begin(), (int(*)(int))std::toupper); return lstr; } /* -------------------------------------------------------------------------- */ /* ArgumentParser */ /* -------------------------------------------------------------------------- */ ArgumentParser::ArgumentParser() : external_exit(NULL), prank(0), psize(1) { this->addArgument("-h;--help", "show this help message and exit", 0, _boolean, false, true); } /* -------------------------------------------------------------------------- */ ArgumentParser::~ArgumentParser() { for(_Arguments::iterator it = arguments.begin(); it != arguments.end(); ++it) { delete it->second; } } /* -------------------------------------------------------------------------- */ void ArgumentParser::setParallelContext(int prank, int psize) { this->prank = prank; this->psize = psize; } /* -------------------------------------------------------------------------- */ void ArgumentParser::_exit(const std::string & msg, int status) { if(prank == 0) { if(msg != "") { std::cerr << msg << std::endl; std::cerr << std::endl; } this->print_help(std::cerr); } if(external_exit) (*external_exit)(status); else { exit(status); } } /* -------------------------------------------------------------------------- */ const ArgumentParser::Argument & ArgumentParser::operator[](const std::string & name) const { Arguments::const_iterator it = success_parsed.find(name); if(it != success_parsed.end()) { return *(it->second); } else { throw std::range_error("No argument named \'" + name + "\' was found in the parsed argument," + " make sur to specify it \'required\'" + " or to give it a default value"); } } /* -------------------------------------------------------------------------- */ bool ArgumentParser::has(const std::string & name) const { return (success_parsed.find(name) != success_parsed.end()); } /* -------------------------------------------------------------------------- */ void ArgumentParser::addArgument(const std::string & name_or_flag, const std::string & help, int nargs, ArgumentType type) { _addArgument(name_or_flag, help, nargs, type); } /* -------------------------------------------------------------------------- */ ArgumentParser::_Argument & ArgumentParser::_addArgument(const std::string & name, const std::string & help, int nargs, ArgumentType type) { _Argument * arg = NULL; switch(type) { case _string: { arg = new ArgumentStorage(); break; } case _float: { arg = new ArgumentStorage(); break; } case _integer: { arg = new ArgumentStorage(); break; } case _boolean: { arg = new ArgumentStorage(); break; } } arg->help = help; arg->nargs = nargs; arg->type = type; std::stringstream sstr(name); std::string item; std::vector tmp_keys; while (std::getline(sstr, item, ';')) { tmp_keys.push_back(item); } int long_key = -1; int short_key = -1; bool problem = (tmp_keys.size() > 2) || (name == ""); for (std::vector::iterator it = tmp_keys.begin(); it != tmp_keys.end(); ++it) { if(it->find("--") == 0) { problem |= (long_key != -1); long_key = it - tmp_keys.begin(); } else if(it->find("-") == 0) { problem |= (long_key != -1); short_key = it - tmp_keys.begin(); } } problem |= ((tmp_keys.size() == 2) && (long_key == -1 || short_key == -1)); if(problem) { throw std::invalid_argument("Synthax of name or flags is not correct. Possible synthax are \'-f\', \'-f;--foo\', \'--foo\', \'bar\'"); } if(long_key != -1) { arg->name = tmp_keys[long_key]; arg->name.erase(0, 2); } else if(short_key != -1) { arg->name = tmp_keys[short_key]; arg->name.erase(0, 1); } else { arg->name = tmp_keys[0]; pos_args.push_back(arg); arg->required = true; arg->is_positional = true; } arguments[arg->name] = arg; if(!arg->is_positional) { if(short_key != -1) { std::string key = tmp_keys[short_key]; key_args[key] = arg; arg->keys.push_back(key); } if(long_key != -1) { std::string key = tmp_keys[long_key]; key_args[key] = arg; arg->keys.push_back(key); } } return *arg; } +#if not HAVE_STRDUP +static char * strdup(const char * str) { + size_t len = strlen(str); + char *x = (char *)malloc(len+1); /* 1 for the null terminator */ + if(!x) return NULL; /* malloc could not allocate memory */ + memcpy(x,str,len+1); /* copy the string into the new buffer */ + return x; +} +#endif /* -------------------------------------------------------------------------- */ void ArgumentParser::parse(int& argc, char**& argv, int flags, bool parse_help) { bool stop_in_not_parsed = flags & _stop_on_not_parsed; bool remove_parsed = flags & _remove_parsed; std::vector argvs; for (int i = 0; i < argc; ++i) { argvs.push_back(std::string(argv[i])); } unsigned int current_position = 0; if(this->program_name == "" && argc > 0) { std::string prog = argvs[current_position]; const char * c_prog = prog.c_str(); char * c_prog_tmp = strdup(c_prog); std::string base_prog(basename(c_prog_tmp)); this->program_name = base_prog; std::free(c_prog_tmp); } std::queue<_Argument *> positional_queue; for(PositionalArgument::iterator it = pos_args.begin(); it != pos_args.end(); ++it) positional_queue.push(*it); std::vector argvs_to_remove; ++current_position; // consume argv[0] while(current_position < argvs.size()) { std::string arg = argvs[current_position]; ++current_position; ArgumentKeyMap::iterator key_it = key_args.find(arg); bool is_positional = false; _Argument * argument_ptr = NULL; if(key_it == key_args.end()) { if(positional_queue.empty()) { if(stop_in_not_parsed) this->_exit("Argument " + arg + " not recognized", EXIT_FAILURE); continue; } else { argument_ptr = positional_queue.front(); is_positional = true; --current_position; } } else { argument_ptr = key_it->second; } if(remove_parsed && ! is_positional && argument_ptr->name != "help") { argvs_to_remove.push_back(current_position - 1); } _Argument & argument = *argument_ptr; unsigned int min_nb_val = 0, max_nb_val = 0; switch(argument.nargs) { case _one_if_possible: max_nb_val = 1; break; // "?" case _at_least_one: min_nb_val = 1; // "+" case _any: max_nb_val = argc - current_position; break; // "*" default: min_nb_val = max_nb_val = argument.nargs; // "N" } std::vector values; unsigned int arg_consumed = 0; if(max_nb_val <= (argc - current_position)) { for(; arg_consumed < max_nb_val; ++arg_consumed) { std::string v = argvs[current_position]; ++current_position; bool is_key = key_args.find(v) != key_args.end(); bool is_good_type = checkType(argument.type, v); if(!is_key && is_good_type) { values.push_back(v); if(remove_parsed) argvs_to_remove.push_back(current_position - 1); } else { // unconsume not parsed argument for optional if(!is_positional || (is_positional && is_key)) --current_position; break; } } } if(arg_consumed < min_nb_val) { if(!is_positional) { this->_exit("Not enought values for the argument " + argument.name + " where provided", EXIT_FAILURE); } else { if(stop_in_not_parsed) this->_exit("Argument " + arg + " not recognized", EXIT_FAILURE); } } else { if (is_positional) positional_queue.pop(); if(!argument.parsed) { success_parsed[argument.name] = &argument; argument.parsed = true; if((argument.nargs == _one_if_possible || argument.nargs == 0) && arg_consumed == 0) { if(argument.has_const) argument.setToConst(); else if(argument.has_default) argument.setToDefault(); } else { argument.setValues(values); } } else { this->_exit("Argument " + argument.name + " already present in the list of argument", EXIT_FAILURE); } } } for (_Arguments::iterator ait = arguments.begin(); ait != arguments.end(); ++ait) { _Argument & argument = *(ait->second); if(!argument.parsed) { if(argument.has_default) { argument.setToDefault(); success_parsed[argument.name] = &argument; } if(argument.required) { this->_exit("Argument " + argument.name + " required but not given!", EXIT_FAILURE); } } } // removing the parsed argument if remove_parsed is true if(argvs_to_remove.size()) { std::vector::const_iterator next_to_remove = argvs_to_remove.begin(); for (int i = 0, c = 0; i < argc; ++i) { if(next_to_remove == argvs_to_remove.end() || i != *next_to_remove) { argv[c] = argv[i]; ++c; } else { if (next_to_remove != argvs_to_remove.end()) ++next_to_remove; } } argc -= argvs_to_remove.size(); } if(this->arguments["help"]->parsed && parse_help) { this->_exit(); } } /* -------------------------------------------------------------------------- */ bool ArgumentParser::checkType(ArgumentType type, const std::string & value) const { std::stringstream sstr(value); switch(type) { case _string: { std::string s; sstr >> s; break; } case _float: { double d; sstr >> d; break; } case _integer: { int i; sstr >> i; break; } case _boolean: { bool b; sstr >> b; break; } } return (sstr.fail() == false); } /* -------------------------------------------------------------------------- */ void ArgumentParser::printself(std::ostream & stream) const { for(Arguments::const_iterator it = success_parsed.begin(); it != success_parsed.end(); ++it) { const Argument & argument = *(it->second); argument.printself(stream); stream << std::endl; } } /* -------------------------------------------------------------------------- */ void ArgumentParser::print_usage(std::ostream & stream) const { stream << "Usage: " << this->program_name; std::stringstream sstr_opt; // print shorten usage for(_Arguments::const_iterator it = arguments.begin(); it != arguments.end(); ++it) { const _Argument & argument = *(it->second); if(!argument.is_positional) { if(!argument.required) stream << " ["; stream << argument.keys[0]; this->print_usage_nargs(stream, argument); if(!argument.required) stream << "]"; } } for(PositionalArgument::const_iterator it = pos_args.begin(); it != pos_args.end(); ++it) { const _Argument & argument = **it; this->print_usage_nargs(stream, argument); } stream << std::endl; } /* -------------------------------------------------------------------------- */ void ArgumentParser::print_usage_nargs(std::ostream & stream, const _Argument & argument) const { std::string u_name = to_upper(argument.name); switch(argument.nargs) { case _one_if_possible: stream << " [" << u_name << "]"; break; case _at_least_one: stream << " " << u_name; case _any: stream << " [" << u_name << " ...]"; break; default: for(int i = 0; i < argument.nargs; ++i) { stream << " " << u_name; } } } void ArgumentParser::print_help(std::ostream & stream) const { this->print_usage(stream); if(!pos_args.empty()) { stream << std::endl; stream << "positional arguments:" << std::endl; for(PositionalArgument::const_iterator it = pos_args.begin(); it != pos_args.end(); ++it) { const _Argument & argument = **it; this->print_help_argument(stream, argument); } } if(!key_args.empty()) { stream << std::endl; stream << "optional arguments:" << std::endl; for(_Arguments::const_iterator it = arguments.begin(); it != arguments.end(); ++it) { const _Argument & argument = *(it->second); if(!argument.is_positional) { this->print_help_argument(stream, argument); } } } } void ArgumentParser::print_help_argument(std::ostream & stream, const _Argument & argument) const { std::string key(""); if(argument.is_positional) key = argument.name; else { std::stringstream sstr; for(unsigned int i = 0; i < argument.keys.size(); ++i) { if(i != 0) sstr << ", "; sstr << argument.keys[i]; this->print_usage_nargs(sstr, argument); } key = sstr.str(); } stream << " " << std::left << std::setw(15) << key << " " << argument.help; argument.printDefault(stream); stream << std::endl; } }