From dba6b1c91d2ff7d901d855fe2e0ddaf65424d1af Mon Sep 17 00:00:00 2001 From: Denes Matetelki Date: Sun, 16 Oct 2011 16:41:41 +0200 Subject: [PATCH] ArgParse choices implemented --- include/ArgParse.hpp | 49 ++++---- src/ArgParse.cpp | 263 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 239 insertions(+), 73 deletions(-) diff --git a/include/ArgParse.hpp b/include/ArgParse.hpp index 65a0e92..e7dbc28 100644 --- a/include/ArgParse.hpp +++ b/include/ArgParse.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include // Aims for the functionality of Python argparse // http://docs.python.org/library/argparse.html#module-argparse @@ -14,11 +14,11 @@ class ArgParse public: enum ValueType { - TYPE_NONE, - TYPE_STRING, - TYPE_INT, - TYPE_DOUBLE, - TYPE_BOOL + NONE, + STRING, + INT, + DOUBLE, + BOOL }; enum ValueRequired { @@ -32,16 +32,15 @@ public: void addArgument(const std::string name, // "-f,--foo" const std::string help, - const enum ValueType type = TYPE_NONE, + const enum ValueType type = NONE, const enum ValueRequired valueRequired = REQUIRED, const std::string valueName = std::string(""), - const std::string choices = std::string("")); // "yes,no" or range: [1..100] - + const std::string choices = std::string("")); // {"yes,no"} or range: {1..100} + // throw(std::runtime_error) bool parseArgs(const int argc, - const char* argv[]) throw(std::runtime_error); - - bool parseArgs(const std::list argList) throw(std::runtime_error); + const char* argv[]); + bool parseArgs(const std::list argList); // is this arg in the map of understood arguments? bool isArg(const std::string arg) const; @@ -73,30 +72,32 @@ private: bool m_valueHasBeenSet; Argument (const std::string help, - const enum ValueType type = TYPE_NONE, + const enum ValueType type = NONE, const enum ValueRequired valueRequired = REQUIRED, const std::string valueName = std::string(""), const std::string choices = std::string(""), - const std::string value = std::string("")) - : m_help(help) - , m_type(type) - , m_valueRequired(valueRequired) - , m_valueName(valueName) - , m_choices(choices) - , m_value(value) - , m_found(false) - , m_valueHasBeenSet(false) {}; + const std::string value = std::string("")); + }; + + class argCompare { + public: + // short and long arg shall be compared with same amount of dashes + bool operator()(const std::string a,const std::string b) const; }; + typedef std::map ArgMap; + // arg is just the shor or long form: "-h" or "--help" - std::map::iterator findElement(const std::string param); + ArgMap::iterator findElement(const std::string param); + std::set parseCommaSepStringToSet(const std::string s) const; + std::string typeToString(const ValueType type, const std::string valueName) const; std::string m_description; std::string m_epilog; std::string m_programName; - std::map m_params; + ArgMap m_params; }; // class ArgParse diff --git a/src/ArgParse.cpp b/src/ArgParse.cpp index 1eb5033..e00d6aa 100644 --- a/src/ArgParse.cpp +++ b/src/ArgParse.cpp @@ -6,9 +6,14 @@ #include #include +#include + +#include + #include + ArgParse::ArgParse(const std::string description, const std::string epilog, const bool add_Help) @@ -29,13 +34,14 @@ void ArgParse::addArgument(const std::string arg, const std::string valueName, const std::string choices) { - Argument argument(help, type, valueRequired, valueName, choices, ""); + + Argument argument(help, type, valueRequired, typeToString(type, valueName), choices, ""); m_params.insert(std::pair(arg, argument)); } bool ArgParse::parseArgs(const int argc, - const char* argv[]) throw(std::runtime_error) + const char* argv[]) { std::list argList; for (int i = 0; i < argc; ++i ) @@ -45,7 +51,7 @@ bool ArgParse::parseArgs(const int argc, } -bool ArgParse::parseArgs(const std::list argList) throw(std::runtime_error) +bool ArgParse::parseArgs(const std::list argList) { m_programName = argList.front(); @@ -54,19 +60,21 @@ bool ArgParse::parseArgs(const std::list argList) throw(std::runtim for (++it; it != argList.end(); ++it ) { if ( (*it).at(0) != '-' ) - throw std::runtime_error(std::string(*it).append(" shall start with a dash.")); + throw std::runtime_error(std::string(*it). + append(" shall start with a dash.")); // inspect each arument - std::map::iterator it2 = findElement(*it); + ArgMap::iterator it2 = findElement(*it); if ( it2 == m_params.end() ) throw std::runtime_error(std::string(*it).append(" is not known.")); if ( (*it2).second.m_found ) - throw std::runtime_error(std::string(*it).append(" has been given before.")); + throw std::runtime_error(std::string(*it). + append(" has been given before.")); (*it2).second.m_found = true; - if ( (*it2).second.m_type == TYPE_NONE ) + if ( (*it2).second.m_type == NONE ) continue; std::list::const_iterator next = it; @@ -74,66 +82,125 @@ bool ArgParse::parseArgs(const std::list argList) throw(std::runtim if ( next == argList.end() ) { if ( (*it2).second.m_valueRequired == REQUIRED ) - throw std::runtime_error(std::string(*it).append(" requires a parameter.")); + throw std::runtime_error(std::string(*it). + append(" requires a parameter.")); if ( (*it2).second.m_valueRequired == OPTIONAL ) continue; } - if ( (*it2).second.m_valueRequired == OPTIONAL && findElement( *next ) != m_params.end() ) + if ( (*it2).second.m_valueRequired == OPTIONAL && + findElement( *next ) != m_params.end() ) continue; - switch ( (*it2).second.m_type ) { - case TYPE_INT : { - int temp; - if ( sscanf( next->c_str(), "%d", &temp ) == 0 ) - throw std::runtime_error(std::string( *next ). - append(" is not an integer, required by ").append(*it)); - break; - } - case TYPE_DOUBLE : { - double temp; - if ( sscanf( next->c_str(), "%f", &temp ) == 0 ) - throw std::runtime_error(std::string( *next ). - append(" is not a double, required by ").append(*it)); - break; - } - case TYPE_BOOL : { - std::string temp = *next; - std::transform(temp.begin(), temp.end(),temp.begin(), ::toupper); - if ( temp != "TRUE" && temp != "FALSE" ) - throw std::runtime_error(std::string( *next ). - append(" is not a boolean, required by ").append(*it)); - break; + switch ( (*it2).second.m_type ) { + case INT : { + int temp; + if ( sscanf( next->c_str(), "%d", &temp ) == 0 ) + throw std::runtime_error(std::string( *next ). + append(" is not an integer, required by ").append( *it )); + + if ( !(*it2).second.m_choices.empty() ) { + int lowerBound; + int upperBound; + if ( sscanf( (*it2).second.m_choices.c_str(), + "%d..%d", &lowerBound, &upperBound ) != 2 ) + throw std::logic_error(std::string( *it ). + append(" has syntax error. "). + append("Range expected in a INT..INT format" )); + + if ( temp < lowerBound || temp > upperBound ) + throw std::runtime_error(std::string( *it ). + append( " expects an integer in the range of {" ). + append( (*it2).second.m_choices). + append("}") ); + } + + break; + } + case DOUBLE : { + double temp; + if ( sscanf( next->c_str(), "%f", &temp ) == 0 ) + throw std::runtime_error(std::string( *next ). + append(" is not a double, required by ").append(*it)); + + if ( !(*it2).second.m_choices.empty() ) { + double lowerBound; + double upperBound; + if ( sscanf( (*it2).second.m_choices.c_str(), + "%f..%f", &lowerBound, &upperBound ) != 2 ) + throw std::logic_error(std::string( *it ). + append(" has syntax error. "). + append("Range expected in a DOUBLE..DOUBLE format" )); + + if ( temp < lowerBound || temp > upperBound ) + throw std::runtime_error(std::string( *it ). + append( " expects a double in the range of [" ). + append( (*it2).second.m_choices). + append("}") ); + } + + break; + } + case BOOL : { + std::string temp = *next; + std::transform(temp.begin(), temp.end(),temp.begin(), ::toupper); + if ( temp != "TRUE" && temp != "FALSE" ) + throw std::runtime_error(std::string( *next ). + append(" is not a boolean, required by ").append(*it)); + + if ( !(*it2).second.m_choices.empty() ) + throw std::logic_error(std::string( *next ). + append(" expects a boolean not choices.")); + + break; + } + case STRING : { + if ( !(*it2).second.m_choices.empty() ) { + + std::set choices = + parseCommaSepStringToSet( (*it2).second.m_choices ); + + if ( choices.find( *next ) == choices.end() ) + throw std::runtime_error(std::string( *next ). + append(" is not in the expected list of choices: {"). + append( (*it2).second.m_choices ). + append("}")); + } + + break; + } + + default: + break; } - default: - break; - } + (*it2).second.m_value = *next; (*it2).second.m_valueHasBeenSet = true; ++it; } + return true; } bool ArgParse::isArg(const std::string arg) const { - std::map::const_iterator it = m_params.find(arg); + ArgMap::const_iterator it = m_params.find(arg); return it != m_params.end(); } bool ArgParse::foundArg(const std::string arg) const { - std::map::const_iterator it = m_params.find(arg); + ArgMap::const_iterator it = m_params.find(arg); return it != m_params.end() && (*it).second.m_found == true; } bool ArgParse::argHasValue(const std::string arg) const { - std::map::const_iterator it = m_params.find(arg); + ArgMap::const_iterator it = m_params.find(arg); return it != m_params.end() && (*it).second.m_found == true && (*it).second.m_valueHasBeenSet; @@ -145,7 +212,7 @@ bool ArgParse::argAsString(const std::string arg, std::string &value) const if ( !argHasValue(arg) ) return false; - std::map::const_iterator it = m_params.find(arg); + ArgMap::const_iterator it = m_params.find(arg); value = (*it).second.m_value; return true; } @@ -156,7 +223,7 @@ bool ArgParse::argAsInt(const std::string arg, int &value) const if ( !argHasValue(arg) ) return false; - std::map::const_iterator it = m_params.find(arg); + ArgMap::const_iterator it = m_params.find(arg); value = atoi((*it).second.m_value.c_str()); return true; } @@ -167,7 +234,7 @@ bool ArgParse::argAsDouble(const std::string arg, double &value) const if ( !argHasValue(arg) ) return false; - std::map::const_iterator it = m_params.find(arg); + ArgMap::const_iterator it = m_params.find(arg); value = atof((*it).second.m_value.c_str()); return true; } @@ -178,7 +245,7 @@ bool ArgParse::argAsBool(const std::string arg, bool &value) const if ( !argHasValue(arg) ) return false; - std::map::const_iterator it = m_params.find(arg); + ArgMap::const_iterator it = m_params.find(arg); std::string temp = (*it).second.m_value; std::transform(temp.begin(), temp.end(),temp.begin(), ::toupper); @@ -199,17 +266,31 @@ std::string ArgParse::usage() const ss << " [OPTION]" << std::endl << std::endl; ss << "Options:" << std::endl; int length; - std::map::const_iterator it; + ArgMap::const_iterator it; for ( it = m_params.begin(); it != m_params.end(); it++ ) { ss << (*it).first; length = (*it).first.length(); - if ( (*it).second.m_type != TYPE_NONE ) { - if ((*it).second.m_valueRequired == REQUIRED ) { - ss << "=" << (*it).second.m_valueName; - length += (*it).second.m_valueName.length()+1; + if ( (*it).second.m_type != NONE ) { + + ss << " "; + length++; + + if ( (*it).second.m_valueRequired == OPTIONAL ) { + ss << "["; + length ++; + } + + if ( !(*it).second.m_choices.empty() ) { + ss << "{" << (*it).second.m_choices << "}"; + length += (*it).second.m_choices.length() + 2; } else { - ss << "[=" << (*it).second.m_valueName << "]"; - length += (*it).second.m_valueName.length() + 3; + ss << (*it).second.m_valueName; + length += (*it).second.m_valueName.length(); + } + + if ( (*it).second.m_valueRequired == OPTIONAL ) { + ss << "]"; + length++; } } ss << std::string(30-length, ' '); @@ -222,10 +303,43 @@ std::string ArgParse::usage() const return ss.str();; } + +ArgParse::Argument::Argument (const std::string help, + const enum ValueType type, + const enum ValueRequired valueRequired, + const std::string valueName, + const std::string choices, + const std::string value) + : m_help(help) + , m_type(type) + , m_valueRequired(valueRequired) + , m_valueName(valueName) + , m_choices(choices) + , m_value(value) + , m_found(false) + , m_valueHasBeenSet(false) +{ + +} + + +bool +ArgParse::argCompare::operator()(const std::string a,const std::string b) const +{ + if ( a.at(1) == '-' && b.at(1) != '-' ) + return a.substr(1) < b; + + if ( b.at(1) == '-' && a.at(1) != '-' ) + return a < b.substr(1); + + return a::iterator ArgParse::findElement(const std::string param) { - std::map::iterator it; + ArgMap::iterator it; for( it = m_params.begin(); it != m_params.end(); ++it) { // if it's the short param at the beginning @@ -240,4 +354,55 @@ ArgParse::findElement(const std::string param) } return m_params.end(); +} + +std::set +ArgParse::parseCommaSepStringToSet(const std::string s) const +{ + std::string tmp(s); + std::set stringSet; + + size_t pos; + std::string element; + + while ( !tmp.empty() ) { + pos = tmp.find(','); + if (pos == std::string::npos) { + element = tmp; + tmp = ""; + } else { + element = tmp.substr(0,pos); + tmp = tmp.substr(pos+1); + } + + // if ( element.empty() ) ... ? + + if ( stringSet.find(element) != stringSet.end() ) { + throw std::logic_error( std::string( element ). + append(" listed twice in ").append(s) ); + } + + stringSet.insert(element); + } + + return stringSet; +} + +std::string ArgParse::typeToString(const ValueType type, std::string valueName) const +{ + if ( type != NONE && valueName.empty() ) { + switch ( type ) { + case INT : + return "INT"; + case DOUBLE : + return "DOUBLE"; + break; + case BOOL : + return "BOOL"; + break; + default: + return ""; + } + } + return valueName; } \ No newline at end of file