Updated formatting

This commit is contained in:
Pranav Srinivas Kumar 2019-08-17 17:01:04 -05:00
parent 8df0a878ac
commit cb04248cfa

View File

@ -28,46 +28,45 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#pragma once #pragma once
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <functional>
#include <any>
#include <memory>
#include <type_traits>
#include <algorithm> #include <algorithm>
#include <any>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <numeric>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <numeric> #include <string>
#include <iomanip> #include <type_traits>
#include <iterator> #include <vector>
namespace argparse { namespace argparse {
namespace { // anonymous namespace for helper methods - not visible outside this header file namespace { // anonymous namespace for helper methods - not visible outside this
// header file
template<typename... Ts> template <typename... Ts> struct is_container_helper {};
struct is_container_helper {};
template<typename T, typename _ = void> template <typename T, typename _ = void>
struct is_container : std::false_type {}; struct is_container : std::false_type {};
template<> template <> struct is_container<std::string> : std::false_type {};
struct is_container<std::string> : std::false_type {};
template<typename T> template <typename T>
struct is_container<T, std::conditional_t< struct is_container<
false, is_container_helper< T,
typename T::value_type, std::conditional_t<false,
decltype(std::declval<T>().begin()), is_container_helper<typename T::value_type,
decltype(std::declval<T>().end()), decltype(std::declval<T>().begin()),
decltype(std::declval<T>().size()) decltype(std::declval<T>().end()),
>, void>> : public std::true_type { decltype(std::declval<T>().size())>,
}; void>> : public std::true_type {};
template<typename T> template <typename T>
static constexpr bool is_container_v = is_container<T>::value; static constexpr bool is_container_v = is_container<T>::value;
template <typename T> template <typename T>
@ -75,50 +74,50 @@ using enable_if_container = std::enable_if_t<is_container_v<T>, T>;
template <typename T> template <typename T>
using enable_if_not_container = std::enable_if_t<!is_container_v<T>, T>; using enable_if_not_container = std::enable_if_t<!is_container_v<T>, T>;
} } // namespace
class Argument { class Argument {
friend class ArgumentParser; friend class ArgumentParser;
public: public:
Argument() = default; Argument() = default;
template <typename ...Args> template <typename... Args>
explicit Argument(Args... args) explicit Argument(Args... args)
: mNames({std::move(args)...}) : mNames({std::move(args)...}), mIsOptional((is_optional(args) || ...)) {
, mIsOptional((is_optional(args) || ...)) std::sort(
{ mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) {
std::sort(mNames.begin(), mNames.end(), [](const auto& lhs, const auto& rhs) { return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); });
});
} }
Argument& help(std::string aHelp) { Argument &help(std::string aHelp) {
mHelp = std::move(aHelp); mHelp = std::move(aHelp);
return *this; return *this;
} }
Argument& default_value(std::any aDefaultValue) { Argument &default_value(std::any aDefaultValue) {
mDefaultValue = std::move(aDefaultValue); mDefaultValue = std::move(aDefaultValue);
return *this; return *this;
} }
Argument& required() { Argument &required() {
mIsRequired = true; mIsRequired = true;
return *this; return *this;
} }
Argument& implicit_value(std::any aImplicitValue) { Argument &implicit_value(std::any aImplicitValue) {
mImplicitValue = std::move(aImplicitValue); mImplicitValue = std::move(aImplicitValue);
mNumArgs = 0; mNumArgs = 0;
return *this; return *this;
} }
Argument& action(std::function<std::any(const std::string&)> aAction) { Argument &action(std::function<std::any(const std::string &)> aAction) {
mAction = std::move(aAction); mAction = std::move(aAction);
return *this; return *this;
} }
Argument& nargs(size_t aNumArgs) { Argument &nargs(size_t aNumArgs) {
mNumArgs = aNumArgs; mNumArgs = aNumArgs;
return *this; return *this;
} }
@ -133,19 +132,16 @@ public:
if (mNumArgs == 0) { if (mNumArgs == 0) {
mValues.emplace_back(mImplicitValue); mValues.emplace_back(mImplicitValue);
return start; return start;
} } else if (mNumArgs <= static_cast<size_t>(std::distance(start, end))) {
else if (mNumArgs <= static_cast<size_t>(std::distance(start, end))) {
end = std::next(start, mNumArgs); end = std::next(start, mNumArgs);
if (std::any_of(start, end, Argument::is_optional)) { if (std::any_of(start, end, Argument::is_optional)) {
throw std::runtime_error("optional argument in parameter sequence"); throw std::runtime_error("optional argument in parameter sequence");
} }
std::transform(start, end, std::back_inserter(mValues), mAction); std::transform(start, end, std::back_inserter(mValues), mAction);
return end; return end;
} } else if (mDefaultValue.has_value()) {
else if (mDefaultValue.has_value()) {
return start; return start;
} } else {
else {
throw std::runtime_error("Too few arguments"); throw std::runtime_error("Too few arguments");
} }
} }
@ -157,53 +153,53 @@ public:
if (mIsOptional) { if (mIsOptional) {
if (mIsUsed && mValues.size() != mNumArgs && !mDefaultValue.has_value()) { if (mIsUsed && mValues.size() != mNumArgs && !mDefaultValue.has_value()) {
std::stringstream stream; std::stringstream stream;
stream << "error: " << mUsedName << ": expected " << mNumArgs << " argument(s). " stream << "error: " << mUsedName << ": expected " << mNumArgs
<< mValues.size() << " provided."; << " argument(s). " << mValues.size() << " provided.";
throw std::runtime_error(stream.str()); throw std::runtime_error(stream.str());
} } else {
else {
// TODO: check if an implicit value was programmed for this argument // TODO: check if an implicit value was programmed for this argument
if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) {
std::stringstream stream; std::stringstream stream;
stream << "error: " << mNames[0] << ": required."; stream << "error: " << mNames[0] << ": required.";
throw std::runtime_error(stream.str()); throw std::runtime_error(stream.str());
} }
if (mIsUsed && mIsRequired && mValues.size() == 0) { if (mIsUsed && mIsRequired && mValues.size() == 0) {
std::stringstream stream; std::stringstream stream;
stream << "error: " << mUsedName << ": no value provided."; stream << "error: " << mUsedName << ": no value provided.";
throw std::runtime_error(stream.str()); throw std::runtime_error(stream.str());
} }
} }
} } else {
else {
if (mValues.size() != mNumArgs && !mDefaultValue.has_value()) { if (mValues.size() != mNumArgs && !mDefaultValue.has_value()) {
std::stringstream stream; std::stringstream stream;
stream << "error: " << mUsedName << ": expected " << mNumArgs << " argument(s). " stream << "error: " << mUsedName << ": expected " << mNumArgs
<< mValues.size() << " provided."; << " argument(s). " << mValues.size() << " provided.";
throw std::runtime_error(stream.str()); throw std::runtime_error(stream.str());
} }
} }
} }
size_t get_arguments_length() const { size_t get_arguments_length() const {
return std::accumulate(std::begin(mNames), std::end(mNames), size_t(0), [](const auto& sum, const auto& s) { return std::accumulate(std::begin(mNames), std::end(mNames), size_t(0),
return sum + s.size() + 1; // +1 for space between names [](const auto &sum, const auto &s) {
}); return sum + s.size() +
1; // +1 for space between names
});
} }
friend std::ostream& operator<<(std::ostream& stream, const Argument& argument) { friend std::ostream &operator<<(std::ostream &stream,
const Argument &argument) {
std::stringstream nameStream; std::stringstream nameStream;
std::copy(std::begin(argument.mNames), std::end(argument.mNames), std::ostream_iterator<std::string>(nameStream, " ")); std::copy(std::begin(argument.mNames), std::end(argument.mNames),
stream << nameStream.str() << "\t" << argument.mHelp; std::ostream_iterator<std::string>(nameStream, " "));
if (argument.mIsRequired) stream << nameStream.str() << "\t" << argument.mHelp;
stream << "[Required]"; if (argument.mIsRequired)
stream << "\n"; stream << "[Required]";
stream << "\n";
return stream; return stream;
} }
template <typename T> bool operator!=(const T &aRhs) const {
template <typename T>
bool operator!=(const T& aRhs) const {
return !(*this == aRhs); return !(*this == aRhs);
} }
@ -212,8 +208,7 @@ public:
* @throws std::logic_error in case of incompatible types * @throws std::logic_error in case of incompatible types
*/ */
template <typename T> template <typename T>
std::enable_if_t <!is_container_v<T>, bool> std::enable_if_t<!is_container_v<T>, bool> operator==(const T &aRhs) const {
operator==(const T& aRhs) const {
return get<T>() == aRhs; return get<T>() == aRhs;
} }
@ -222,324 +217,324 @@ public:
* @throws std::logic_error in case of incompatible types * @throws std::logic_error in case of incompatible types
*/ */
template <typename T> template <typename T>
std::enable_if_t <is_container_v<T>, bool> std::enable_if_t<is_container_v<T>, bool> operator==(const T &aRhs) const {
operator==(const T& aRhs) const {
using ValueType = typename T::value_type; using ValueType = typename T::value_type;
auto tLhs = get<T>(); auto tLhs = get<T>();
if (tLhs.size() != aRhs.size()) if (tLhs.size() != aRhs.size())
return false; return false;
else { else {
return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs), [](const auto& lhs, const auto& rhs) { return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs),
return std::any_cast<const ValueType&>(lhs) == rhs; [](const auto &lhs, const auto &rhs) {
}); return std::any_cast<const ValueType &>(lhs) == rhs;
});
} }
} }
private: private:
static bool is_integer(const std::string &aValue) {
if (aValue.empty() ||
((!isdigit(aValue[0])) && (aValue[0] != '-') && (aValue[0] != '+')))
return false;
char *tPtr;
strtol(aValue.c_str(), &tPtr, 10);
return (*tPtr == 0);
}
static bool is_integer(const std::string& aValue) { static bool is_float(const std::string &aValue) {
if(aValue.empty() || std::istringstream tStream(aValue);
((!isdigit(aValue[0])) && (aValue[0] != '-') && (aValue[0] != '+'))) float tFloat;
return false; // noskipws considers leading whitespace invalid
char * tPtr; tStream >> std::noskipws >> tFloat;
strtol(aValue.c_str(), &tPtr, 10); // Check the entire string was consumed
return (*tPtr == 0); // and if either failbit or badbit is set
return tStream.eof() && !tStream.fail();
}
// If an argument starts with "-" or "--", then it's optional
static bool is_optional(const std::string &aName) {
return (!aName.empty() && aName[0] == '-' && !is_integer(aName) &&
!is_float(aName));
}
static bool is_positional(const std::string &aName) {
return !is_optional(aName);
}
/*
* Getter for template non-container types
* @throws std::logic_error in case of incompatible types
*/
template <typename T> enable_if_not_container<T> get() const {
if (!mValues.empty()) {
return std::any_cast<T>(mValues.front());
} }
if (mDefaultValue.has_value()) {
static bool is_float(const std::string& aValue) { return std::any_cast<T>(mDefaultValue);
std::istringstream tStream(aValue);
float tFloat;
// noskipws considers leading whitespace invalid
tStream >> std::noskipws >> tFloat;
// Check the entire string was consumed
// and if either failbit or badbit is set
return tStream.eof() && !tStream.fail();
} }
throw std::logic_error("No value provided");
// If an argument starts with "-" or "--", then it's optional }
static bool is_optional(const std::string& aName) {
return (!aName.empty() && aName[0] == '-' && /*
!is_integer(aName) && !is_float(aName)); * Getter for container types
* @throws std::logic_error in case of incompatible types
*/
template <typename CONTAINER> enable_if_container<CONTAINER> get() const {
using ValueType = typename CONTAINER::value_type;
CONTAINER tResult;
if (!mValues.empty()) {
std::transform(
std::begin(mValues), std::end(mValues), std::back_inserter(tResult),
[](const auto &value) { return std::any_cast<ValueType>(value); });
return tResult;
} }
if (mDefaultValue.has_value()) {
static bool is_positional(const std::string& aName) { const auto &tDefaultValues =
return !is_optional(aName); std::any_cast<const CONTAINER &>(mDefaultValue);
std::transform(std::begin(tDefaultValues), std::end(tDefaultValues),
std::back_inserter(tResult), [](const auto &value) {
return std::any_cast<ValueType>(value);
});
return tResult;
} }
throw std::logic_error("No value provided");
}
/* std::vector<std::string> mNames;
* Getter for template non-container types std::string mUsedName;
* @throws std::logic_error in case of incompatible types std::string mHelp;
*/ std::any mDefaultValue;
template <typename T> std::any mImplicitValue;
enable_if_not_container<T> std::function<std::any(const std::string &)> mAction =
get() const { [](const std::string &aValue) { return aValue; };
if (!mValues.empty()) { std::vector<std::any> mValues;
return std::any_cast<T>(mValues.front()); std::vector<std::string> mRawValues;
} size_t mNumArgs = 1;
if (mDefaultValue.has_value()) { bool mIsOptional = false;
return std::any_cast<T>(mDefaultValue); bool mIsRequired = false;
} bool mIsUsed = false; // relevant for optional arguments. True if used by user
throw std::logic_error("No value provided");
}
/* public:
* Getter for container types static constexpr auto mHelpOption = "-h";
* @throws std::logic_error in case of incompatible types static constexpr auto mHelpOptionLong = "--help";
*/
template <typename CONTAINER>
enable_if_container<CONTAINER>
get() const {
using ValueType = typename CONTAINER::value_type;
CONTAINER tResult;
if (!mValues.empty()) {
std::transform(std::begin(mValues), std::end(mValues), std::back_inserter(tResult), [](const auto & value) {
return std::any_cast<ValueType>(value);
});
return tResult;
}
if (mDefaultValue.has_value()) {
const auto& tDefaultValues = std::any_cast<const CONTAINER&>(mDefaultValue);
std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), std::back_inserter(tResult), [](const auto & value) {
return std::any_cast<ValueType>(value);
});
return tResult;
}
throw std::logic_error("No value provided");
}
std::vector<std::string> mNames;
std::string mUsedName;
std::string mHelp;
std::any mDefaultValue;
std::any mImplicitValue;
std::function<std::any(const std::string&)> mAction = [](const std::string& aValue) { return aValue; };
std::vector<std::any> mValues;
std::vector<std::string> mRawValues;
size_t mNumArgs = 1;
bool mIsOptional = false;
bool mIsRequired = false;
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 {
public: public:
explicit ArgumentParser(std::string aProgramName = {}) : explicit ArgumentParser(std::string aProgramName = {})
mProgramName(std::move(aProgramName)) : mProgramName(std::move(aProgramName)) {
{ add_argument(Argument::mHelpOption, Argument::mHelpOptionLong)
add_argument(Argument::mHelpOption, Argument::mHelpOptionLong)
.help("show this help message and exit") .help("show this help message and exit")
.nargs(0) .nargs(0)
.default_value(false) .default_value(false)
.implicit_value(true); .implicit_value(true);
}
// Parameter packing
// Call add_argument with variadic number of string arguments
template <typename... Targs> Argument &add_argument(Targs... Fargs) {
std::shared_ptr<Argument> tArgument =
std::make_shared<Argument>(std::move(Fargs)...);
if (tArgument->mIsOptional)
mOptionalArguments.emplace_back(tArgument);
else
mPositionalArguments.emplace_back(tArgument);
for (const auto &mName : tArgument->mNames) {
mArgumentMap.insert_or_assign(mName, tArgument);
} }
return *tArgument;
}
// Parameter packing // Parameter packed add_parents method
// Call add_argument with variadic number of string arguments // Accepts a variadic number of ArgumentParser objects
template<typename... Targs> template <typename... Targs> void add_parents(Targs... Fargs) {
Argument& add_argument(Targs... Fargs) { const auto tNewParentParsers = {Fargs...};
std::shared_ptr<Argument> tArgument = std::make_shared<Argument>(std::move(Fargs)...); for (const auto &tParentParser : tNewParentParsers) {
const auto &tPositionalArguments = tParentParser.mPositionalArguments;
std::copy(std::begin(tPositionalArguments),
std::end(tPositionalArguments),
std::back_inserter(mPositionalArguments));
if (tArgument->mIsOptional) const auto &tOptionalArguments = tParentParser.mOptionalArguments;
mOptionalArguments.emplace_back(tArgument); std::copy(std::begin(tOptionalArguments), std::end(tOptionalArguments),
else std::back_inserter(mOptionalArguments));
mPositionalArguments.emplace_back(tArgument);
for (const auto& mName : tArgument->mNames) { const auto &tArgumentMap = tParentParser.mArgumentMap;
mArgumentMap.insert_or_assign(mName, tArgument); for (const auto &[tKey, tValue] : tArgumentMap) {
mArgumentMap.insert_or_assign(tKey, tValue);
} }
return *tArgument; }
std::move(std::begin(tNewParentParsers), std::end(tNewParentParsers),
std::back_inserter(mParentParsers));
}
/* Call parse_args_internal - which does all the work
* Then, validate the parsed arguments
* This variant is used mainly for testing
* @throws std::runtime_error in case of any invalid argument
*/
void parse_args(const std::vector<std::string> &aArguments) {
parse_args_internal(aArguments);
parse_args_validate();
}
/* Main entry point for parsing command-line arguments using this
* ArgumentParser
* @throws std::runtime_error in case of any invalid argument
*/
void parse_args(int argc, const char *const argv[]) {
std::vector<std::string> arguments;
std::copy(argv, argv + argc, std::back_inserter(arguments));
parse_args(arguments);
}
/* Getter enabled for all template types other than std::vector and std::list
* @throws std::logic_error in case of an invalid argument name
* @throws std::logic_error in case of incompatible types
*/
template <typename T = std::string> T get(const std::string &aArgumentName) {
auto tIterator = mArgumentMap.find(aArgumentName);
if (tIterator != mArgumentMap.end()) {
return tIterator->second->get<T>();
}
throw std::logic_error("No such argument");
}
/* Indexing operator. Return a reference to an Argument object
* Used in conjuction with Argument.operator== e.g., parser["foo"] == true
* @throws std::logic_error in case of an invalid argument name
*/
Argument &operator[](const std::string &aArgumentName) {
auto tIterator = mArgumentMap.find(aArgumentName);
if (tIterator != mArgumentMap.end()) {
return *(tIterator->second);
}
throw std::logic_error("No such argument");
}
// Printing the one and only help message
// I've stuck with a simple message format, nothing fancy.
// TODO: support user-defined help and usage messages for the ArgumentParser
std::string print_help() {
std::stringstream stream;
stream << std::left;
stream << "Usage: " << mProgramName << " [options] ";
size_t tLongestArgumentLength = get_length_of_longest_argument();
for (const auto &argument : mPositionalArguments) {
stream << argument->mNames.front() << " ";
}
stream << "\n\n";
if (!mPositionalArguments.empty())
stream << "Positional arguments:\n";
for (const auto &mPositionalArgument : mPositionalArguments) {
stream.width(tLongestArgumentLength);
stream << *mPositionalArgument;
} }
// Parameter packed add_parents method if (!mOptionalArguments.empty())
// Accepts a variadic number of ArgumentParser objects stream << (mPositionalArguments.empty() ? "" : "\n")
template<typename... Targs> << "Optional arguments:\n";
void add_parents(Targs... Fargs) {
const auto tNewParentParsers = {Fargs...};
for (const auto& tParentParser : tNewParentParsers) {
const auto& tPositionalArguments = tParentParser.mPositionalArguments;
std::copy(std::begin(tPositionalArguments), std::end(tPositionalArguments), std::back_inserter(mPositionalArguments));
const auto& tOptionalArguments = tParentParser.mOptionalArguments; for (const auto &mOptionalArgument : mOptionalArguments) {
std::copy(std::begin(tOptionalArguments), std::end(tOptionalArguments), std::back_inserter(mOptionalArguments)); stream.width(tLongestArgumentLength);
stream << *mOptionalArgument;
}
const auto& tArgumentMap = tParentParser.mArgumentMap; std::cout << stream.str();
for (const auto&[tKey, tValue] : tArgumentMap) { return stream.str();
mArgumentMap.insert_or_assign(tKey, tValue); }
private:
/*
* @throws std::runtime_error in case of any invalid argument
*/
void parse_args_internal(const std::vector<std::string> &aArguments) {
if (mProgramName.empty() && !aArguments.empty()) {
mProgramName = aArguments.front();
}
auto end = std::end(aArguments);
auto positionalArgumentIt = std::begin(mPositionalArguments);
for (auto it = std::next(std::begin(aArguments)); it != end;) {
const auto &tCurrentArgument = *it;
if (tCurrentArgument == Argument::mHelpOption ||
tCurrentArgument == Argument::mHelpOptionLong) {
throw std::runtime_error("help called");
}
if (Argument::is_positional(tCurrentArgument)) {
if (positionalArgumentIt == std::end(mPositionalArguments)) {
throw std::runtime_error(
"Maximum number of positional arguments exceeded");
} }
} auto tArgument = *(positionalArgumentIt++);
std::move(std::begin(tNewParentParsers), std::end(tNewParentParsers), std::back_inserter(mParentParsers)); it = tArgument->consume(it, end);
} } else if (auto tIterator = mArgumentMap.find(tCurrentArgument);
tIterator != mArgumentMap.end()) {
/* Call parse_args_internal - which does all the work auto tArgument = tIterator->second;
* Then, validate the parsed arguments it = tArgument->consume(std::next(it), end, tCurrentArgument);
* This variant is used mainly for testing } else if (const auto &tCompoundArgument = tCurrentArgument;
* @throws std::runtime_error in case of any invalid argument tCompoundArgument.size() > 1 && tCompoundArgument[0] == '-' &&
*/
void parse_args(const std::vector<std::string>& aArguments) {
parse_args_internal(aArguments);
parse_args_validate();
}
/* Main entry point for parsing command-line arguments using this ArgumentParser
* @throws std::runtime_error in case of any invalid argument
*/
void parse_args(int argc, const char * const argv[]) {
std::vector<std::string> arguments;
std::copy(argv, argv + argc, std::back_inserter(arguments));
parse_args(arguments);
}
/* Getter enabled for all template types other than std::vector and std::list
* @throws std::logic_error in case of an invalid argument name
* @throws std::logic_error in case of incompatible types
*/
template <typename T = std::string>
T get(const std::string& aArgumentName) {
auto tIterator = mArgumentMap.find(aArgumentName);
if (tIterator != mArgumentMap.end()) {
return tIterator->second->get<T>();
}
throw std::logic_error("No such argument");
}
/* Indexing operator. Return a reference to an Argument object
* Used in conjuction with Argument.operator== e.g., parser["foo"] == true
* @throws std::logic_error in case of an invalid argument name
*/
Argument& operator[](const std::string& aArgumentName) {
auto tIterator = mArgumentMap.find(aArgumentName);
if (tIterator != mArgumentMap.end()) {
return *(tIterator->second);
}
throw std::logic_error("No such argument");
}
// Printing the one and only help message
// I've stuck with a simple message format, nothing fancy.
// TODO: support user-defined help and usage messages for the ArgumentParser
std::string print_help() {
std::stringstream stream;
stream << std::left;
stream << "Usage: " << mProgramName << " [options] ";
size_t tLongestArgumentLength = get_length_of_longest_argument();
for (const auto& argument : mPositionalArguments) {
stream << argument->mNames.front() << " ";
}
stream << "\n\n";
if (!mPositionalArguments.empty())
stream << "Positional arguments:\n";
for (const auto& mPositionalArgument : mPositionalArguments) {
stream.width(tLongestArgumentLength);
stream << *mPositionalArgument;
}
if (!mOptionalArguments.empty())
stream << (mPositionalArguments.empty() ? "" : "\n") << "Optional arguments:\n";
for (const auto & mOptionalArgument : mOptionalArguments) {
stream.width(tLongestArgumentLength);
stream << *mOptionalArgument;
}
std::cout << stream.str();
return stream.str();
}
private:
/*
* @throws std::runtime_error in case of any invalid argument
*/
void parse_args_internal(const std::vector<std::string>& aArguments) {
if (mProgramName.empty() && !aArguments.empty()) {
mProgramName = aArguments.front();
}
auto end = std::end(aArguments);
auto positionalArgumentIt = std::begin(mPositionalArguments);
for (auto it = std::next(std::begin(aArguments)); it != end;) {
const auto& tCurrentArgument = *it;
if (tCurrentArgument == Argument::mHelpOption || tCurrentArgument == Argument::mHelpOptionLong) {
throw std::runtime_error("help called");
}
if (Argument::is_positional(tCurrentArgument)) {
if (positionalArgumentIt == std::end(mPositionalArguments)) {
throw std::runtime_error("Maximum number of positional arguments exceeded");
}
auto tArgument = *(positionalArgumentIt++);
it = tArgument->consume(it, end);
}
else if (auto tIterator = mArgumentMap.find(tCurrentArgument); tIterator != mArgumentMap.end()) {
auto tArgument = tIterator->second;
it = tArgument->consume(std::next(it), end, tCurrentArgument);
}
else if (const auto& tCompoundArgument = tCurrentArgument;
tCompoundArgument.size() > 1 &&
tCompoundArgument[0] == '-' &&
tCompoundArgument[1] != '-') { tCompoundArgument[1] != '-') {
++it; ++it;
for (size_t j = 1; j < tCompoundArgument.size(); j++) { for (size_t j = 1; j < tCompoundArgument.size(); j++) {
auto tCurrentArgument = std::string{'-', tCompoundArgument[j]}; auto tCurrentArgument = std::string{'-', tCompoundArgument[j]};
if (auto tIterator = mArgumentMap.find(tCurrentArgument); tIterator != mArgumentMap.end()) { if (auto tIterator = mArgumentMap.find(tCurrentArgument);
auto tArgument = tIterator->second; tIterator != mArgumentMap.end()) {
it = tArgument->consume(it, end, tCurrentArgument); auto tArgument = tIterator->second;
} it = tArgument->consume(it, end, tCurrentArgument);
else { } else {
throw std::runtime_error("Unknown argument"); throw std::runtime_error("Unknown argument");
}
} }
} }
else { } else {
throw std::runtime_error("Unknown argument"); throw std::runtime_error("Unknown argument");
}
} }
} }
}
/* /*
* @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 arguments are parsed // Check if all arguments are parsed
std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap), std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap),
[](const auto& argPair) { [](const auto &argPair) {
const auto& tArgument = argPair.second; const auto &tArgument = argPair.second;
tArgument->validate(); tArgument->validate();
}); });
} }
// Used by print_help. // Used by print_help.
size_t get_length_of_longest_argument() { size_t get_length_of_longest_argument() {
if (mArgumentMap.empty()) if (mArgumentMap.empty())
return 0; return 0;
std::vector<size_t> argumentLengths(mArgumentMap.size()); std::vector<size_t> argumentLengths(mArgumentMap.size());
std::transform(std::begin(mArgumentMap), std::end(mArgumentMap), std::transform(std::begin(mArgumentMap), std::end(mArgumentMap),
std::begin(argumentLengths), [](const auto& argPair) { std::begin(argumentLengths), [](const auto &argPair) {
const auto& tArgument = argPair.second; const auto &tArgument = argPair.second;
return tArgument->get_arguments_length(); return tArgument->get_arguments_length();
}); });
return *std::max_element(std::begin(argumentLengths), return *std::max_element(std::begin(argumentLengths),
std::end(argumentLengths)); std::end(argumentLengths));
} }
std::string mProgramName; std::string mProgramName;
std::vector<ArgumentParser> mParentParsers; std::vector<ArgumentParser> mParentParsers;
std::vector<std::shared_ptr<Argument>> mPositionalArguments; std::vector<std::shared_ptr<Argument>> mPositionalArguments;
std::vector<std::shared_ptr<Argument>> mOptionalArguments; std::vector<std::shared_ptr<Argument>> mOptionalArguments;
std::map<std::string, std::shared_ptr<Argument>> mArgumentMap; std::map<std::string, std::shared_ptr<Argument>> mArgumentMap;
}; };
#define PARSE_ARGS(parser, argc, argv) \ #define PARSE_ARGS(parser, argc, argv) \
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::cout << err.what() << std::endl; \ std::cout << err.what() << std::endl; \
parser.print_help(); \ parser.print_help(); \
exit(0); \ exit(0); \
} }
} } // namespace argparse