mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-03 22:54:39 +00:00
Renamed directories
This commit is contained in:
parent
11729b4dfe
commit
bb4a2dbba7
682
include/argparse.hpp
Normal file
682
include/argparse.hpp
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
/*
|
||||||
|
__ _ _ __ __ _ _ __ __ _ _ __ ___ ___
|
||||||
|
/ _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++
|
||||||
|
| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse
|
||||||
|
\__,_|_| \__, | .__/ \__,_|_| |___/\___|
|
||||||
|
|___/|_|
|
||||||
|
|
||||||
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
Copyright (c) 2019 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include <functional>
|
||||||
|
#include <any>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace argparse {
|
||||||
|
|
||||||
|
// Some utility structs to check template specialization
|
||||||
|
template<typename Test, template<typename...> class Ref>
|
||||||
|
struct is_specialization : std::false_type {};
|
||||||
|
|
||||||
|
template<template<typename...> class Ref, typename... Args>
|
||||||
|
struct is_specialization<Ref<Args...>, Ref> : std::true_type {};
|
||||||
|
|
||||||
|
// Upsert into std::map
|
||||||
|
template <class KeyType, class ElementType>
|
||||||
|
bool upsert(std::map<KeyType, ElementType>& aMap, KeyType const& aKey, ElementType const& aNewValue) {
|
||||||
|
typedef typename std::map<KeyType, ElementType>::iterator Iterator;
|
||||||
|
typedef typename std::pair<Iterator, bool> Result;
|
||||||
|
Result tResult = aMap.insert(typename std::map<KeyType, ElementType>::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 <typename T>
|
||||||
|
T get_from_list(const std::list<T>& aList, size_t aIndex) {
|
||||||
|
if (aList.size() > aIndex) {
|
||||||
|
auto tIterator = aList.begin();
|
||||||
|
std::advance(tIterator, aIndex);
|
||||||
|
return *tIterator;
|
||||||
|
}
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Argument {
|
||||||
|
friend class ArgumentParser;
|
||||||
|
public:
|
||||||
|
Argument() :
|
||||||
|
mNames({}),
|
||||||
|
mHelp(""),
|
||||||
|
mAction([](const std::string& aValue) { return aValue; }),
|
||||||
|
mValues({}),
|
||||||
|
mRawValues({}),
|
||||||
|
mNumArgs(1),
|
||||||
|
mIsOptional(false),
|
||||||
|
mIsUsed(false) {}
|
||||||
|
|
||||||
|
Argument& help(const std::string& aHelp) {
|
||||||
|
mHelp = aHelp;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument& default_value(std::any aDefaultValue) {
|
||||||
|
mDefaultValue = aDefaultValue;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument& implicit_value(std::any aImplicitValue) {
|
||||||
|
mImplicitValue = aImplicitValue;
|
||||||
|
mNumArgs = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument& action(std::function<std::any(const std::string&)> aAction) {
|
||||||
|
mAction = aAction;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument& nargs(size_t aNumArgs) {
|
||||||
|
mNumArgs = aNumArgs;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool operator!=(const T& aRhs) const {
|
||||||
|
return !(*this == aRhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point for template types other than std::vector and std::list
|
||||||
|
template <typename T>
|
||||||
|
typename std::enable_if<is_specialization<T, std::vector>::value == false &&
|
||||||
|
is_specialization<T, std::list>::value == false, bool>::type
|
||||||
|
operator==(const T& aRhs) const {
|
||||||
|
return get<T>() == aRhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template specialization for std::vector<...>
|
||||||
|
template <typename T>
|
||||||
|
typename std::enable_if<is_specialization<T, std::vector>::value, bool>::type
|
||||||
|
operator==(const T& aRhs) const {
|
||||||
|
T tLhs = get_vector<T>();
|
||||||
|
if (tLhs.size() != aRhs.size())
|
||||||
|
return false;
|
||||||
|
else {
|
||||||
|
for (size_t i = 0; i < tLhs.size(); i++) {
|
||||||
|
auto tValueAtIndex = std::any_cast<typename T::value_type>(tLhs[i]);
|
||||||
|
if (tValueAtIndex != aRhs[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template specialization for std::list<...>
|
||||||
|
template <typename T>
|
||||||
|
typename std::enable_if<is_specialization<T, std::list>::value, bool>::type
|
||||||
|
operator==(const T& aRhs) const {
|
||||||
|
T tLhs = get_list<T>();
|
||||||
|
if (tLhs.size() != aRhs.size())
|
||||||
|
return false;
|
||||||
|
else {
|
||||||
|
for (size_t i = 0; i < tLhs.size(); i++) {
|
||||||
|
auto tValueAtIndex = std::any_cast<typename T::value_type>(get_from_list(tLhs, i));
|
||||||
|
if (tValueAtIndex != get_from_list(aRhs, i))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Getter for template types other than std::vector and std::list
|
||||||
|
template <typename T>
|
||||||
|
T get() const {
|
||||||
|
if (mValues.size() == 0) {
|
||||||
|
if (mDefaultValue.has_value()) {
|
||||||
|
return std::any_cast<T>(mDefaultValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (mRawValues.size() > 0)
|
||||||
|
return std::any_cast<T>(mValues[0]);
|
||||||
|
else {
|
||||||
|
if (mDefaultValue.has_value())
|
||||||
|
return std::any_cast<T>(mDefaultValue);
|
||||||
|
else
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter for std::vector. Here T = std::vector<...>
|
||||||
|
template <typename T>
|
||||||
|
T get_vector() const {
|
||||||
|
T tResult;
|
||||||
|
if (mValues.size() == 0) {
|
||||||
|
if (mDefaultValue.has_value()) {
|
||||||
|
T tDefaultValues = std::any_cast<T>(mDefaultValue);
|
||||||
|
for (size_t i = 0; i < tDefaultValues.size(); i++) {
|
||||||
|
tResult.push_back(std::any_cast<typename T::value_type>(tDefaultValues[i]));
|
||||||
|
}
|
||||||
|
return tResult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (mRawValues.size() > 0) {
|
||||||
|
for (size_t i = 0; i < mValues.size(); i++) {
|
||||||
|
tResult.push_back(std::any_cast<typename T::value_type>(mValues[i]));
|
||||||
|
}
|
||||||
|
return tResult;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (mDefaultValue.has_value()) {
|
||||||
|
std::vector<T> tDefaultValues = std::any_cast<std::vector<T>>(mDefaultValue);
|
||||||
|
for (size_t i = 0; i < tDefaultValues.size(); i++) {
|
||||||
|
tResult.push_back(std::any_cast<typename T::value_type>(tDefaultValues[i]));
|
||||||
|
}
|
||||||
|
return tResult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter for std::list. Here T = std::list<...>
|
||||||
|
template <typename T>
|
||||||
|
T get_list() const {
|
||||||
|
T tResult;
|
||||||
|
if (mValues.size() == 0) {
|
||||||
|
if (mDefaultValue.has_value()) {
|
||||||
|
T tDefaultValues = std::any_cast<T>(mDefaultValue);
|
||||||
|
for (size_t i = 0; i < tDefaultValues.size(); i++) {
|
||||||
|
tResult.push_back(std::any_cast<typename T::value_type>(get_from_list(tDefaultValues, i)));
|
||||||
|
}
|
||||||
|
return tResult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (mRawValues.size() > 0) {
|
||||||
|
for (size_t i = 0; i < mValues.size(); i++) {
|
||||||
|
tResult.push_back(std::any_cast<typename T::value_type>(mValues[i]));
|
||||||
|
}
|
||||||
|
return tResult;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (mDefaultValue.has_value()) {
|
||||||
|
std::list<T> tDefaultValues = std::any_cast<std::list<T>>(mDefaultValue);
|
||||||
|
for (size_t i = 0; i < tDefaultValues.size(); i++) {
|
||||||
|
tResult.push_back(std::any_cast<typename T::value_type>(get_from_list(tDefaultValues, i)));
|
||||||
|
}
|
||||||
|
return tResult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> mNames;
|
||||||
|
std::string mHelp;
|
||||||
|
std::any mDefaultValue;
|
||||||
|
std::any mImplicitValue;
|
||||||
|
std::function<std::any(const std::string&)> mAction;
|
||||||
|
std::vector<std::any> mValues;
|
||||||
|
std::vector<std::string> mRawValues;
|
||||||
|
size_t mNumArgs;
|
||||||
|
bool mIsOptional;
|
||||||
|
bool mIsUsed; // relevant for optional arguments. True if used by user
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArgumentParser {
|
||||||
|
public:
|
||||||
|
ArgumentParser(const std::string& aProgramName = "") :
|
||||||
|
mProgramName(aProgramName),
|
||||||
|
mNextPositionalArgument(0) {
|
||||||
|
std::shared_ptr<Argument> tArgument = std::make_shared<Argument>();
|
||||||
|
tArgument->mNames = { "-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter packing
|
||||||
|
// Call add_argument with variadic number of string arguments
|
||||||
|
// TODO: enforce T to be std::string
|
||||||
|
template<typename T, typename... Targs>
|
||||||
|
Argument& add_argument(T value, Targs... Fargs) {
|
||||||
|
std::shared_ptr<Argument> tArgument = std::make_shared<Argument>();
|
||||||
|
tArgument->mNames.push_back(value);
|
||||||
|
add_argument_internal(tArgument, Fargs...);
|
||||||
|
|
||||||
|
for (auto& mName : tArgument->mNames) {
|
||||||
|
if (is_optional(mName))
|
||||||
|
tArgument->mIsOptional = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tArgument->mIsOptional)
|
||||||
|
mPositionalArguments.push_back(tArgument);
|
||||||
|
else
|
||||||
|
mOptionalArguments.push_back(tArgument);
|
||||||
|
|
||||||
|
for (auto& mName : tArgument->mNames) {
|
||||||
|
upsert(mArgumentMap, 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];
|
||||||
|
auto tPositionalArguments = tParentParser.mPositionalArguments;
|
||||||
|
for (auto& tArgument : tPositionalArguments) {
|
||||||
|
mPositionalArguments.push_back(tArgument);
|
||||||
|
}
|
||||||
|
auto tOptionalArguments = tParentParser.mOptionalArguments;
|
||||||
|
for (auto& tArgument : tOptionalArguments) {
|
||||||
|
mOptionalArguments.push_back(tArgument);
|
||||||
|
}
|
||||||
|
auto tArgumentMap = tParentParser.mArgumentMap;
|
||||||
|
for (auto&[tKey, tValue] : tArgumentMap) {
|
||||||
|
upsert(mArgumentMap, tKey, tValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.push_back(aArgumentParser);
|
||||||
|
add_parents(Fargs...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call parse_args_internal - which does all the work
|
||||||
|
// Then, validate the parsed arguments
|
||||||
|
// This variant is used mainly for testing
|
||||||
|
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
|
||||||
|
void parse_args(int argc, char * argv[]) {
|
||||||
|
parse_args_internal(argc, argv);
|
||||||
|
parse_args_validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter enabled for all template types other than std::vector and std::list
|
||||||
|
template <typename T = std::string>
|
||||||
|
typename std::enable_if<is_specialization<T, std::vector>::value == false &&
|
||||||
|
is_specialization<T, std::list>::value == false, T>::type
|
||||||
|
get(const char * aArgumentName) {
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>>::iterator tIterator = mArgumentMap.find(aArgumentName);
|
||||||
|
if (tIterator != mArgumentMap.end()) {
|
||||||
|
return tIterator->second->get<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) {
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>>::iterator 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) {
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>>::iterator 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
|
||||||
|
// Used in conjuction with Argument.operator== e.g., parser["foo"] == true
|
||||||
|
Argument& operator[](const std::string& aArgumentName) {
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>>::iterator tIterator = mArgumentMap.find(aArgumentName);
|
||||||
|
if (tIterator != mArgumentMap.end()) {
|
||||||
|
return *(tIterator->second);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("Argument " + aArgumentName + " not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 << "Usage: " << mProgramName << " [options]";
|
||||||
|
size_t tLongestArgumentLength = get_length_of_longest_argument();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mPositionalArguments.size(); i++) {
|
||||||
|
auto tNames = mPositionalArguments[i]->mNames;
|
||||||
|
stream << (i == 0 ? " " : "") << tNames[0] << " ";
|
||||||
|
}
|
||||||
|
stream << "\n\n";
|
||||||
|
|
||||||
|
if (mPositionalArguments.size() > 0)
|
||||||
|
stream << "Positional arguments:\n";
|
||||||
|
for (size_t i = 0; i < mPositionalArguments.size(); i++) {
|
||||||
|
size_t tCurrentLength = 0;
|
||||||
|
auto tNames = mPositionalArguments[i]->mNames;
|
||||||
|
for (size_t j = 0; j < tNames.size() - 1; j++) {
|
||||||
|
auto tCurrentName = tNames[j];
|
||||||
|
stream << tCurrentName;
|
||||||
|
stream << ", ";
|
||||||
|
tCurrentLength += tCurrentName.length() + 2;
|
||||||
|
}
|
||||||
|
stream << tNames[tNames.size() - 1];
|
||||||
|
tCurrentLength += tNames[tNames.size() - 1].length();
|
||||||
|
if (tCurrentLength < tLongestArgumentLength)
|
||||||
|
stream << std::string((tLongestArgumentLength - tCurrentLength) + 2, ' ');
|
||||||
|
else if (tCurrentLength == tLongestArgumentLength)
|
||||||
|
stream << std::string(2, ' ');
|
||||||
|
else
|
||||||
|
stream << std::string((tCurrentLength - tLongestArgumentLength) + 2, ' ');
|
||||||
|
|
||||||
|
stream << mPositionalArguments[i]->mHelp << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mOptionalArguments.size() > 0 && mPositionalArguments.size() > 0)
|
||||||
|
stream << "\nOptional arguments:\n";
|
||||||
|
else if (mOptionalArguments.size() > 0)
|
||||||
|
stream << "Optional arguments:\n";
|
||||||
|
for (size_t i = 0; i < mOptionalArguments.size(); i++) {
|
||||||
|
size_t tCurrentLength = 0;
|
||||||
|
auto tNames = mOptionalArguments[i]->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();
|
||||||
|
});
|
||||||
|
for (size_t j = 0; j < tNames.size() - 1; j++) {
|
||||||
|
auto tCurrentName = tNames[j];
|
||||||
|
stream << tCurrentName;
|
||||||
|
stream << ", ";
|
||||||
|
tCurrentLength += tCurrentName.length() + 2;
|
||||||
|
}
|
||||||
|
stream << tNames[tNames.size() - 1];
|
||||||
|
tCurrentLength += tNames[tNames.size() - 1].length();
|
||||||
|
if (tCurrentLength < tLongestArgumentLength)
|
||||||
|
stream << std::string((tLongestArgumentLength - tCurrentLength) + 2, ' ');
|
||||||
|
else if (tCurrentLength == tLongestArgumentLength)
|
||||||
|
stream << std::string(2, ' ');
|
||||||
|
else
|
||||||
|
stream << std::string((tCurrentLength - tLongestArgumentLength) + 2, ' ');
|
||||||
|
|
||||||
|
stream << mOptionalArguments[i]->mHelp << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << stream.str();
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Argument& add_argument_internal(std::shared_ptr<Argument> aArgument) {
|
||||||
|
return *aArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Targs>
|
||||||
|
Argument& add_argument_internal(std::shared_ptr<Argument> 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<std::string, std::shared_ptr<Argument>>::iterator tIterator = mArgumentMap.find(aName);
|
||||||
|
return (tIterator != mArgumentMap.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_args_internal(const std::vector<std::string>& aArguments) {
|
||||||
|
std::vector<char*> argv;
|
||||||
|
for (const auto& arg : aArguments)
|
||||||
|
argv.push_back((char*)arg.data());
|
||||||
|
argv.push_back(nullptr);
|
||||||
|
return parse_args_internal(argv.size() - 1, argv.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_args_internal(int argc, char * argv[]) {
|
||||||
|
if (mProgramName == "" && argc > 0)
|
||||||
|
mProgramName = argv[0];
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
auto tCurrentArgument = std::string(argv[i]);
|
||||||
|
if (tCurrentArgument == "-h" || tCurrentArgument == "--help") {
|
||||||
|
print_help();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>>::iterator tIterator = mArgumentMap.find(argv[i]);
|
||||||
|
if (tIterator != mArgumentMap.end()) {
|
||||||
|
// Start parsing optional argument
|
||||||
|
auto tArgument = tIterator->second;
|
||||||
|
tArgument->mIsUsed = true;
|
||||||
|
auto tCount = tArgument->mNumArgs;
|
||||||
|
|
||||||
|
// Check to see if implicit value should be used
|
||||||
|
// Two cases to handle here:
|
||||||
|
// (1) User has explicitly programmed nargs to be 0
|
||||||
|
// (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("");
|
||||||
|
tCount = 0;
|
||||||
|
}
|
||||||
|
while (tCount > 0) {
|
||||||
|
i = i + 1;
|
||||||
|
if (i < argc) {
|
||||||
|
tArgument->mRawValues.push_back(argv[i]);
|
||||||
|
if (tArgument->mAction != nullptr)
|
||||||
|
tArgument->mValues.push_back(tArgument->mAction(argv[i]));
|
||||||
|
else {
|
||||||
|
if (tArgument->mDefaultValue.has_value())
|
||||||
|
tArgument->mValues.push_back(tArgument->mDefaultValue);
|
||||||
|
else
|
||||||
|
tArgument->mValues.push_back(std::string(argv[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tCount -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (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 ...
|
||||||
|
// Here -aux is a compound optional argument
|
||||||
|
std::string tCompoundArgument = std::string(argv[i]);
|
||||||
|
for (size_t j = 1; j < tCompoundArgument.size(); j++) {
|
||||||
|
std::string tArgument(1, tCompoundArgument[j]);
|
||||||
|
size_t tNumArgs = 0;
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>>::iterator tIterator = mArgumentMap.find("-" + tArgument);
|
||||||
|
if (tIterator != mArgumentMap.end()) {
|
||||||
|
auto tArgumentObject = tIterator->second;
|
||||||
|
tNumArgs = tArgumentObject->mNumArgs;
|
||||||
|
}
|
||||||
|
std::vector<std::string> tArgumentsForRecursiveParsing = { "", "-" + tArgument };
|
||||||
|
while (tNumArgs > 0 && i < argc) {
|
||||||
|
i += 1;
|
||||||
|
if (i < argc) {
|
||||||
|
tArgumentsForRecursiveParsing.push_back(argv[i]);
|
||||||
|
tNumArgs -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse_args_internal(tArgumentsForRecursiveParsing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// This is a positional argument.
|
||||||
|
// Parse and save into mPositionalArguments vector
|
||||||
|
auto tArgument = mPositionalArguments[mNextPositionalArgument];
|
||||||
|
auto tCount = tArgument->mNumArgs - tArgument->mRawValues.size();
|
||||||
|
while (tCount > 0) {
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>>::iterator tIterator = mArgumentMap.find(argv[i]);
|
||||||
|
if (tIterator != mArgumentMap.end() || is_optional(argv[i])) {
|
||||||
|
i = i - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i < argc) {
|
||||||
|
tArgument->mRawValues.push_back(argv[i]);
|
||||||
|
if (tArgument->mAction != nullptr)
|
||||||
|
tArgument->mValues.push_back(tArgument->mAction(argv[i]));
|
||||||
|
else {
|
||||||
|
if (tArgument->mDefaultValue.has_value())
|
||||||
|
tArgument->mValues.push_back(tArgument->mDefaultValue);
|
||||||
|
else
|
||||||
|
tArgument->mValues.push_back(std::string(argv[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tCount -= 1;
|
||||||
|
if (tCount > 0) i += 1;
|
||||||
|
}
|
||||||
|
if (tCount == 0)
|
||||||
|
mNextPositionalArgument += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_args_validate() {
|
||||||
|
// Check if all positional arguments are parsed
|
||||||
|
for (size_t i = 0; i < mPositionalArguments.size(); i++) {
|
||||||
|
auto tArgument = mPositionalArguments[i];
|
||||||
|
if (tArgument->mValues.size() != tArgument->mNumArgs) {
|
||||||
|
std::cout << "error: " << tArgument->mNames[0] << ": expected "
|
||||||
|
<< tArgument->mNumArgs << (tArgument->mNumArgs == 1 ? "argument. " : " arguments. ")
|
||||||
|
<< tArgument->mValues.size() << " provided.\n" << std::endl;
|
||||||
|
print_help();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all user-provided optional argument values are parsed correctly
|
||||||
|
for (size_t i = 0; i < mOptionalArguments.size(); i++) {
|
||||||
|
auto tArgument = mOptionalArguments[i];
|
||||||
|
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::cout << "error: " << tArgument->mNames[0] << ": expected "
|
||||||
|
<< tArgument->mNumArgs << (tArgument->mNumArgs == 1 ? "argument. " : " arguments. ")
|
||||||
|
<< tArgument->mValues.size() << " provided.\n" << std::endl;
|
||||||
|
print_help();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: check if an implicit value was programmed for this argument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by print_help.
|
||||||
|
size_t get_length_of_longest_argument() {
|
||||||
|
size_t tResult = 0;
|
||||||
|
for (size_t i = 0; i < mPositionalArguments.size(); i++) {
|
||||||
|
size_t tCurrentArgumentLength = 0;
|
||||||
|
auto tNames = mPositionalArguments[i]->mNames;
|
||||||
|
for (size_t j = 0; j < tNames.size() - 1; j++) {
|
||||||
|
auto tNameLength = tNames[j].length();
|
||||||
|
tCurrentArgumentLength += tNameLength + 2; // +2 for ", "
|
||||||
|
}
|
||||||
|
tCurrentArgumentLength += tNames[tNames.size() - 1].length();
|
||||||
|
if (tCurrentArgumentLength > tResult)
|
||||||
|
tResult = tCurrentArgumentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mOptionalArguments.size(); i++) {
|
||||||
|
size_t tCurrentArgumentLength = 0;
|
||||||
|
auto tNames = mOptionalArguments[i]->mNames;
|
||||||
|
for (size_t j = 0; j < tNames.size() - 1; j++) {
|
||||||
|
auto tNameLength = tNames[j].length();
|
||||||
|
tCurrentArgumentLength += tNameLength + 2; // +2 for ", "
|
||||||
|
}
|
||||||
|
tCurrentArgumentLength += tNames[tNames.size() - 1].length();
|
||||||
|
if (tCurrentArgumentLength > tResult)
|
||||||
|
tResult = tCurrentArgumentLength;
|
||||||
|
}
|
||||||
|
return tResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mProgramName;
|
||||||
|
std::vector<ArgumentParser> mParentParsers;
|
||||||
|
std::vector<std::shared_ptr<Argument>> mPositionalArguments;
|
||||||
|
std::vector<std::shared_ptr<Argument>> mOptionalArguments;
|
||||||
|
size_t mNextPositionalArgument;
|
||||||
|
std::map<std::string, std::shared_ptr<Argument>> mArgumentMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
2
test/.gitignore
vendored
Normal file
2
test/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
build_linux/
|
25
test/CMakeLists.txt
Normal file
25
test/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.6)
|
||||||
|
project(ARGPARSE)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Release)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Disable deprecation for windows
|
||||||
|
if (WIN32)
|
||||||
|
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ARGPARSE executable
|
||||||
|
file(GLOB ARGPARSE_TEST_SOURCES
|
||||||
|
"*.cpp"
|
||||||
|
"*.hpp"
|
||||||
|
"../include/argparse.hpp"
|
||||||
|
)
|
||||||
|
ADD_EXECUTABLE(ARGPARSE ${ARGPARSE_TEST_SOURCES})
|
||||||
|
INCLUDE_DIRECTORIES("../include" ".")
|
||||||
|
set_target_properties(ARGPARSE PROPERTIES OUTPUT_NAME tests)
|
||||||
|
set_property(TARGET ARGPARSE PROPERTY CXX_STANDARD 17)
|
||||||
|
|
||||||
|
# Set ${PROJECT_NAME} as the startup project
|
||||||
|
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE)
|
21
test/README.md
Normal file
21
test/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Argparse Tests
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mkdir build
|
||||||
|
$ cd build
|
||||||
|
$ cmake ../.
|
||||||
|
$ make
|
||||||
|
$ ./tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mkdir build
|
||||||
|
$ cd build
|
||||||
|
$ cmake ../. -G "Visual Studio 15 2017"
|
||||||
|
$ make
|
||||||
|
$ ./tests
|
||||||
|
```
|
15883
test/catch.hpp
Normal file
15883
test/catch.hpp
Normal file
File diff suppressed because it is too large
Load Diff
10
test/main.cpp
Normal file
10
test/main.cpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include <iostream>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
#include <test_parse_args.hpp>
|
||||||
|
#include <test_positional_arguments.hpp>
|
||||||
|
#include <test_optional_arguments.hpp>
|
||||||
|
#include <test_compound_arguments.hpp>
|
||||||
|
#include <test_actions.hpp>
|
||||||
|
#include <test_container_arguments.hpp>
|
||||||
|
#include <test_parent_parsers.hpp>
|
19
test/test_actions.hpp
Normal file
19
test/test_actions.hpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Users can use defaul value inside actions", "[actions]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("input")
|
||||||
|
.default_value("bar")
|
||||||
|
.action([=](const std::string& value) {
|
||||||
|
static const std::vector<std::string> choices = { "foo", "bar", "baz" };
|
||||||
|
if (std::find(choices.begin(), choices.end(), value) != choices.end()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return std::string{ "bar" };
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse_args({ "test", "fez" });
|
||||||
|
REQUIRE(program.get("input") == "bar");
|
||||||
|
}
|
152
test/test_compound_arguments.hpp
Normal file
152
test/test_compound_arguments.hpp
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Parse compound toggle arguments with implicit values", "[compound_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-a")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-u")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-x")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.parse_args({ "./test.exe", "-aux" });
|
||||||
|
REQUIRE(program.get<bool>("-a") == true);
|
||||||
|
REQUIRE(program.get<bool>("-u") == true);
|
||||||
|
REQUIRE(program.get<bool>("-x") == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse compound toggle arguments with implicit values and nargs", "[compound_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-a")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-b")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-c")
|
||||||
|
.nargs(2)
|
||||||
|
.action([](const std::string& value) { return std::stof(value); });
|
||||||
|
|
||||||
|
program.add_argument("--input_files")
|
||||||
|
.nargs(3);
|
||||||
|
|
||||||
|
program.parse_args({ "./test.exe", "-abc", "3.14", "2.718", "--input_files",
|
||||||
|
"a.txt", "b.txt", "c.txt" });
|
||||||
|
REQUIRE(program.get<bool>("-a") == true);
|
||||||
|
REQUIRE(program.get<bool>("-b") == true);
|
||||||
|
auto c = program.get<std::vector<float>>("-c");
|
||||||
|
REQUIRE(c.size() == 2);
|
||||||
|
REQUIRE(c[0] == 3.14f);
|
||||||
|
REQUIRE(c[1] == 2.718f);
|
||||||
|
auto input_files = program.get<std::vector<std::string>>("--input_files");
|
||||||
|
REQUIRE(input_files.size() == 3);
|
||||||
|
REQUIRE(input_files[0] == "a.txt");
|
||||||
|
REQUIRE(input_files[1] == "b.txt");
|
||||||
|
REQUIRE(input_files[2] == "c.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse compound toggle arguments with implicit values and nargs and other positional arguments", "[compound_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
program.add_argument("numbers")
|
||||||
|
.nargs(3)
|
||||||
|
.action([](const std::string& value) { return std::stoi(value); });
|
||||||
|
|
||||||
|
program.add_argument("-a")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-b")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-c")
|
||||||
|
.nargs(2)
|
||||||
|
.action([](const std::string& value) { return std::stof(value); });
|
||||||
|
|
||||||
|
program.add_argument("--input_files")
|
||||||
|
.nargs(3);
|
||||||
|
|
||||||
|
program.parse_args({ "./test.exe", "1", "-abc", "3.14", "2.718", "2", "--input_files",
|
||||||
|
"a.txt", "b.txt", "c.txt", "3" });
|
||||||
|
|
||||||
|
REQUIRE(program.get<bool>("-a") == true);
|
||||||
|
REQUIRE(program.get<bool>("-b") == true);
|
||||||
|
auto c = program.get<std::vector<float>>("-c");
|
||||||
|
REQUIRE(c.size() == 2);
|
||||||
|
REQUIRE(c[0] == 3.14f);
|
||||||
|
REQUIRE(c[1] == 2.718f);
|
||||||
|
auto input_files = program.get<std::vector<std::string>>("--input_files");
|
||||||
|
REQUIRE(input_files.size() == 3);
|
||||||
|
REQUIRE(input_files[0] == "a.txt");
|
||||||
|
REQUIRE(input_files[1] == "b.txt");
|
||||||
|
REQUIRE(input_files[2] == "c.txt");
|
||||||
|
auto numbers = program.get<std::vector<int>>("numbers");
|
||||||
|
REQUIRE(numbers.size() == 3);
|
||||||
|
REQUIRE(numbers[0] == 1);
|
||||||
|
REQUIRE(numbers[1] == 2);
|
||||||
|
REQUIRE(numbers[2] == 3);
|
||||||
|
auto numbers_list = program.get<std::list<int>>("numbers");
|
||||||
|
REQUIRE(numbers.size() == 3);
|
||||||
|
REQUIRE(argparse::get_from_list(numbers_list, 0) == 1);
|
||||||
|
REQUIRE(argparse::get_from_list(numbers_list, 1) == 2);
|
||||||
|
REQUIRE(argparse::get_from_list(numbers_list, 2) == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse out-of-order compound arguments", "[compound_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
program.add_argument("-a")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-b")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-c")
|
||||||
|
.nargs(2)
|
||||||
|
.action([](const std::string& value) { return std::stof(value); });
|
||||||
|
|
||||||
|
program.parse_args({ "./main", "-cab", "3.14", "2.718" });
|
||||||
|
|
||||||
|
auto a = program.get<bool>("-a"); // true
|
||||||
|
auto b = program.get<bool>("-b"); // true
|
||||||
|
auto c = program.get<std::vector<float>>("-c"); // {3.14f, 2.718f}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse out-of-order compound arguments. Second variation", "[compound_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
program.add_argument("-a")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-b")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-c")
|
||||||
|
.nargs(2)
|
||||||
|
.default_value(std::vector<float>{0.0f, 0.0f})
|
||||||
|
.action([](const std::string& value) { return std::stof(value); });
|
||||||
|
|
||||||
|
program.parse_args({"./main", "-cb"});
|
||||||
|
|
||||||
|
auto a = program.get<bool>("-a");
|
||||||
|
auto b = program.get<bool>("-b");
|
||||||
|
auto c = program.get<std::vector<float>>("-c");
|
||||||
|
|
||||||
|
REQUIRE(a == false);
|
||||||
|
REQUIRE(b == true);
|
||||||
|
REQUIRE(program["-c"] == std::vector<float>{0.0f, 0.0f});
|
||||||
|
}
|
76
test/test_container_arguments.hpp
Normal file
76
test/test_container_arguments.hpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Parse vector of arguments", "[vector]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("input")
|
||||||
|
.nargs(2);
|
||||||
|
|
||||||
|
program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv" });
|
||||||
|
|
||||||
|
auto inputs = program.get<std::vector<std::string>>("input");
|
||||||
|
REQUIRE(inputs.size() == 2);
|
||||||
|
REQUIRE(inputs[0] == "rocket.mesh");
|
||||||
|
REQUIRE(inputs[1] == "thrust_profile.csv");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse list of arguments", "[vector]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("input")
|
||||||
|
.nargs(2);
|
||||||
|
|
||||||
|
program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv" });
|
||||||
|
|
||||||
|
auto inputs = program.get<std::list<std::string>>("input");
|
||||||
|
REQUIRE(inputs.size() == 2);
|
||||||
|
REQUIRE(argparse::get_from_list(inputs, 0) == "rocket.mesh");
|
||||||
|
REQUIRE(argparse::get_from_list(inputs, 1) == "thrust_profile.csv");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse list of arguments with default values", "[vector]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--input")
|
||||||
|
.default_value(std::list<int>{1, 2, 3, 4, 5})
|
||||||
|
.nargs(5);
|
||||||
|
|
||||||
|
program.parse_args({ "test" });
|
||||||
|
|
||||||
|
auto inputs = program.get<std::list<int>>("--input");
|
||||||
|
REQUIRE(inputs.size() == 5);
|
||||||
|
REQUIRE(argparse::get_from_list(inputs, 0) == 1);
|
||||||
|
REQUIRE(argparse::get_from_list(inputs, 1) == 2);
|
||||||
|
REQUIRE(argparse::get_from_list(inputs, 2) == 3);
|
||||||
|
REQUIRE(argparse::get_from_list(inputs, 3) == 4);
|
||||||
|
REQUIRE(argparse::get_from_list(inputs, 4) == 5);
|
||||||
|
REQUIRE(program["--input"] == std::list<int>{1, 2, 3, 4, 5});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse list of arguments and save in an object", "[vector]") {
|
||||||
|
|
||||||
|
struct ConfigManager {
|
||||||
|
std::vector<std::string> files;
|
||||||
|
void add_file(const std::string& file) {
|
||||||
|
files.push_back(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigManager config_manager;
|
||||||
|
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--input_files")
|
||||||
|
.nargs(2)
|
||||||
|
.action([&](const std::string& value) { config_manager.add_file(value); return value; });
|
||||||
|
|
||||||
|
program.parse_args({ "test", "--input_files", "config.xml", "system.json" });
|
||||||
|
|
||||||
|
auto file_args = program.get<std::vector<std::string>>("--input_files");
|
||||||
|
REQUIRE(file_args.size() == 2);
|
||||||
|
REQUIRE(file_args[0] == "config.xml");
|
||||||
|
REQUIRE(file_args[1] == "system.json");
|
||||||
|
|
||||||
|
REQUIRE(config_manager.files.size() == 2);
|
||||||
|
REQUIRE(config_manager.files[0] == "config.xml");
|
||||||
|
REQUIRE(config_manager.files[1] == "system.json");
|
||||||
|
REQUIRE(program["--input_files"] == std::vector<std::string>{"config.xml", "system.json"});
|
||||||
|
}
|
46
test/test_optional_arguments.hpp
Normal file
46
test/test_optional_arguments.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Parse toggle arguments with default value", "[optional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--verbose", "-v")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.parse_args({ "./test.exe" });
|
||||||
|
REQUIRE(program.get<bool>("--verbose") == false);
|
||||||
|
REQUIRE(program["--verbose"] == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse toggle arguments with implicit value", "[optional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--verbose")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.parse_args({ "./test.exe", "--verbose" });
|
||||||
|
REQUIRE(program.get<bool>("--verbose") == true);
|
||||||
|
REQUIRE(program["--verbose"] == true);
|
||||||
|
REQUIRE(program["--verbose"] != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse multiple toggle arguments with implicit values", "[optional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-a")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-u")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("-x")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.parse_args({ "./test.exe", "-a", "-x" });
|
||||||
|
REQUIRE(program.get<bool>("-a") == true);
|
||||||
|
REQUIRE(program.get<bool>("-u") == false);
|
||||||
|
REQUIRE(program.get<bool>("-x") == true);
|
||||||
|
}
|
34
test/test_parent_parsers.hpp
Normal file
34
test/test_parent_parsers.hpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Add parent parsers", "[parent_parsers]") {
|
||||||
|
argparse::ArgumentParser parent_parser("main");
|
||||||
|
parent_parser.add_argument("--verbose")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
argparse::ArgumentParser child_parser("foo");
|
||||||
|
child_parser.add_parents(parent_parser);
|
||||||
|
child_parser.parse_args({ "./main", "--verbose"});
|
||||||
|
REQUIRE(child_parser["--verbose"] == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Add parent to multiple parent parsers", "[parent_parsers]") {
|
||||||
|
argparse::ArgumentParser parent_parser("main");
|
||||||
|
parent_parser.add_argument("--parent")
|
||||||
|
.default_value(0)
|
||||||
|
.action([](const std::string& value) { return std::stoi(value); });
|
||||||
|
|
||||||
|
argparse::ArgumentParser foo_parser("foo");
|
||||||
|
foo_parser.add_argument("foo");
|
||||||
|
foo_parser.add_parents(parent_parser);
|
||||||
|
foo_parser.parse_args({ "./main", "--parent", "2", "XXX" });
|
||||||
|
REQUIRE(foo_parser["--parent"] == 2);
|
||||||
|
REQUIRE(foo_parser["foo"] == std::string("XXX"));
|
||||||
|
|
||||||
|
argparse::ArgumentParser bar_parser("bar");
|
||||||
|
bar_parser.add_argument("--bar");
|
||||||
|
bar_parser.parse_args({ "./main", "--bar", "YYY" });
|
||||||
|
REQUIRE(bar_parser["--bar"] == std::string("YYY"));
|
||||||
|
}
|
166
test/test_parse_args.hpp
Normal file
166
test/test_parse_args.hpp
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Parse a string argument with value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--config");
|
||||||
|
program.parse_args({ "test", "--config", "config.yml"});
|
||||||
|
REQUIRE(program.get("--config") == "config.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a string argument with default value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--config")
|
||||||
|
.default_value(std::string("foo.yml"));
|
||||||
|
program.parse_args({ "test", "--config" });
|
||||||
|
REQUIRE(program.get("--config") == "foo.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse an int argument with value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--count")
|
||||||
|
.action([](const std::string& value) { return std::stoi(value); });
|
||||||
|
program.parse_args({ "test", "--count", "5" });
|
||||||
|
REQUIRE(program.get<int>("--count") == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse an int argument with default value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--count")
|
||||||
|
.default_value(2)
|
||||||
|
.action([](const std::string& value) { return std::stoi(value); });
|
||||||
|
program.parse_args({ "test", "--count" });
|
||||||
|
REQUIRE(program.get<int>("--count") == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a float argument with value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--ratio")
|
||||||
|
.action([](const std::string& value) { return std::stof(value); });
|
||||||
|
program.parse_args({ "test", "--ratio", "5.6645" });
|
||||||
|
REQUIRE(program.get<float>("--ratio") == 5.6645f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a float argument with default value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--ratio")
|
||||||
|
.default_value(3.14f)
|
||||||
|
.action([](const std::string& value) { return std::stof(value); });
|
||||||
|
program.parse_args({ "test", "--ratio" });
|
||||||
|
REQUIRE(program.get<float>("--ratio") == 3.14f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a double argument with value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--ratio")
|
||||||
|
.action([](const std::string& value) { return std::stod(value); });
|
||||||
|
program.parse_args({ "test", "--ratio", "5.6645" });
|
||||||
|
REQUIRE(program.get<double>("--ratio") == 5.6645);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a double argument with default value", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--ratio")
|
||||||
|
.default_value(3.14)
|
||||||
|
.action([](const std::string& value) { return std::stod(value); });
|
||||||
|
program.parse_args({ "test", "--ratio" });
|
||||||
|
REQUIRE(program.get<double>("--ratio") == 3.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a vector of integer arguments", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--vector")
|
||||||
|
.nargs(5)
|
||||||
|
.action([](const std::string& value) { return std::stoi(value); });
|
||||||
|
program.parse_args({ "test", "--vector", "1", "2", "3", "4", "5" });
|
||||||
|
auto vector = program.get<std::vector<int>>("--vector");
|
||||||
|
REQUIRE(vector.size() == 5);
|
||||||
|
REQUIRE(vector[0] == 1);
|
||||||
|
REQUIRE(vector[1] == 2);
|
||||||
|
REQUIRE(vector[2] == 3);
|
||||||
|
REQUIRE(vector[3] == 4);
|
||||||
|
REQUIRE(vector[4] == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a vector of float arguments", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--vector")
|
||||||
|
.nargs(5)
|
||||||
|
.action([](const std::string& value) { return std::stof(value); });
|
||||||
|
program.parse_args({ "test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5" });
|
||||||
|
auto vector = program.get<std::vector<float>>("--vector");
|
||||||
|
REQUIRE(vector.size() == 5);
|
||||||
|
REQUIRE(vector[0] == 1.1f);
|
||||||
|
REQUIRE(vector[1] == 2.2f);
|
||||||
|
REQUIRE(vector[2] == 3.3f);
|
||||||
|
REQUIRE(vector[3] == 4.4f);
|
||||||
|
REQUIRE(vector[4] == 5.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a vector of double arguments", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--vector")
|
||||||
|
.nargs(5)
|
||||||
|
.action([](const std::string& value) { return std::stod(value); });
|
||||||
|
program.parse_args({ "test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5" });
|
||||||
|
auto vector = program.get<std::vector<double>>("--vector");
|
||||||
|
REQUIRE(vector.size() == 5);
|
||||||
|
REQUIRE(vector[0] == 1.1);
|
||||||
|
REQUIRE(vector[1] == 2.2);
|
||||||
|
REQUIRE(vector[2] == 3.3);
|
||||||
|
REQUIRE(vector[3] == 4.4);
|
||||||
|
REQUIRE(vector[4] == 5.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a vector of string arguments", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--vector")
|
||||||
|
.nargs(5)
|
||||||
|
.action([](const std::string& value) { return value; });
|
||||||
|
program.parse_args({ "test", "--vector", "abc", "def", "ghi", "jkl", "mno" });
|
||||||
|
auto vector = program.get<std::vector<std::string>>("--vector");
|
||||||
|
REQUIRE(vector.size() == 5);
|
||||||
|
REQUIRE(vector[0] == "abc");
|
||||||
|
REQUIRE(vector[1] == "def");
|
||||||
|
REQUIRE(vector[2] == "ghi");
|
||||||
|
REQUIRE(vector[3] == "jkl");
|
||||||
|
REQUIRE(vector[4] == "mno");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a vector of character arguments", "[parse_args]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--vector")
|
||||||
|
.nargs(5)
|
||||||
|
.action([](const std::string& value) { return value[0]; });
|
||||||
|
program.parse_args({ "test", "--vector", "a", "b", "c", "d", "e" });
|
||||||
|
auto vector = program.get<std::vector<char>>("--vector");
|
||||||
|
REQUIRE(vector.size() == 5);
|
||||||
|
REQUIRE(vector[0] == 'a');
|
||||||
|
REQUIRE(vector[1] == 'b');
|
||||||
|
REQUIRE(vector[2] == 'c');
|
||||||
|
REQUIRE(vector[3] == 'd');
|
||||||
|
REQUIRE(vector[4] == 'e');
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse a vector of string arguments and construct objects", "[parse_args]") {
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
public:
|
||||||
|
Foo(const std::string& value) : value(value) {}
|
||||||
|
std::string value;
|
||||||
|
};
|
||||||
|
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("--vector")
|
||||||
|
.nargs(5)
|
||||||
|
.action([](const std::string& value) { return Foo(value); });
|
||||||
|
program.parse_args({ "test", "--vector", "abc", "def", "ghi", "jkl", "mno" });
|
||||||
|
auto vector = program.get<std::vector<Foo>>("--vector");
|
||||||
|
REQUIRE(vector.size() == 5);
|
||||||
|
REQUIRE(vector[0].value == Foo("abc").value);
|
||||||
|
REQUIRE(vector[1].value == Foo("def").value);
|
||||||
|
REQUIRE(vector[2].value == Foo("ghi").value);
|
||||||
|
REQUIRE(vector[3].value == Foo("jkl").value);
|
||||||
|
REQUIRE(vector[4].value == Foo("mno").value);
|
||||||
|
}
|
69
test/test_positional_arguments.hpp
Normal file
69
test/test_positional_arguments.hpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <argparse.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Parse positional arguments", "[positional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("input");
|
||||||
|
program.add_argument("output");
|
||||||
|
program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv" });
|
||||||
|
REQUIRE(program.get("input") == "rocket.mesh");
|
||||||
|
REQUIRE(program.get("output") == "thrust_profile.csv");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse positional arguments with fixed nargs", "[positional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("input");
|
||||||
|
program.add_argument("output").nargs(2);
|
||||||
|
program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "output.mesh" });
|
||||||
|
REQUIRE(program.get("input") == "rocket.mesh");
|
||||||
|
auto outputs = program.get<std::vector<std::string>>("output");
|
||||||
|
REQUIRE(outputs.size() == 2);
|
||||||
|
REQUIRE(outputs[0] == "thrust_profile.csv");
|
||||||
|
REQUIRE(outputs[1] == "output.mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse positional arguments with optional arguments", "[positional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("input");
|
||||||
|
program.add_argument("output").nargs(2);
|
||||||
|
program.add_argument("--num_iterations")
|
||||||
|
.action([](const std::string& value) { return std::stoi(value); });
|
||||||
|
program.parse_args({ "test", "rocket.mesh", "--num_iterations", "15", "thrust_profile.csv", "output.mesh" });
|
||||||
|
REQUIRE(program.get<int>("--num_iterations") == 15);
|
||||||
|
REQUIRE(program.get("input") == "rocket.mesh");
|
||||||
|
auto outputs = program.get<std::vector<std::string>>("output");
|
||||||
|
REQUIRE(outputs.size() == 2);
|
||||||
|
REQUIRE(outputs[0] == "thrust_profile.csv");
|
||||||
|
REQUIRE(outputs[1] == "output.mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse positional arguments with optional arguments in the middle", "[positional_arguments]") {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("input");
|
||||||
|
program.add_argument("output").nargs(2);
|
||||||
|
program.add_argument("--num_iterations")
|
||||||
|
.action([](const std::string& value) { return std::stoi(value); });
|
||||||
|
program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "--num_iterations", "15", "output.mesh" });
|
||||||
|
REQUIRE(program.get<int>("--num_iterations") == 15);
|
||||||
|
REQUIRE(program.get("input") == "rocket.mesh");
|
||||||
|
auto outputs = program.get<std::vector<std::string>>("output");
|
||||||
|
REQUIRE(outputs.size() == 2);
|
||||||
|
REQUIRE(outputs[0] == "thrust_profile.csv");
|
||||||
|
REQUIRE(outputs[1] == "output.mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Square a number", "[positional_arguments]") {
|
||||||
|
argparse::ArgumentParser program;
|
||||||
|
program.add_argument("--verbose", "-v")
|
||||||
|
.help("enable verbose logging")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
|
||||||
|
program.add_argument("square")
|
||||||
|
.help("display a square of a given number")
|
||||||
|
.action([](const std::string& value) { return pow(std::stoi(value), 2); });
|
||||||
|
|
||||||
|
program.parse_args({"./main", "15"});
|
||||||
|
REQUIRE(program.get<double>("square") == 225);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user