Merge pull request #14 from svanveen/fix/refactoring

Do some refactoring to improve readability
This commit is contained in:
Pranav Srinivas Kumar 2019-05-12 11:05:37 -04:00 committed by GitHub
commit cddde9f1b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -40,9 +40,11 @@ SOFTWARE.
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <numeric>
namespace argparse { namespace argparse {
namespace { // anonymous namespace for helper methods - not visible outside this header file
// Some utility structs to check template specialization // Some utility structs to check template specialization
template<typename Test, template<typename...> class Ref> template<typename Test, template<typename...> class Ref>
struct is_specialization : std::false_type {}; struct is_specialization : std::false_type {};
@ -66,6 +68,7 @@ T get_from_list(const std::list<T>& aList, size_t aIndex) {
} }
return T(); return T();
} }
}
class Argument { class Argument {
friend class ArgumentParser; friend class ArgumentParser;
@ -104,6 +107,32 @@ public:
return *this; return *this;
} }
/*
* @throws std::runtime_error if argument values are not valid
*/
void validate() const {
if (mIsOptional) {
if (mIsUsed && mValues.size() != mNumArgs && !mDefaultValue.has_value()) {
std::stringstream stream;
stream << "error: " << mUsedName << ": expected " << mNumArgs << " argument(s). "
<< mValues.size() << " provided.\n" << std::endl;
throw std::runtime_error(stream.str());
}
else {
// TODO: check if an implicit value was programmed for this argument
}
}
else {
if (mValues.size() != mNumArgs) {
std::stringstream stream;
stream << "error: " << mUsedName << ": expected " << mNumArgs << " argument(s). "
<< mValues.size() << " provided.\n" << std::endl;
throw std::runtime_error(stream.str());
}
}
}
template <typename T> template <typename T>
bool operator!=(const T& aRhs) const { bool operator!=(const T& aRhs) const {
return !(*this == aRhs); return !(*this == aRhs);
@ -121,7 +150,7 @@ public:
template <typename T> template <typename T>
typename std::enable_if<is_specialization<T, std::vector>::value, bool>::type typename std::enable_if<is_specialization<T, std::vector>::value, bool>::type
operator==(const T& aRhs) const { operator==(const T& aRhs) const {
T tLhs = get_vector<T>(); T tLhs = get<T>();
if (tLhs.size() != aRhs.size()) if (tLhs.size() != aRhs.size())
return false; return false;
else { else {
@ -138,7 +167,7 @@ public:
template <typename T> template <typename T>
typename std::enable_if<is_specialization<T, std::list>::value, bool>::type typename std::enable_if<is_specialization<T, std::list>::value, bool>::type
operator==(const T& aRhs) const { operator==(const T& aRhs) const {
T tLhs = get_list<T>(); T tLhs = get<T>();
if (tLhs.size() != aRhs.size()) if (tLhs.size() != aRhs.size())
return false; return false;
else { else {
@ -159,7 +188,9 @@ public:
// Getter for template types other than std::vector and std::list // Getter for template types other than std::vector and std::list
template <typename T> template <typename T>
T get() const { typename std::enable_if<!is_specialization<T, std::vector>::value &&
!is_specialization<T, std::list>::value, T>::type
get() const {
if (mValues.empty()) { if (mValues.empty()) {
if (mDefaultValue.has_value()) { if (mDefaultValue.has_value()) {
return std::any_cast<T>(mDefaultValue); return std::any_cast<T>(mDefaultValue);
@ -181,7 +212,8 @@ public:
// Getter for std::vector. Here T = std::vector<...> // Getter for std::vector. Here T = std::vector<...>
template <typename T> template <typename T>
T get_vector() const { typename std::enable_if<is_specialization<T, std::vector>::value, T>::type
get() const {
T tResult; T tResult;
if (mValues.empty()) { if (mValues.empty()) {
if (mDefaultValue.has_value()) { if (mDefaultValue.has_value()) {
@ -217,7 +249,8 @@ public:
// Getter for std::list. Here T = std::list<...> // Getter for std::list. Here T = std::list<...>
template <typename T> template <typename T>
T get_list() const { typename std::enable_if<is_specialization<T, std::list>::value, T>::type
get() const {
T tResult; T tResult;
if (mValues.empty()) { if (mValues.empty()) {
if (mDefaultValue.has_value()) { if (mDefaultValue.has_value()) {
@ -262,6 +295,10 @@ public:
size_t mNumArgs = 1; size_t mNumArgs = 1;
bool mIsOptional = false; bool mIsOptional = false;
bool mIsUsed = false; // relevant for optional arguments. True if used by user bool mIsUsed = false; // relevant for optional arguments. True if used by user
public:
static constexpr auto mHelpOption = "-h";
static constexpr auto mHelpOptionLong = "--help";
}; };
class ArgumentParser { class ArgumentParser {
@ -269,14 +306,11 @@ class ArgumentParser {
explicit ArgumentParser(std::string aProgramName = {}) : explicit ArgumentParser(std::string aProgramName = {}) :
mProgramName(std::move(aProgramName)) mProgramName(std::move(aProgramName))
{ {
std::shared_ptr<Argument> tArgument = std::make_shared<Argument>("-h", "--help"); add_argument(Argument::mHelpOption, Argument::mHelpOptionLong)
tArgument->mHelp = "show this help message and exit"; .help("show this help message and exit")
tArgument->mNumArgs = 0; .nargs(0)
tArgument->mDefaultValue = false; .default_value(false)
tArgument->mImplicitValue = true; .implicit_value(true);
mOptionalArguments.emplace_back(tArgument);
mArgumentMap.insert_or_assign(std::string("-h"), tArgument);
mArgumentMap.insert_or_assign(std::string("--help"), tArgument);
} }
// Parameter packing // Parameter packing
@ -285,41 +319,35 @@ class ArgumentParser {
Argument& add_argument(Targs... Fargs) { Argument& add_argument(Targs... Fargs) {
std::shared_ptr<Argument> tArgument = std::make_shared<Argument>(std::move(Fargs)...); std::shared_ptr<Argument> tArgument = std::make_shared<Argument>(std::move(Fargs)...);
if (!tArgument->mIsOptional) if (tArgument->mIsOptional)
mPositionalArguments.emplace_back(tArgument);
else
mOptionalArguments.emplace_back(tArgument); mOptionalArguments.emplace_back(tArgument);
else
mPositionalArguments.emplace_back(tArgument);
for (auto& mName : tArgument->mNames) { for (const auto& mName : tArgument->mNames) {
mArgumentMap.insert_or_assign(mName, tArgument); mArgumentMap.insert_or_assign(mName, tArgument);
} }
return *tArgument; return *tArgument;
} }
// Base case for add_parents parameter packing // Parameter packed add_parents method
void add_parents() { // Accepts a variadic number of ArgumentParser objects
for (const auto& tParentParser : mParentParsers) { template<typename... Targs>
auto tPositionalArguments = tParentParser.mPositionalArguments; void add_parents(Targs... Fargs) {
for (auto& tArgument : tPositionalArguments) { const auto tNewParentParsers = {Fargs...};
mPositionalArguments.emplace_back(tArgument); for (const auto& tParentParser : tNewParentParsers) {
} const auto& tPositionalArguments = tParentParser.mPositionalArguments;
auto tOptionalArguments = tParentParser.mOptionalArguments; std::copy(std::begin(tPositionalArguments), std::end(tPositionalArguments), std::back_inserter(mPositionalArguments));
for (auto& tArgument : tOptionalArguments) {
mOptionalArguments.emplace_back(tArgument); const auto& tOptionalArguments = tParentParser.mOptionalArguments;
} std::copy(std::begin(tOptionalArguments), std::end(tOptionalArguments), std::back_inserter(mOptionalArguments));
auto tArgumentMap = tParentParser.mArgumentMap;
for (auto&[tKey, tValue] : tArgumentMap) { const auto& tArgumentMap = tParentParser.mArgumentMap;
for (const auto&[tKey, tValue] : tArgumentMap) {
mArgumentMap.insert_or_assign(tKey, tValue); mArgumentMap.insert_or_assign(tKey, tValue);
} }
} }
} std::move(std::begin(tNewParentParsers), std::end(tNewParentParsers), std::back_inserter(mParentParsers));
// Parameter packed add_parents method
// Accepts a variadic number of ArgumentParser objects
template<typename T, typename... Targs>
void add_parents(T aArgumentParser, Targs... Fargs) {
mParentParsers.emplace_back(aArgumentParser);
add_parents(Fargs...);
} }
/* Call parse_args_internal - which does all the work /* Call parse_args_internal - which does all the work
@ -342,9 +370,7 @@ class ArgumentParser {
// Getter enabled for all template types other than std::vector and std::list // Getter enabled for all template types other than std::vector and std::list
template <typename T = std::string> template <typename T = std::string>
typename std::enable_if<!is_specialization<T, std::vector>::value && T get(const std::string& aArgumentName) {
!is_specialization<T, std::list>::value, T>::type
get(const char * aArgumentName) {
auto tIterator = mArgumentMap.find(aArgumentName); auto tIterator = mArgumentMap.find(aArgumentName);
if (tIterator != mArgumentMap.end()) { if (tIterator != mArgumentMap.end()) {
return tIterator->second->get<T>(); return tIterator->second->get<T>();
@ -352,28 +378,6 @@ class ArgumentParser {
return T(); return T();
} }
// Getter enabled for std::vector
template <typename T>
typename std::enable_if<is_specialization<T, std::vector>::value, T>::type
get(const char * aArgumentName) {
auto tIterator = mArgumentMap.find(aArgumentName);
if (tIterator != mArgumentMap.end()) {
return tIterator->second->get_vector<T>();
}
return T();
}
// Getter enabled for std::list
template <typename T>
typename std::enable_if<is_specialization<T, std::list>::value, T>::type
get(const char * aArgumentName) {
auto tIterator = mArgumentMap.find(aArgumentName);
if (tIterator != mArgumentMap.end()) {
return tIterator->second->get_list<T>();
}
return T();
}
// Indexing operator. Return a reference to an Argument object // Indexing operator. Return a reference to an Argument object
// Used in conjuction with Argument.operator== e.g., parser["foo"] == true // Used in conjuction with Argument.operator== e.g., parser["foo"] == true
Argument& operator[](const std::string& aArgumentName) { Argument& operator[](const std::string& aArgumentName) {
@ -482,7 +486,7 @@ class ArgumentParser {
mProgramName = argv[0]; mProgramName = argv[0];
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
auto tCurrentArgument = std::string(argv[i]); auto tCurrentArgument = std::string(argv[i]);
if (tCurrentArgument == "-h" || tCurrentArgument == "--help") { if (tCurrentArgument == Argument::mHelpOption || tCurrentArgument == Argument::mHelpOptionLong) {
throw std::runtime_error("help called"); throw std::runtime_error("help called");
} }
auto tIterator = mArgumentMap.find(argv[i]); auto tIterator = mArgumentMap.find(argv[i]);
@ -601,65 +605,41 @@ class ArgumentParser {
* @throws std::runtime_error in case of any invalid argument * @throws std::runtime_error in case of any invalid argument
*/ */
void parse_args_validate() { void parse_args_validate() {
// Check if all positional arguments are parsed try {
for (const auto& tArgument : mPositionalArguments) { // Check if all positional arguments are parsed
if (tArgument->mValues.size() != tArgument->mNumArgs) { std::for_each(std::begin(mPositionalArguments),
std::stringstream stream; std::end(mPositionalArguments),
stream << "error: " << tArgument->mUsedName << ": expected " std::mem_fn(&Argument::validate));
<< tArgument->mNumArgs << (tArgument->mNumArgs == 1 ? " argument. " : " arguments. ") // Check if all user-provided optional argument values are parsed correctly
<< tArgument->mValues.size() << " provided.\n" << std::endl; std::for_each(std::begin(mOptionalArguments),
throw std::runtime_error(stream.str()); std::end(mOptionalArguments),
} std::mem_fn(&Argument::validate));
} } catch (const std::runtime_error& err) {
throw err;
// Check if all user-provided optional argument values are parsed correctly
for (const auto& tArgument : mOptionalArguments) {
if (tArgument->mIsUsed && tArgument->mNumArgs > 0) {
if (tArgument->mValues.size() != tArgument->mNumArgs) {
// All cool if there's a default value to return
// If no default value, then there's a problem
if (!tArgument->mDefaultValue.has_value()) {
std::stringstream stream;
stream << "error: " << tArgument->mUsedName << ": expected "
<< tArgument->mNumArgs << (tArgument->mNumArgs == 1 ? " argument. " : " arguments. ")
<< tArgument->mValues.size() << " provided.\n" << std::endl;
throw std::runtime_error(stream.str());
}
}
}
else {
// TODO: check if an implicit value was programmed for this argument
}
} }
} }
// Used by print_help. // Used by print_help.
size_t get_length_of_longest_argument() { size_t get_length_of_longest_argument(const std::vector<std::shared_ptr<Argument>>& aArguments) {
size_t tResult = 0; if (aArguments.empty())
for (const auto& mPositionalArgument : mPositionalArguments) { return 0;
size_t tCurrentArgumentLength = 0; std::vector<size_t> argumentLengths(aArguments.size());
auto tNames = mPositionalArgument->mNames; std::transform(std::begin(aArguments), std::end(aArguments), std::begin(argumentLengths), [](const auto& arg) {
for (size_t j = 0; j < tNames.size() - 1; j++) { const auto& names = arg->mNames;
auto tNameLength = tNames[j].length(); auto maxLength = std::accumulate(std::begin(names), std::end(names), 0, [](const auto& sum, const auto& s) {
tCurrentArgumentLength += tNameLength + 2; // +2 for ", " return sum + s.size() + 2; // +2 for ", "
} });
tCurrentArgumentLength += tNames[tNames.size() - 1].length(); return maxLength - 2; // -2 since the last one doesn't need ", "
if (tCurrentArgumentLength > tResult) });
tResult = tCurrentArgumentLength; return *std::max_element(std::begin(argumentLengths), std::end(argumentLengths));
} }
for (const auto& mOptionalArgument : mOptionalArguments) { // Used by print_help.
size_t tCurrentArgumentLength = 0; size_t get_length_of_longest_argument() {
auto tNames = mOptionalArgument->mNames; const auto positionalArgMaxSize = get_length_of_longest_argument(mPositionalArguments);
for (size_t j = 0; j < tNames.size() - 1; j++) { const auto optionalArgMaxSize = get_length_of_longest_argument(mOptionalArguments);
auto tNameLength = tNames[j].length();
tCurrentArgumentLength += tNameLength + 2; // +2 for ", " return std::max(positionalArgMaxSize, optionalArgMaxSize);
}
tCurrentArgumentLength += tNames[tNames.size() - 1].length();
if (tCurrentArgumentLength > tResult)
tResult = tCurrentArgumentLength;
}
return tResult;
} }
std::string mProgramName; std::string mProgramName;
@ -674,7 +654,7 @@ class ArgumentParser {
try { \ try { \
parser.parse_args(argc, argv); \ parser.parse_args(argc, argv); \
} catch (const std::runtime_error& err) { \ } catch (const std::runtime_error& err) { \
std::cerr << err.what() << std::endl; \ std::cout << err.what() << std::endl; \
parser.print_help(); \ parser.print_help(); \
exit(0); \ exit(0); \
} }