diff --git a/include/argparse.hpp b/include/argparse.hpp index 731854d..aef3c38 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -49,29 +49,11 @@ struct is_specialization : std::false_type {}; template class Ref, typename... Args> struct is_specialization, Ref> : std::true_type {}; -// Upsert into std::map -template -bool upsert(std::map& aMap, KeyType const& aKey, ElementType const& aNewValue) { - typedef typename std::map::iterator Iterator; - typedef typename std::pair Result; - Result tResult = aMap.insert(typename std::map::value_type(aKey, aNewValue)); - if (!tResult.second) { - if (!(tResult.first->second == aNewValue)) { - tResult.first->second = aNewValue; - return true; - } - else - return false; // it was the same - } - else - return true; // changed cause not existing -} - // Check if string (haystack) starts with a substring (needle) bool starts_with(const std::string& haystack, const std::string& needle) { return needle.length() <= haystack.length() && std::equal(needle.begin(), needle.end(), haystack.begin()); -}; +} // Get value at index from std::list template @@ -87,16 +69,13 @@ T get_from_list(const std::list& aList, size_t aIndex) { class Argument { friend class ArgumentParser; public: - Argument() : - mNames({}), - mUsedName(""), - mHelp(""), - mAction([](const std::string& aValue) { return aValue; }), - mValues({}), - mRawValues({}), - mNumArgs(1), - mIsOptional(false), - mIsUsed(false) {} + Argument() = default; + + template + explicit Argument(Args... args) + : mNames({std::move(args)...}) + , mIsOptional((is_optional(args) || ...)) + {} Argument& help(const std::string& aHelp) { mHelp = aHelp; @@ -104,18 +83,18 @@ public: } Argument& default_value(std::any aDefaultValue) { - mDefaultValue = aDefaultValue; + mDefaultValue = std::move(aDefaultValue); return *this; } Argument& implicit_value(std::any aImplicitValue) { - mImplicitValue = aImplicitValue; + mImplicitValue = std::move(aImplicitValue); mNumArgs = 0; return *this; } Argument& action(std::function aAction) { - mAction = aAction; + mAction = std::move(aAction); return *this; } @@ -131,8 +110,8 @@ public: // Entry point for template types other than std::vector and std::list template - typename std::enable_if::value == false && - is_specialization::value == false, bool>::type + typename std::enable_if::value && + !is_specialization::value, bool>::type operator==(const T& aRhs) const { return get() == aRhs; } @@ -172,11 +151,15 @@ public: } private: + // If an argument starts with "-" or "--", then it's optional + static bool is_optional(const std::string& aName) { + return (starts_with(aName, "--") || starts_with(aName, "-")); + } // Getter for template types other than std::vector and std::list template T get() const { - if (mValues.size() == 0) { + if (mValues.empty()) { if (mDefaultValue.has_value()) { return std::any_cast(mDefaultValue); } @@ -184,7 +167,7 @@ public: return T(); } else { - if (mRawValues.size() > 0) + if (!mRawValues.empty()) return std::any_cast(mValues[0]); else { if (mDefaultValue.has_value()) @@ -199,11 +182,11 @@ public: template T get_vector() const { T tResult; - if (mValues.size() == 0) { + if (mValues.empty()) { if (mDefaultValue.has_value()) { T tDefaultValues = std::any_cast(mDefaultValue); for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.push_back(std::any_cast(tDefaultValues[i])); + tResult.emplace_back(std::any_cast(tDefaultValues[i])); } return tResult; } @@ -211,9 +194,9 @@ public: return T(); } else { - if (mRawValues.size() > 0) { - for (size_t i = 0; i < mValues.size(); i++) { - tResult.push_back(std::any_cast(mValues[i])); + if (!mRawValues.empty()) { + for (const auto& mValue : mValues) { + tResult.emplace_back(std::any_cast(mValue)); } return tResult; } @@ -221,7 +204,7 @@ public: if (mDefaultValue.has_value()) { std::vector tDefaultValues = std::any_cast>(mDefaultValue); for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.push_back(std::any_cast(tDefaultValues[i])); + tResult.emplace_back(std::any_cast(tDefaultValues[i])); } return tResult; } @@ -235,11 +218,11 @@ public: template T get_list() const { T tResult; - if (mValues.size() == 0) { + if (mValues.empty()) { if (mDefaultValue.has_value()) { T tDefaultValues = std::any_cast(mDefaultValue); for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.push_back(std::any_cast(get_from_list(tDefaultValues, i))); + tResult.emplace_back(std::any_cast(get_from_list(tDefaultValues, i))); } return tResult; } @@ -247,9 +230,9 @@ public: return T(); } else { - if (mRawValues.size() > 0) { - for (size_t i = 0; i < mValues.size(); i++) { - tResult.push_back(std::any_cast(mValues[i])); + if (!mRawValues.empty()) { + for (const auto& mValue : mValues) { + tResult.emplace_back(std::any_cast(mValue)); } return tResult; } @@ -257,7 +240,7 @@ public: if (mDefaultValue.has_value()) { std::list tDefaultValues = std::any_cast>(mDefaultValue); for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.push_back(std::any_cast(get_from_list(tDefaultValues, i))); + tResult.emplace_back(std::any_cast(get_from_list(tDefaultValues, i))); } return tResult; } @@ -272,70 +255,60 @@ public: std::string mHelp; std::any mDefaultValue; std::any mImplicitValue; - std::function mAction; + std::function mAction = [](const std::string& aValue) { return aValue; }; std::vector mValues; std::vector mRawValues; - size_t mNumArgs; - bool mIsOptional; - bool mIsUsed; // relevant for optional arguments. True if used by user + size_t mNumArgs = 1; + bool mIsOptional = false; + bool mIsUsed = false; // relevant for optional arguments. True if used by user }; class ArgumentParser { public: - ArgumentParser(const std::string& aProgramName = "") : - mProgramName(aProgramName), - mNextPositionalArgument(0) { - std::shared_ptr tArgument = std::make_shared(); - tArgument->mNames = { "-h", "--help" }; + explicit ArgumentParser(std::string aProgramName = {}) : + mProgramName(std::move(aProgramName)) + { + std::shared_ptr tArgument = std::make_shared("-h", "--help"); tArgument->mHelp = "show this help message and exit"; tArgument->mNumArgs = 0; tArgument->mDefaultValue = false; tArgument->mImplicitValue = true; - mOptionalArguments.push_back(tArgument); - upsert(mArgumentMap, std::string("-h"), tArgument); - upsert(mArgumentMap, std::string("--help"), tArgument); + mOptionalArguments.emplace_back(tArgument); + mArgumentMap.insert_or_assign(std::string("-h"), tArgument); + mArgumentMap.insert_or_assign(std::string("--help"), tArgument); } // Parameter packing // Call add_argument with variadic number of string arguments - // TODO: enforce T to be std::string - template - Argument& add_argument(T value, Targs... Fargs) { - std::shared_ptr tArgument = std::make_shared(); - tArgument->mNames.push_back(value); - add_argument_internal(tArgument, Fargs...); - - for (auto& mName : tArgument->mNames) { - if (is_optional(mName)) - tArgument->mIsOptional = true; - } + template + Argument& add_argument(Targs... Fargs) { + std::shared_ptr tArgument = std::make_shared(std::move(Fargs)...); if (!tArgument->mIsOptional) - mPositionalArguments.push_back(tArgument); + mPositionalArguments.emplace_back(tArgument); else - mOptionalArguments.push_back(tArgument); + mOptionalArguments.emplace_back(tArgument); for (auto& mName : tArgument->mNames) { - upsert(mArgumentMap, mName, tArgument); + mArgumentMap.insert_or_assign(mName, tArgument); } return *tArgument; } // Base case for add_parents parameter packing void add_parents() { - for (size_t i = 0; i < mParentParsers.size(); i++) { - auto tParentParser = mParentParsers[i]; + for (const auto& tParentParser : mParentParsers) { auto tPositionalArguments = tParentParser.mPositionalArguments; for (auto& tArgument : tPositionalArguments) { - mPositionalArguments.push_back(tArgument); + mPositionalArguments.emplace_back(tArgument); } auto tOptionalArguments = tParentParser.mOptionalArguments; for (auto& tArgument : tOptionalArguments) { - mOptionalArguments.push_back(tArgument); + mOptionalArguments.emplace_back(tArgument); } auto tArgumentMap = tParentParser.mArgumentMap; for (auto&[tKey, tValue] : tArgumentMap) { - upsert(mArgumentMap, tKey, tValue); + mArgumentMap.insert_or_assign(tKey, tValue); } } } @@ -344,7 +317,7 @@ class ArgumentParser { // Accepts a variadic number of ArgumentParser objects template void add_parents(T aArgumentParser, Targs... Fargs) { - mParentParsers.push_back(aArgumentParser); + mParentParsers.emplace_back(aArgumentParser); add_parents(Fargs...); } @@ -364,10 +337,10 @@ class ArgumentParser { // Getter enabled for all template types other than std::vector and std::list template - typename std::enable_if::value == false && - is_specialization::value == false, T>::type + typename std::enable_if::value && + !is_specialization::value, T>::type get(const char * aArgumentName) { - std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + auto tIterator = mArgumentMap.find(aArgumentName); if (tIterator != mArgumentMap.end()) { return tIterator->second->get(); } @@ -378,7 +351,7 @@ class ArgumentParser { template typename std::enable_if::value, T>::type get(const char * aArgumentName) { - std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + auto tIterator = mArgumentMap.find(aArgumentName); if (tIterator != mArgumentMap.end()) { return tIterator->second->get_vector(); } @@ -389,7 +362,7 @@ class ArgumentParser { template typename std::enable_if::value, T>::type get(const char * aArgumentName) { - std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + auto tIterator = mArgumentMap.find(aArgumentName); if (tIterator != mArgumentMap.end()) { return tIterator->second->get_list(); } @@ -399,7 +372,7 @@ class ArgumentParser { // Indexing operator. Return a reference to an Argument object // Used in conjuction with Argument.operator== e.g., parser["foo"] == true Argument& operator[](const std::string& aArgumentName) { - std::map>::iterator tIterator = mArgumentMap.find(aArgumentName); + auto tIterator = mArgumentMap.find(aArgumentName); if (tIterator != mArgumentMap.end()) { return *(tIterator->second); } @@ -422,11 +395,11 @@ class ArgumentParser { } stream << "\n\n"; - if (mPositionalArguments.size() > 0) + if (!mPositionalArguments.empty()) stream << "Positional arguments:\n"; - for (size_t i = 0; i < mPositionalArguments.size(); i++) { + for (const auto& mPositionalArgument : mPositionalArguments) { size_t tCurrentLength = 0; - auto tNames = mPositionalArguments[i]->mNames; + auto tNames = mPositionalArgument->mNames; for (size_t j = 0; j < tNames.size() - 1; j++) { auto tCurrentName = tNames[j]; stream << tCurrentName; @@ -442,16 +415,16 @@ class ArgumentParser { else stream << std::string((tCurrentLength - tLongestArgumentLength) + 2, ' '); - stream << mPositionalArguments[i]->mHelp << "\n"; + stream << mPositionalArgument->mHelp << "\n"; } - if (mOptionalArguments.size() > 0 && mPositionalArguments.size() > 0) + if (!mOptionalArguments.empty() && !mPositionalArguments.empty()) stream << "\nOptional arguments:\n"; - else if (mOptionalArguments.size() > 0) + else if (!mOptionalArguments.empty()) stream << "Optional arguments:\n"; - for (size_t i = 0; i < mOptionalArguments.size(); i++) { + for (const auto & mOptionalArgument : mOptionalArguments) { size_t tCurrentLength = 0; - auto tNames = mOptionalArguments[i]->mNames; + auto tNames = mOptionalArgument->mNames; std::sort(tNames.begin(), tNames.end(), [](const std::string& lhs, const std::string& rhs) { return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); @@ -471,7 +444,7 @@ class ArgumentParser { else stream << std::string((tCurrentLength - tLongestArgumentLength) + 2, ' '); - stream << mOptionalArguments[i]->mHelp << "\n"; + stream << mOptionalArgument->mHelp << "\n"; } std::cout << stream.str(); @@ -479,38 +452,22 @@ class ArgumentParser { } private: - Argument& add_argument_internal(std::shared_ptr aArgument) { - return *aArgument; - } - - template - Argument& add_argument_internal(std::shared_ptr aArgument, T aArgumentName, Targs... Fargs) { - aArgument->mNames.push_back(aArgumentName); - add_argument_internal(aArgument, Fargs...); - return *aArgument; - } - - // If an argument starts with "-" or "--", then it's optional - bool is_optional(const std::string& aName) { - return (starts_with(aName, "--") || starts_with(aName, "-")); - } - // If the argument was defined by the user and can be found in mArgumentMap, then it's valid bool is_valid_argument(const std::string& aName) { - std::map>::iterator tIterator = mArgumentMap.find(aName); + auto tIterator = mArgumentMap.find(aName); return (tIterator != mArgumentMap.end()); } void parse_args_internal(const std::vector& aArguments) { std::vector argv; for (const auto& arg : aArguments) - argv.push_back(const_cast(arg.data())); - argv.push_back(nullptr); + argv.emplace_back(const_cast(arg.data())); + argv.emplace_back(nullptr); return parse_args_internal(int(argv.size()) - 1, argv.data()); } void parse_args_internal(int argc, char * argv[]) { - if (mProgramName == "" && argc > 0) + if (mProgramName.empty() && argc > 0) mProgramName = argv[0]; for (int i = 1; i < argc; i++) { auto tCurrentArgument = std::string(argv[i]); @@ -518,12 +475,11 @@ class ArgumentParser { print_help(); exit(0); } - std::map>::iterator tIterator = - mArgumentMap.find(argv[i]); + auto tIterator = mArgumentMap.find(argv[i]); if (tIterator != mArgumentMap.end()) { // Start parsing optional argument auto tArgument = tIterator->second; - tArgument->mUsedName = tCurrentArgument; + tArgument->mUsedName = tCurrentArgument; tArgument->mIsUsed = true; auto tCount = tArgument->mNumArgs; @@ -533,29 +489,29 @@ class ArgumentParser { // (2) User has provided an implicit value, which also sets nargs to 0 if (tCount == 0) { // Use implicit value for this optional argument - tArgument->mValues.push_back(tArgument->mImplicitValue); - tArgument->mRawValues.push_back(""); + tArgument->mValues.emplace_back(tArgument->mImplicitValue); + tArgument->mRawValues.emplace_back(); tCount = 0; } while (tCount > 0) { i = i + 1; if (i < argc) { - tArgument->mUsedName = tCurrentArgument; - tArgument->mRawValues.push_back(argv[i]); + tArgument->mUsedName = tCurrentArgument; + tArgument->mRawValues.emplace_back(argv[i]); if (tArgument->mAction != nullptr) - tArgument->mValues.push_back(tArgument->mAction(argv[i])); + tArgument->mValues.emplace_back(tArgument->mAction(argv[i])); else { if (tArgument->mDefaultValue.has_value()) - tArgument->mValues.push_back(tArgument->mDefaultValue); + tArgument->mValues.emplace_back(tArgument->mDefaultValue); else - tArgument->mValues.push_back(std::string(argv[i])); + tArgument->mValues.emplace_back(std::string(argv[i])); } } tCount -= 1; } } else { - if (is_optional(argv[i])) { + if (Argument::is_optional(argv[i])) { // This is possibly a compound optional argument // Example: We have three optional arguments -a, -u and -x // The user provides ./main -aux ... @@ -569,24 +525,24 @@ class ArgumentParser { if (tIterator != mArgumentMap.end()) { auto tArgumentObject = tIterator->second; tNumArgs = tArgumentObject->mNumArgs; - std::vector tArgumentsForRecursiveParsing = { "", "-" + tArgument }; - while (tNumArgs > 0 && i < argc) { - i += 1; - if (i < argc) { - tArgumentsForRecursiveParsing.push_back(argv[i]); - tNumArgs -= 1; - } - } - parse_args_internal(tArgumentsForRecursiveParsing); + std::vector tArgumentsForRecursiveParsing = {"", "-" + tArgument}; + while (tNumArgs > 0 && i < argc) { + i += 1; + if (i < argc) { + tArgumentsForRecursiveParsing.emplace_back(argv[i]); + tNumArgs -= 1; + } + } + parse_args_internal(tArgumentsForRecursiveParsing); + } + else { + if (!tArgument.empty() && tArgument[0] == '-') + std::cout << "warning: unrecognized optional argument " << tArgument + << std::endl; + else + std::cout << "warning: unrecognized optional argument -" << tArgument + << std::endl; } - else { - if (tArgument.size() > 0 && tArgument[0] == '-') - std::cout << "warning: unrecognized optional argument " << tArgument - << std::endl; - else - std::cout << "warning: unrecognized optional argument -" << tArgument - << std::endl; - } } } else { @@ -605,20 +561,20 @@ class ArgumentParser { auto tCount = tArgument->mNumArgs - tArgument->mRawValues.size(); while (tCount > 0) { tIterator = mArgumentMap.find(argv[i]); - if (tIterator != mArgumentMap.end() || is_optional(argv[i])) { + if (tIterator != mArgumentMap.end() || Argument::is_optional(argv[i])) { i = i - 1; break; } if (i < argc) { - tArgument->mUsedName = tCurrentArgument; - tArgument->mRawValues.push_back(argv[i]); + tArgument->mUsedName = tCurrentArgument; + tArgument->mRawValues.emplace_back(argv[i]); if (tArgument->mAction != nullptr) - tArgument->mValues.push_back(tArgument->mAction(argv[i])); + tArgument->mValues.emplace_back(tArgument->mAction(argv[i])); else { if (tArgument->mDefaultValue.has_value()) - tArgument->mValues.push_back(tArgument->mDefaultValue); + tArgument->mValues.emplace_back(tArgument->mDefaultValue); else - tArgument->mValues.push_back(std::string(argv[i])); + tArgument->mValues.emplace_back(std::string(argv[i])); } } tCount -= 1; @@ -633,8 +589,7 @@ class ArgumentParser { void parse_args_validate() { // Check if all positional arguments are parsed - for (size_t i = 0; i < mPositionalArguments.size(); i++) { - auto tArgument = mPositionalArguments[i]; + for (const auto& tArgument : mPositionalArguments) { if (tArgument->mValues.size() != tArgument->mNumArgs) { std::cout << "error: " << tArgument->mUsedName << ": expected " << tArgument->mNumArgs << (tArgument->mNumArgs == 1 ? " argument. " : " arguments. ") @@ -645,8 +600,7 @@ class ArgumentParser { } // Check if all user-provided optional argument values are parsed correctly - for (size_t i = 0; i < mOptionalArguments.size(); i++) { - auto tArgument = mOptionalArguments[i]; + 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 @@ -669,9 +623,9 @@ class ArgumentParser { // Used by print_help. size_t get_length_of_longest_argument() { size_t tResult = 0; - for (size_t i = 0; i < mPositionalArguments.size(); i++) { + for (const auto& mPositionalArgument : mPositionalArguments) { size_t tCurrentArgumentLength = 0; - auto tNames = mPositionalArguments[i]->mNames; + auto tNames = mPositionalArgument->mNames; for (size_t j = 0; j < tNames.size() - 1; j++) { auto tNameLength = tNames[j].length(); tCurrentArgumentLength += tNameLength + 2; // +2 for ", " @@ -681,9 +635,9 @@ class ArgumentParser { tResult = tCurrentArgumentLength; } - for (size_t i = 0; i < mOptionalArguments.size(); i++) { + for (const auto& mOptionalArgument : mOptionalArguments) { size_t tCurrentArgumentLength = 0; - auto tNames = mOptionalArguments[i]->mNames; + auto tNames = mOptionalArgument->mNames; for (size_t j = 0; j < tNames.size() - 1; j++) { auto tNameLength = tNames[j].length(); tCurrentArgumentLength += tNameLength + 2; // +2 for ", " @@ -699,7 +653,7 @@ class ArgumentParser { std::vector mParentParsers; std::vector> mPositionalArguments; std::vector> mOptionalArguments; - size_t mNextPositionalArgument; + size_t mNextPositionalArgument = 0; std::map> mArgumentMap; };