something

This commit is contained in:
Gwendolyn 2023-05-16 14:48:01 +02:00
commit 3bf3cfbc9c
30 changed files with 4971 additions and 0 deletions

67
.clang-format Normal file
View File

@ -0,0 +1,67 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never
SeparateDefinitionBlocks: Always

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/cmake-*
/.idea

48
CMakeLists.txt Normal file
View File

@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.25)
project(argparser)
set(CMAKE_CXX_STANDARD 23)
find_program(BASH bash)
add_custom_target(combined-header
COMMAND bash build.sh
BYPRODUCTS "${CMAKE_SOURCE_DIR}/dist/include/argparser/argparser.h"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
SOURCES src/_include_order.h src/argument.h src/basic-type.h src/builtin_parser.h src/defs.h
src/distinct-types.h src/errors.h src/list-type.h src/option.h src/optional-arg.h
src/parse-result.h src/parser.h src/parser_func.h src/repeat-arg.h src/repeat-flag.h
src/repeat-opt.h src/single-arg.h src/single-flag.h src/single-opt.h src/tuple-iteration.h
src/tuple-type.h src/type.h src/union-type.h
)
add_library(argparser INTERFACE)
target_include_directories(argparser INTERFACE dist/include)
add_dependencies(argparser combined-header)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# TODO: everything below this should only be for running cmake on this project, and not when it's included from somewhere else
include(FetchContent)
FETCHCONTENT_DECLARE(
gtest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG bc860af08783b8113005ca7697da5f5d49a8056f
)
enable_testing()
FETCHCONTENT_MAKEAVAILABLE(gtest)
include(GoogleTest)
add_executable(ArgparserTest
tests/parser.cpp
tests/types.cpp
)
target_link_libraries(ArgparserTest GTest::gtest_main)
target_link_libraries(ArgparserTest argparser)
gtest_discover_tests(ArgparserTest)
target_compile_options(ArgparserTest PRIVATE -Wall -Wextra -Wpedantic -Werror)

24
build.sh Normal file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
rm -rf header-build
mkdir -p header-build/headers
for header in src/*.h; do
grep -E '^#include <' "$header" >> "header-build/system-includes-1.h"
sed -e '/^#include </d' "$header" > "header-build/headers/$(basename "$header")"
done
sed -i '1s/^/\/*START*\/\n/' header-build/headers/_include_order.h
gcc -E -C -P "header-build/headers/_include_order.h" > header-build/combined-1.h
sed '0,/^\/\*START\*\/$/d' header-build/combined-1.h > header-build/combined-2.h
sort header-build/system-includes-1.h | uniq > header-build/system-includes-2.h
sed -r -e '/^namespace argparser \{/d' -e '/}\/\/ namespace argparser$/d' header-build/combined-2.h > header-build/combined-3.h
sed -r -e 's/^namespace argparser::(.*) \{/namespace \1 {/' -e 's/}\/\/ namespace argparser::(.*)$/}\/\/ namespace \1/' header-build/combined-3.h > header-build/combined-4.h
cat header-build/system-includes-2.h > header-build/combined.h
echo "namespace argparser {" >> header-build/combined.h
cat header-build/combined-4.h >> header-build/combined.h
echo "}// namespace argparser" >> header-build/combined.h
clang-tidy --fix-errors --quiet header-build/combined.h >/dev/null 2>&1
clang-format -i -style=file:.clang-format header-build/combined.h
mkdir -p dist/include/argparser
cp header-build/combined.h dist/include/argparser/argparser.h
rm -rf header-build

1584
dist/include/argparser/argparser.h vendored Normal file

File diff suppressed because it is too large Load Diff

1
src/_include_order.h Normal file
View File

@ -0,0 +1 @@
#include "parser.h"

59
src/argument.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef ARGPARSER_ARGUMENT_H
#define ARGPARSER_ARGUMENT_H
#include "errors.h"
#include "parse-result.h"
#include "type.h"
#include <any>
#include <memory>
#include <span>
#include <string>
namespace argparser {
class arg {
public:
explicit arg(std::string name) : name(std::move(name)) {}
virtual ~arg() = default;
void parse(const std::string &input, parse_result &pr) const {
do_parse(input, pr);
}
[[nodiscard]] bool has_parsed_enough(parse_result &pr) const {
return this->get_has_parsed_enough(pr);
}
[[nodiscard]] bool can_parse_more(parse_result &pr) const {
return this->get_can_parse_more(pr);
}
[[nodiscard]] std::string get_name() const {
return name;
}
protected:
const std::string name;
template<typename T>
static T parse_single_value(std::string input, const type_handle_impl<T> &type) {
const char *parse_end;
auto begin = &*input.begin();
auto end = &*input.end();
auto val = type->parse(begin, end, parse_end, internal::parser_allow_undelimited::Any);
if (parse_end != end) {
throw errors::type_parsing_error(type->get_name(), std::string(begin, end), parse_end - begin, "unexpected input");
}
return val;
}
private:
virtual void do_parse(std::string input, parse_result &pr) const = 0;
[[nodiscard]] virtual bool get_has_parsed_enough(parse_result &pr) const = 0;
[[nodiscard]] virtual bool get_can_parse_more(parse_result &pr) const = 0;
};
using arg_handle = std::shared_ptr<arg>;
}// namespace argparser
#endif//ARGPARSER_ARGUMENT_H

33
src/basic-type.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef ARGPARSER_BASIC_TYPE_H
#define ARGPARSER_BASIC_TYPE_H
#include "defs.h"
#include "parser_func.h"
#include "type.h"
#include <any>
#include <functional>
#include <memory>
#include <string>
namespace argparser {
template<typename T>
class basic_type : public type_impl<T> {
public:
basic_type(std::string name, internal::parser_func<T> parser) : type_impl<T>(std::move(name)), parser(std::move(parser)) {}
T parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
const char *n_parse_end;
auto val = parser(begin, end, n_parse_end, allow_undelimited);
parse_end = n_parse_end;
return val;
}
private:
internal::parser_func<T> parser;
};
}// namespace argparser
#endif//ARGPARSER_BASIC_TYPE_H

172
src/builtin_parser.h Normal file
View File

@ -0,0 +1,172 @@
#ifndef ARGPARSER_BUILTIN_PARSER_H
#define ARGPARSER_BUILTIN_PARSER_H
#include "defs.h"
#include "errors.h"
#include "parser_func.h"
#include <charconv>
#include <iomanip>
#include <string>
namespace argparser::internal {
template<typename T, parse_opt parse_opt>
class automatic_parser {
public:
static parser_func<T> make_parser(const std::string &name) {
return [name](const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited) {
T val;
auto pos = begin;
while (pos < end && isspace(*pos)) pos++;
auto res = std::from_chars(pos, end, val);
if (res.ec == std::errc{}) {
parse_end = res.ptr;
return val;
} else {
throw errors::type_parsing_error(name, std::string(begin, end), pos - begin, "invalid number");
}
};
}
};
constexpr parse_opt string_parse_opt_with_default(parse_opt opt) {
if ((opt & parse_opt::AnyString) == parse_opt::None) {
return opt | parse_opt::AnyString;
} else {
return opt;
}
}
constexpr bool string_parser_enable_bare(parse_opt opt) {
return enum_flag_contains(string_parse_opt_with_default(opt), parse_opt::BareString);
}
constexpr bool string_parser_enable_single_quoted(parse_opt opt) {
return enum_flag_contains(string_parse_opt_with_default(opt), parse_opt::SingleQuotedString);
}
constexpr bool string_parser_enable_double_quoted(parse_opt opt) {
return enum_flag_contains(string_parse_opt_with_default(opt), parse_opt::DoubleQuotedString);
}
template<parse_opt parse_opt>
class automatic_parser<std::string, parse_opt> {
static std::string trim_string(const std::string &str) {
auto start = str.find_first_not_of(whitespace);
auto end = str.find_last_not_of(whitespace);
return str.substr(start == std::string::npos ? 0 : start, end == std::string::npos ? 0 : end);
}
public:
static parser_func<std::string> make_parser(const std::string &name) {
return [name](const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) {
auto str = std::string_view(begin, end);
if (str.length() == 0) {
if (parse_opt == parse_opt::None || enum_flag_contains(parse_opt, parse_opt::BareString)) {
parse_end = begin;
return std::string("");
} else {
throw errors::type_parsing_error(name, std::string(""), 0, "unexpected empty input");
}
}
if (str[0] == '\'' && string_parser_enable_single_quoted(parse_opt)) {
auto ss = std::stringstream(std::string(str));
std::string val;
ss >> std::quoted(val, '\'', '\\');
auto len = ss.tellg();
parse_end = begin + len;
if (enum_flag_contains(parse_opt, parse_opt::TrimString)) {
return trim_string(val);
} else {
return val;
}
}
if (str[0] == '"' && string_parser_enable_double_quoted(parse_opt)) {
auto ss = std::stringstream(std::string(str));
std::string val;
ss >> std::quoted(val, '"', '\\');
auto len = ss.tellg();
parse_end = begin + len;
if (enum_flag_contains(parse_opt, parse_opt::TrimString)) {
return trim_string(val);
} else {
return val;
}
}
if (string_parser_enable_bare(parse_opt)) {
std::string illegal_characters = "\"'";
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Comma)) {
illegal_characters += ",";
}
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Parenthesis)) {
illegal_characters += "()";
}
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Brackets)) {
illegal_characters += "[]";
}
auto pos = str.find_first_of(illegal_characters);
if (pos == std::string_view::npos) {
parse_end = end;
} else {
parse_end = begin + pos;
}
std::string val{begin, parse_end};
if (enum_flag_contains(parse_opt, parse_opt::TrimString) || enum_flag_contains(parse_opt, parse_opt::TrimBareString)) {
return trim_string(val);
} else {
return val;
}
}
throw errors::type_parsing_error(name, std::string(begin, end), 0, "failed to parse string");
};
}
};
template<typename T>
parser_func<T> make_enum_parser(const std::string &name, internal::string_map<T> values) {
if (values.size() == 0) {
throw errors::empty_enum_map_error(name);
}
return [name, values](const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited) {
auto input = std::basic_string_view(begin, end);
auto start = input.find_first_not_of(whitespace);
if (start == std::string::npos) {
input = "";
} else {
input = std::basic_string_view(begin + start, end);
}
T value;
size_t value_len = 0;
for (const auto &[str, val]: values) {
if (str.length() <= input.length()) {
if (input.starts_with(str)) {
if (str.length() > value_len) {
value = val;
value_len = str.length();
}
}
}
}
if (value_len == 0) {
std::string expected_values = values.begin()->first;
for (auto it = std::next(values.begin()); it != values.end(); it++) {
if (std::next(it) != values.end()) {
expected_values += ", " + it->first;
} else {
expected_values += " or " + it->first;
}
}
throw errors::type_parsing_error(name, std::string(begin, end), 0, std::format("expected {}", expected_values));
} else {
parse_end = begin + value_len;
return value;
}
};
}
}// namespace argparser::internal
#endif//ARGPARSER_BUILTIN_PARSER_H

53
src/defs.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef ARGPARSER_DEFS_H
#define ARGPARSER_DEFS_H
#include <string_view>
#include <type_traits>
#include <map>
#include <functional>
namespace argparser::internal {
constexpr std::string_view whitespace = " \f\n\r\t\v";
enum class parser_allow_undelimited {
None = 0,
Comma = 1,
Parenthesis = 2,
Brackets = 4,
Any = 7,
};
constexpr parser_allow_undelimited operator|(parser_allow_undelimited a, parser_allow_undelimited b) {
return static_cast<parser_allow_undelimited>(static_cast<std::underlying_type_t<parser_allow_undelimited>>(a) | static_cast<std::underlying_type_t<parser_allow_undelimited>>(b));
}
constexpr parser_allow_undelimited operator&(parser_allow_undelimited a, parser_allow_undelimited b) {
return static_cast<parser_allow_undelimited>(static_cast<std::underlying_type_t<parser_allow_undelimited>>(a) & static_cast<std::underlying_type_t<parser_allow_undelimited>>(b));
}
enum class parse_opt {
None = 0,
BareString = 1,
SingleQuotedString = 2,
DoubleQuotedString = 4,
AnyString = 7,
TrimString = 8,
TrimBareString = 16,
};
constexpr parse_opt operator|(parse_opt a, parse_opt b) {
return static_cast<parse_opt>(static_cast<std::underlying_type_t<parse_opt>>(a) | static_cast<std::underlying_type_t<parse_opt>>(b));
}
constexpr parse_opt operator&(parse_opt a, parse_opt b) {
return static_cast<parse_opt>(static_cast<std::underlying_type_t<parse_opt>>(a) & static_cast<std::underlying_type_t<parse_opt>>(b));
}
template<typename T>
constexpr bool enum_flag_contains(T a, T b) {
return static_cast<std::underlying_type_t<T>>(a & b) != 0;
}
template <typename T>
using string_map = std::map<std::string, T, std::less<>>;
}// namespace argparser::internal
#endif//ARGPARSER_DEFS_H

51
src/distinct-types.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef ARGPARSER_DISTINCT_TYPES_H
#define ARGPARSER_DISTINCT_TYPES_H
#include <type_traits>
#include <variant>
namespace argparser::internal::distinct_types_impl {
template<typename TList, typename... Ts>
struct UniqueTypes;
template<template<typename...> typename X, typename... Ts>
struct UniqueTypes<X<Ts...>> {
using type = X<Ts...>;
};
template<template<typename...> typename X, typename... Ts, typename T, typename... Us>
struct UniqueTypes<X<Ts...>, T, Us...> {
using type = typename UniqueTypes<
typename std::conditional<
std::disjunction<std::is_same<T, Ts>...>::value,
X<Ts...>,
X<Ts..., T>>::type,
Us...>::type;
};
template<template<typename...> typename X, typename... Ts>
struct Distinct {
using type = typename UniqueTypes<X<>, Ts...>::type;
};
}// namespace argparser::internal::distinct_types_impl
namespace argparser::internal {
template<typename... Ts>
using distinct_types_variant = typename internal::distinct_types_impl::Distinct<std::variant, Ts...>::type;
template<typename... Ts>
using distinct_types_tuple = typename internal::distinct_types_impl::Distinct<std::tuple, Ts...>::type;
template<typename T, typename... Ts>
using if_single_type = std::enable_if_t<std::tuple_size<distinct_types_tuple<Ts...>>::value == 1, T>;
template<typename T, typename... Ts>
using if_not_single_type = std::enable_if_t<(std::tuple_size<distinct_types_tuple<Ts...>>::value > 1), T>;
template<typename T, typename F, typename... Ts>
using single_type_conditional = std::conditional<(std::tuple_size<distinct_types_tuple<Ts...>>::value == 1), T, F>;
template<typename... Ts>
using single_type = if_single_type<typename std::tuple_element<0, std::tuple<Ts...>>::type, Ts...>;
}// namespace argparser::internal
#endif// ARGPARSER_DISTINCT_TYPES_H

116
src/errors.h Normal file
View File

@ -0,0 +1,116 @@
#ifndef ARGPARSER_ERRORS_H
#define ARGPARSER_ERRORS_H
#include <format>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
namespace argparser::errors {
class runtime_error : public std::runtime_error {
public:
template<typename... Ts>
explicit runtime_error(Ts... args) : std::runtime_error(args...) {}
};
class logic_error : public std::logic_error {
public:
template<typename... Ts>
explicit logic_error(Ts... args) : std::logic_error(args...) {}
};
class missing_option_error : public runtime_error {
public:
explicit missing_option_error(std::string option_name) : runtime_error(std::format("missing required option {}", option_name)) {}
};
class missing_argument_error : public runtime_error {
public:
explicit missing_argument_error(std::string arg_name) : runtime_error(std::format("missing argument {}", arg_name)) {}
};
class unknown_option_error : public runtime_error {
public:
explicit unknown_option_error(std::string option_name) : runtime_error(std::format("unknown option {}", option_name)) {}
};
class wrong_option_count_error : public runtime_error {
public:
wrong_option_count_error(const std::string &option_name, std::optional<unsigned int> min, std::optional<unsigned int> max, unsigned int actual)
: runtime_error(make_message(option_name, min, max, actual)) {}
private:
static std::string make_message(std::string option_name, std::optional<unsigned int> min, std::optional<unsigned int> max, unsigned int actual) {
if (min != std::nullopt && max != std::nullopt) {
return std::format("option {} was provided {} times, but is required at least {} times and at most {} times", option_name, actual, min.value(), max.value());
} else if (min != std::nullopt) {
return std::format("option {} was provided {} times, but is required at least {} times", option_name, actual, min.value());
} else {
return std::format("option {} was provided {} times, but is required at most {} times", option_name, actual, max.value());
}
}
};
class missing_option_value_error : public runtime_error {
public:
explicit missing_option_value_error(std::string option_name) : runtime_error(std::format("missing value for option {}", option_name)) {}
};
class not_enough_arguments_error : public runtime_error {
public:
explicit not_enough_arguments_error(size_t min_required) : runtime_error(std::format("not enough arguments, need at least {}", min_required)) {}
};
class too_many_arguments_error : public runtime_error {
public:
explicit too_many_arguments_error() : runtime_error("too many arguments") {}
};
class invalid_option_value_error : public runtime_error {
public:
explicit invalid_option_value_error(std::string option_name) : runtime_error(std::format("invalid value for option {}", option_name)) {}
};
class unexpected_option_value_error : public runtime_error {
public:
explicit unexpected_option_value_error(std::string option_name) : runtime_error(std::format("unexpected value for option {}", option_name)) {}
};
class type_parsing_error : public runtime_error {
public:
type_parsing_error(std::string type_name, std::string input, size_t error_pos, std::string message)
: runtime_error(std::format("error parsing type {} at position {}: {}", type_name, error_pos, message)),
type_name(type_name), input(input), error_pos(error_pos) {}
const std::string type_name;
const std::string input;
const int error_pos;
};
class ambiguous_parse_error : public type_parsing_error {
public:
explicit ambiguous_parse_error(std::string type_name, std::string input, size_t error_pos, std::vector<std::string> possible_types)
: type_parsing_error(type_name, input, error_pos, make_message(possible_types)) {}
private:
static std::string make_message(std::vector<std::string> possible_types) {
std::string message = "ambiguity between ";
for (auto it = possible_types.begin(); it != possible_types.end(); it++) {
if (std::next(it) == possible_types.end()) {
message += " and " + *it;
} else {
message += ", " + *it;
}
}
return message;
}
};
class invalid_option_name_error : public logic_error {
public:
explicit invalid_option_name_error(std::string option_name) : logic_error(std::format("invalid option name {}", option_name)) {}
};
class duplicate_option_name : public logic_error {
public:
explicit duplicate_option_name(std::string option_name) : logic_error(std::format("option with name {} already exists", option_name)) {}
};
class duplicate_argument_name : public logic_error {
public:
explicit duplicate_argument_name(std::string arg_name) : logic_error(std::format("argument with name {} already exists", arg_name)) {}
};
class empty_enum_map_error : public logic_error {
public:
explicit empty_enum_map_error(std::string type_name) : logic_error(std::format("no enum values for type {}", type_name)) {}
};
}// namespace argparser::errors
#endif//ARGPARSER_ERRORS_H

84
src/list-type.h Normal file
View File

@ -0,0 +1,84 @@
#ifndef ARGPARSER_LIST_TYPE_H
#define ARGPARSER_LIST_TYPE_H
#include "errors.h"
#include "type.h"
#include <memory>
#include <string>
#include <vector>
namespace argparser {
template<typename T>
class list_type : public type_impl<std::vector<T>> {
public:
list_type(std::string name, type_handle_impl<T> element_type) : type_impl<std::vector<T>>(std::move(name)), element_type(element_type) {}
std::vector<T> parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
auto cur_pos = begin;
std::vector<T> values{};
bool is_delimited;
if (*cur_pos == '[') {
is_delimited = true;
cur_pos++;
} else {
if (!internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Comma)) {
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected '['");
} else {
is_delimited = false;
}
}
if (cur_pos >= end) {
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "unexpected end of input");
}
internal::parser_allow_undelimited sub_parse_allow_undelimited = internal::parser_allow_undelimited::Parenthesis;
if (!is_delimited) {
sub_parse_allow_undelimited & allow_undelimited;
}
while (true) {
const char *this_parse_end;
auto val = element_type->parse(cur_pos, end, this_parse_end, sub_parse_allow_undelimited);
values.push_back(val);
cur_pos = this_parse_end;
while (std::isspace(*cur_pos) && cur_pos <= end)
cur_pos++;
if (cur_pos >= end) {
if (!is_delimited) {
break;
} else {
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "unexpected end of input");
}
}
if (*cur_pos == ',') {
cur_pos++;
auto s = std::string_view(cur_pos, end);
auto close_bracket_pos = s.find_first_of(']');
if (s.find_first_not_of(internal::whitespace) == close_bracket_pos) {
break;
}
} else if (!is_delimited || *cur_pos == ']') {
break;
} else {
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "expected , or ]");
}
}
if (is_delimited) {
if (*cur_pos != ']') {
throw errors::type_parsing_error(this->name, std::string(begin, end), end - begin, "unexpected end of input");
} else {
cur_pos++;
}
}
parse_end = cur_pos;
return values;
}
private:
type_handle_impl<T> element_type;
};
}// namespace argparser
#endif//ARGPARSER_LIST_TYPE_H

39
src/option.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef ARGPARSER_OPTION_H
#define ARGPARSER_OPTION_H
#include "parse-result.h"
#include <any>
#include <memory>
#include <optional>
#include <string>
namespace argparser {
class option {
public:
explicit option(std::string name) : name(std::move(name)) {}
virtual ~option() = default;
void parse(std::optional<std::string> arg, std::any &val) {
return this->do_parse(std::move(arg), val);
}
[[nodiscard]] std::string get_name() {
return name;
}
[[nodiscard]] virtual bool consumes_value() const = 0;
virtual void validate(const parse_result &res) const = 0;
protected:
const std::string name;
private:
virtual void do_parse(std::optional<std::string> arg, std::any &val) = 0;
};
using option_handle = std::shared_ptr<option>;
}// namespace argparser
#endif//ARGPARSER_OPTION_H

66
src/optional-arg.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef ARGPARSER_OPTIONAL_ARG_H
#define ARGPARSER_OPTIONAL_ARG_H
#include "argument.h"
#include "errors.h"
#include "parse-result.h"
#include "type.h"
#include <any>
#include <cassert>
#include <memory>
#include <optional>
#include <span>
#include <string>
namespace argparser {
template<typename T>
class optional_arg : public arg, public std::enable_shared_from_this<optional_arg<T>> {
public:
optional_arg(std::string name, type_handle_impl<T> type) : arg(std::move(name)), type(type) {}
std::shared_ptr<optional_arg<T>> default_value(T val) {
default_value_ = val;
return this->shared_from_this();
}
[[nodiscard]] std::optional<T> get(const parse_result &p) const {
auto v = p.get_arg(name);
if (!v.has_value()) {
if (default_value_.has_value()) {
return default_value_.value();
}
return std::nullopt;
}
return std::make_optional(std::any_cast<T>(v));
}
[[nodiscard]] bool has(const parse_result &p) const {
return p.has_arg(name);
}
private:
type_handle_impl<T> type;
std::optional<T> default_value_ = std::nullopt;
void do_parse(std::string input, parse_result &pr) const override {
assert(!pr.get_arg(name).has_value());// an optional arg can only be parsed once
auto val = this->parse_single_value(input, type);
pr.set_arg(name, std::make_any<T>(val));
}
[[nodiscard]] bool get_can_parse_more(parse_result &pr) const override {
return !pr.get_arg(name).has_value();
}
[[nodiscard]] bool get_has_parsed_enough(parse_result &) const override {
return true;
}
};
template<typename T>
using optional_arg_handle = std::shared_ptr<optional_arg<T>>;
}// namespace argparser
#endif//ARGPARSER_OPTIONAL_ARG_H

61
src/parse-result.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef ARGPARSER_PARSE_RESULT_H
#define ARGPARSER_PARSE_RESULT_H
#include <any>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "defs.h"
namespace argparser {
class parse_result {
internal::string_map<std::any> opts{};
internal::string_map<std::any> args{};
std::vector<std::string> remaining_args{};
public:
void set_opt(const std::string &name, std::any value) {
opts[name] = std::move(value);
}
[[nodiscard]] std::any get_opt(const std::string &name) const {
if (opts.find(name) != opts.end()) {
return opts.at(name);
} else {
return {};
}
}
[[nodiscard]] bool has_opt(const std::string &name) const {
return opts.find(name) != opts.end();
}
void set_arg(const std::string &name, std::any value) {
args[name] = std::move(value);
}
[[nodiscard]] std::any get_arg(const std::string &name) const {
if (args.find(name) != args.end()) {
return args.at(name);
} else {
return {};
}
}
[[nodiscard]] bool has_arg(const std::string &name) const {
auto argit = args.find(name);
if (argit == args.end())
return false;
return argit != args.end() && argit->second.has_value();
}
void set_remaining(std::vector<std::string> remaining) {
remaining_args = std::move(remaining);
}
[[nodiscard]] std::vector<std::string> remaining() const {
return remaining_args;
}
};
}// namespace argparser
#endif//ARGPARSER_PARSE_RESULT_H

364
src/parser.h Normal file
View File

@ -0,0 +1,364 @@
#ifndef ARGPARSER_PARSER_H
#define ARGPARSER_PARSER_H
#include "argument.h"
#include "basic-type.h"
#include "builtin_parser.h"
#include "defs.h"
#include "distinct-types.h"
#include "errors.h"
#include "list-type.h"
#include "option.h"
#include "optional-arg.h"
#include "parse-result.h"
#include "repeat-arg.h"
#include "repeat-flag.h"
#include "repeat-opt.h"
#include "single-arg.h"
#include "single-flag.h"
#include "single-opt.h"
#include "tuple-type.h"
#include "type.h"
#include "union-type.h"
#include <any>
#include <deque>
#include <functional>
#include <iomanip>
#include <map>
#include <memory>
#include <span>
#include <string>
#include <tuple>
#include <vector>
// TODO: help output
namespace argparser {
class parser {
void validate_opt_or_flag_name(const std::string &name) {
if (!name.starts_with("--")) {
if (!name.starts_with("-") || name.length() != 2) {
throw errors::invalid_option_name_error(name);
}
}
if (options.find(name) != options.end()) {
throw errors::duplicate_option_name(name);
}
}
bool allow_remaining_args_{};
public:
internal::string_map<option_handle> options;
std::vector<arg_handle> arguments;
template<typename T>
[[nodiscard]] option_handle_impl<T> option(const std::string &name, const type_handle_impl<T> &type) {
validate_opt_or_flag_name(name);
auto o = std::make_shared<option_impl<T>>(name, type);
options[name] = o;
return o;
}
template<typename T>
[[nodiscard]] option_handle_impl<T> option(const std::string &name, const std::string &alt_name, const type_handle_impl<T> &type) {
validate_opt_or_flag_name(name);
validate_opt_or_flag_name(alt_name);
auto o = std::make_shared<option_impl<T>>(name, type);
options[name] = o;
options[alt_name] = o;
return o;
}
template<typename T>
[[nodiscard]] repeatable_option_handle_impl<T> repeatable_option(const std::string &name, const type_handle_impl<T> &type) {
validate_opt_or_flag_name(name);
auto o = std::make_shared<repeatable_option_impl<T>>(name, type);
options[name] = o;
return o;
}
template<typename T>
[[nodiscard]] repeatable_option_handle_impl<T> repeatable_option(const std::string &name, const std::string &alt_name, const type_handle_impl<T> &type) {
validate_opt_or_flag_name(name);
validate_opt_or_flag_name(alt_name);
auto o = std::make_shared<repeatable_option_impl<T>>(name, type);
options[name] = o;
options[alt_name] = o;
return o;
}
[[nodiscard]] flag_handle_impl flag(const std::string &name) {
validate_opt_or_flag_name(name);
auto f = std::make_shared<flag_impl>(name);
options[name] = f;
return f;
}
[[nodiscard]] flag_handle_impl flag(const std::string &name, const std::string &alt_name) {
validate_opt_or_flag_name(name);
validate_opt_or_flag_name(alt_name);
auto f = std::make_shared<flag_impl>(name);
options[name] = f;
options[alt_name] = f;
return f;
}
[[nodiscard]] repeatable_flag_handle_impl repeatable_flag(const std::string &name) {
validate_opt_or_flag_name(name);
auto f = std::make_shared<repeatable_flag_impl>(name);
options[name] = f;
return f;
}
[[nodiscard]] repeatable_flag_handle_impl repeatable_flag(const std::string &name, const std::string &alt_name) {
validate_opt_or_flag_name(name);
validate_opt_or_flag_name(alt_name);
auto f = std::make_shared<repeatable_flag_impl>(name);
options[name] = f;
options[alt_name] = f;
return f;
}
template<typename T>
[[nodiscard]] single_arg_handle<T> arg(const std::string &name, type_handle_impl<T> type) {
auto a = std::make_shared<single_arg<T>>(name, type);
arguments.push_back(a);
return a;
}
template<typename T>
[[nodiscard]] optional_arg_handle<T> optional_arg(const std::string name, type_handle_impl<T> type) {
auto a = std::make_shared<argparser::optional_arg<T>>(name, type);
arguments.push_back(a);
return a;
}
template<typename T>
[[nodiscard]] repeatable_arg_handle<T> repeatable_arg(const std::string name, type_handle_impl<T> type) {
auto a = std::make_shared<argparser::repeatable_arg<T>>(name, type);
arguments.push_back(a);
return a;
}
[[nodiscard]] type_handle_impl<std::string> enum_type(const std::string &name, const std::vector<std::string> &values) {
internal::string_map<std::string> actual_values{};
for (const auto &item: values) {
actual_values[item] = item;
}
auto parser_fn = internal::make_enum_parser(name, actual_values);
auto t = std::make_shared<argparser::basic_type<std::string>>(name, parser_fn);
known_types[name] = t;
return t;
}
template<typename T>
[[nodiscard]] type_handle_impl<T> enum_type(const std::string &name, const internal::string_map<T> &values) {
auto parser_fn = internal::make_enum_parser(name, values);
auto t = std::make_shared<argparser::basic_type<T>>(name, parser_fn);
known_types[name] = t;
return t;
}
template<typename... Ts>
[[nodiscard]] type_handle_impl<std::tuple<Ts...>>
tuple_type(const std::string &name, const type_handle_impl<Ts> &...types) {
auto t = std::make_shared<argparser::tuple_type<Ts...>>(name, std::forward_as_tuple(types...));
known_types[name] = t;
return t;
}
template<typename R, typename... Ts>
[[nodiscard]] type_handle_impl<R> tuple_type(const std::string &name, const type_handle_impl<Ts> &...types) {
auto t = std::make_shared<custom_tuple_type<R, Ts...>>(name, std::forward_as_tuple(types...));
known_types[name] = t;
return t;
}
template<typename R, typename... Ts>
[[nodiscard]] type_handle_impl<R> tuple_type(const std::string &name, typename std::type_identity<std::function<R(Ts...)>>::type constructor, const type_handle_impl<Ts> &...types) {
auto t = std::make_shared<custom_tuple_type_with_constructor<R, Ts...>>(name, std::forward_as_tuple(types...), constructor);
known_types[name] = t;
return t;
}
template<typename R, typename... Ts>
[[nodiscard]] type_handle_impl<R> tuple_type(const std::string &name, const std::tuple<type_handle_impl<Ts>...> &types, typename std::type_identity<std::function<R(Ts...)>>::type constructor) {
auto t = std::make_shared<custom_tuple_type_with_constructor<R, Ts...>>(name, std::move(types), constructor);
known_types[name] = t;
return t;
}
template<typename... Ts>
[[nodiscard]] internal::if_not_single_type<type_handle_impl<internal::distinct_types_variant<Ts...>>, Ts...>
union_type(const std::string &name, const type_handle_impl<Ts> &...types) {
auto t = std::make_shared<argparser::union_type<Ts...>>(name, std::forward_as_tuple(types...));
known_types[name] = t;
return t;
}
template<typename... Ts>
[[nodiscard]] type_handle_impl<internal::single_type<Ts...>>
union_type(const std::string &name, const type_handle_impl<Ts> &...types) {
auto t = std::make_shared<argparser::union_type<Ts...>>(name, std::forward_as_tuple(types...));
known_types[name] = t;
return t;
}
template<typename T>
[[nodiscard]] type_handle_impl<std::vector<T>> list_type(const std::string &name, const type_handle_impl<T> &type) {
auto t = std::make_shared<argparser::list_type<T>>(name, type);
known_types[name] = t;
return t;
}
internal::string_map<type_handle> known_types;
template<typename T, internal::parse_opt parse_opt = internal::parse_opt::None>
[[nodiscard]] type_handle_impl<T> basic_type(const std::string &name) {
auto parse_fn = internal::automatic_parser<T, parse_opt>::make_parser(name);
auto t = std::make_shared<argparser::basic_type<T>>(name, parse_fn);
known_types[name] = t;
return t;
}
void enable_remaining_args() {
allow_remaining_args_ = true;
}
[[nodiscard]] parse_result parse(const std::vector<std::string> &params) {
std::deque<std::string> params_queue(params.size());
std::copy(params.begin(), params.end(), params_queue.begin());
auto pr = parse_result{};
auto arg_iter = arguments.begin();
while (!params_queue.empty()) {
auto current = params_queue.front();
bool looks_like_option = false;
option_handle opt{};
std::string opt_name;
if (allow_remaining_args_ && current == "--") {
params_queue.pop_front();
pr.set_remaining(std::vector<std::string>(std::make_move_iterator(params_queue.begin()), std::make_move_iterator(params_queue.end())));
params_queue.clear();
break;
}
if (current.starts_with("--")) {
looks_like_option = true;
if (current.contains('=')) {
opt_name = current.substr(0, current.find_first_of('='));
auto optit = options.find(opt_name);
if (optit != options.end()) {
opt = optit->second;
if (!opt->consumes_value()) {
throw errors::unexpected_option_value_error(opt_name);
}
auto remain = current.substr(opt_name.length() + 1);
params_queue.pop_front();
params_queue.push_front(remain);
}
} else {
opt_name = current;
auto optit = options.find(opt_name);
if (optit != options.end()) {
opt = optit->second;
params_queue.pop_front();
}
}
} else if (current.starts_with("-") && current.length() >= 2) {
looks_like_option = true;
if (current.length() == 2) {
opt_name = current;
auto optit = options.find(opt_name);
if (optit != options.end()) {
opt = optit->second;
params_queue.pop_front();
}
} else {
opt_name = current.substr(0, 2);
auto optit = options.find(opt_name);
if (optit != options.end()) {
opt = optit->second;
auto remain = current.substr(2);
params_queue.pop_front();
if (opt->consumes_value()) {
params_queue.push_front(remain);
} else {
params_queue.push_front("-" + remain);
}
}
}
}
if (opt == nullptr) {
if (arg_iter == arguments.end()) {
if (allow_remaining_args_) {
pr.set_remaining(std::vector<std::string>(std::make_move_iterator(params_queue.begin()), std::make_move_iterator(params_queue.end())));
params_queue.clear();
} else {
if (looks_like_option) {
throw errors::unknown_option_error(opt_name);
} else {
throw errors::too_many_arguments_error();
}
}
} else {
auto arg = arg_iter->get();
try {
arg->parse(current, pr);
params_queue.pop_front();
} catch (errors::runtime_error &) {
if (!arg->has_parsed_enough(pr)) {
if (looks_like_option) {
throw errors::unknown_option_error(opt_name);
} else {
throw;
}
}
if (std::next(arg_iter) == arguments.end()) {
throw;
}
arg_iter = std::next(arg_iter);
continue;// retry parsing the value with the next arg
}
if (!arg->can_parse_more(pr)) {
arg_iter = std::next(arg_iter);
}
}
} else {
std::optional<std::string> option_val = std::nullopt;
auto consumes_value = opt->consumes_value();
if (consumes_value) {
if (params_queue.empty()) {
throw errors::missing_option_value_error(opt_name);
}
option_val = params_queue.front();
params_queue.pop_front();
}
std::any val = {};
auto canonical_name = opt->get_name();
if (pr.has_opt(canonical_name)) {
val = pr.get_opt(canonical_name);
}
try {
opt->parse(option_val, val);
} catch (errors::type_parsing_error &) {
if (option_val.has_value() && options.find(option_val.value()) != options.end()) {
throw errors::missing_option_value_error(opt_name);
} else {
throw;
}
}
pr.set_opt(canonical_name, val);
}
}
if (arg_iter != arguments.end() && !arg_iter->get()->has_parsed_enough(pr)) {
throw errors::missing_argument_error(arg_iter->get()->get_name());
}
for (const auto &[name, option]: options) {
option->validate(pr);
}
return pr;
}
};
}// namespace argparser
#endif//ARGPARSER_PARSER_H

14
src/parser_func.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef ARGPARSER_PARSE_FUNC_H
#define ARGPARSER_PARSE_FUNC_H
#include "defs.h"
#include <functional>
namespace argparser::internal {
template<typename T>
using parser_func = std::function<T(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited)>;
}
#endif//ARGPARSER_PARSE_FUNC_H

95
src/repeat-arg.h Normal file
View File

@ -0,0 +1,95 @@
#ifndef ARGPARSER_REPEAT_ARG_H
#define ARGPARSER_REPEAT_ARG_H
#include "argument.h"
#include "errors.h"
#include "parse-result.h"
#include "type.h"
#include <any>
#include <cassert>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <vector>
namespace argparser {
template<typename T>
class repeatable_arg : public arg, public std::enable_shared_from_this<repeatable_arg<T>> {
public:
repeatable_arg(std::string name, type_handle_impl<T> type) : arg(std::move(name)), type(type) {}
std::shared_ptr<repeatable_arg<T>> min(size_t min) {
min_ = min;
return this->shared_from_this();
}
std::shared_ptr<repeatable_arg<T>> max(size_t max) {
max_ = max;
return this->shared_from_this();
}
[[nodiscard]] std::vector<T> get(const parse_result &pr) const {
auto v = pr.get_arg(name);
if (!v.has_value()) {
return std::vector<T>{};
}
return std::any_cast<std::vector<T>>(v);
}
[[nodiscard]] bool has(const parse_result &pr) const {
return pr.has_arg(name);
}
private:
type_handle_impl<T> type;
std::optional<size_t> max_;
std::optional<size_t> min_;
void do_parse(std::string input, parse_result &pr) const override {
auto current_val = pr.get_arg(name);
std::vector<T> values;
if (current_val.has_value()) {
values = std::any_cast<std::vector<T>>(current_val);
}
assert(!max_.has_value() || values.size() < max_.value());
auto val = this->parse_single_value(input, type);
values.push_back(val);
pr.set_arg(name, std::make_any<std::vector<T>>(std::move(values)));
}
[[nodiscard]] bool get_can_parse_more(parse_result &pr) const override {
if (max_.has_value()) {
auto val = pr.get_arg(name);
if (!val.has_value()) {
return true;
}
return std::any_cast<std::vector<T>>(val).size() < max_.value();
} else {
return true;
}
}
[[nodiscard]] bool get_has_parsed_enough(parse_result &pr) const override {
if (min_.has_value() && min_.value() > 0) {
auto val = pr.get_arg(name);
if (!val.has_value()) {
return false;
}
auto val_ = std::any_cast<std::vector<T>>(val);
return val_.size() >= min_.value();
} else {
return true;
}
}
};
template<typename T>
using repeatable_arg_handle = std::shared_ptr<repeatable_arg<T>>;
}// namespace argparser
#endif//ARGPARSER_REPEAT_ARG_H

72
src/repeat-flag.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef ARGPARSER_REPEAT_FLAG_H
#define ARGPARSER_REPEAT_FLAG_H
#include <cassert>
#include <memory>
#include <optional>
#include <string>
#include "errors.h"
#include "option.h"
#include "parse-result.h"
#include "type.h"
namespace argparser {
class repeatable_flag_impl : public option, public std::enable_shared_from_this<repeatable_flag_impl> {
public:
repeatable_flag_impl(std::string name) : option(std::move(name)) {}
std::shared_ptr<repeatable_flag_impl> min(unsigned int min) {
min_ = min;
return this->shared_from_this();
}
std::shared_ptr<repeatable_flag_impl> max(unsigned int max) {
max_ = max;
return this->shared_from_this();
}
[[nodiscard]] bool consumes_value() const override {
return false;
}
void validate(const parse_result &pr) const override {
unsigned int count = get(pr);
if ((min_.has_value() && count < min_.value()) ||
(max_.has_value() && count > max_.value())) {
throw errors::wrong_option_count_error(this->name, min_, max_, count);
}
}
[[nodiscard]] unsigned int get(const parse_result &pr) const {
auto v = pr.get_opt(name);
if (!v.has_value()) {
return 0;
}
return std::any_cast<unsigned int>(v);
}
[[nodiscard]] bool has(const parse_result &pr) const {
return pr.has_opt(name);
}
private:
void do_parse(std::optional<std::string> arg, std::any &val) override {
assert(!arg.has_value());
unsigned int count = 0;
if (val.has_value()) {
count = std::any_cast<unsigned int>(val);
}
val = std::make_any<unsigned int>(count + 1);
}
std::optional<unsigned int> min_ = std::nullopt;
std::optional<unsigned int> max_ = std::nullopt;
};
using repeatable_flag_handle_impl = std::shared_ptr<repeatable_flag_impl>;
}// namespace argparser
#endif//ARGPARSER_REPEAT_FLAG_H

96
src/repeat-opt.h Normal file
View File

@ -0,0 +1,96 @@
#ifndef ARGPARSER_REPEAT_OPT_H
#define ARGPARSER_REPEAT_OPT_H
#include <cassert>
#include <format>
#include <memory>
#include <string>
#include <vector>
#include "errors.h"
#include "option.h"
#include "parse-result.h"
#include "type.h"
namespace argparser {
template<typename T>
class repeatable_option_impl : public option, public std::enable_shared_from_this<repeatable_option_impl<T>> {
public:
repeatable_option_impl(std::string name, type_handle_impl<T> type) : option(std::move(name)), type(std::move(type)) {}
std::shared_ptr<repeatable_option_impl<T>> default_value(std::vector<T> val) {
this->default_value_ = std::make_optional(val);
return this->shared_from_this();
}
std::shared_ptr<repeatable_option_impl<T>> min(unsigned int min) {
this->min_ = min;
return this->shared_from_this();
}
std::shared_ptr<repeatable_option_impl<T>> max(unsigned int max) {
this->max_ = max;
return this->shared_from_this();
}
void validate(const parse_result &pr) const override {
unsigned int count = 0;
if (pr.has_opt(this->name)) {
count = get(pr).size();
}
if ((min_.has_value() && count < min_.value()) ||
(max_.has_value() && count > max_.value())) {
throw errors::wrong_option_count_error(this->name, min_, max_, count);
}
}
[[nodiscard]] bool consumes_value() const override {
return true;
}
[[nodiscard]] std::vector<T> get(const parse_result &pr) const {
auto v = pr.get_opt(name);
if (!v.has_value()) {
if (default_value_.has_value()) {
return default_value_.value();
}
throw std::runtime_error(std::format("option {} was not provided", name));
}
return std::any_cast<std::vector<T>>(v);
}
[[nodiscard]] bool has(const parse_result &pr) const {
return pr.has_opt(name);
}
private:
void do_parse(std::optional<std::string> arg, std::any &val) override {
assert(arg.has_value());
const char *parse_end;
auto begin = &*arg->begin();
auto end = &*arg->end();
auto single_val = this->type->parse(begin, end, parse_end, internal::parser_allow_undelimited::Any);
if (parse_end != end) {
throw errors::type_parsing_error(this->type->get_name(), std::string(begin, end), parse_end - begin, "unexpected input");
}
std::vector<T> val_vec{};
if (val.has_value()) {
val_vec = std::any_cast<std::vector<T>>(val);
}
val_vec.push_back(single_val);
val = std::make_any<std::vector<T>>(val_vec);
}
type_handle_impl<T> type;
std::optional<std::vector<T>> default_value_ = std::nullopt;
std::optional<unsigned int> min_ = std::nullopt;
std::optional<unsigned int> max_ = std::nullopt;
};
template<typename T>
using repeatable_option_handle_impl = std::shared_ptr<repeatable_option_impl<T>>;
}// namespace argparser
#endif//ARGPARSER_REPEAT_OPT_H

54
src/single-arg.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef ARGPARSER_SINGLE_ARG_H
#define ARGPARSER_SINGLE_ARG_H
#include <memory>
#include <span>
#include <string>
#include "argument.h"
#include "errors.h"
#include "parse-result.h"
#include "type.h"
namespace argparser {
template<typename T>
class single_arg : public arg, public std::enable_shared_from_this<single_arg<T>> {
public:
single_arg(std::string name, type_handle_impl<T> type) : arg(std::move(name)), type(type) {}
[[nodiscard]] T get(const parse_result &pr) const {
auto v = pr.get_arg(name);
if (!v.has_value()) {
throw errors::missing_argument_error(name);
}
return std::any_cast<T>(v);
}
[[nodiscard]] bool has(const parse_result &pr) const {
return pr.has_arg(name);
}
private:
type_handle_impl<T> type;
void do_parse(std::string input, parse_result &pr) const override {
assert(!pr.get_arg(name).has_value());// a single arg can only be parsed once
auto val = this->parse_single_value(input, type);
pr.set_arg(name, std::make_any<T>(val));
}
bool get_can_parse_more(parse_result &pr) const override {
return !pr.get_arg(name).has_value();
}
bool get_has_parsed_enough(parse_result &pr) const override {
return pr.get_arg(name).has_value();
}
};
template<typename T>
using single_arg_handle = std::shared_ptr<single_arg<T>>;
}// namespace argparser
#endif//ARGPARSER_SINGLE_ARG_H

60
src/single-flag.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef ARGPARSER_SINGLE_FLAG_H
#define ARGPARSER_SINGLE_FLAG_H
#include <cassert>
#include <memory>
#include <optional>
#include <string>
#include "option.h"
#include "parse-result.h"
#include "type.h"
namespace argparser {
class flag_impl : public option, public std::enable_shared_from_this<flag_impl> {
public:
explicit flag_impl(std::string name) : option(std::move(name)) {}
[[nodiscard]] bool is_inverted() const {
return is_inverted_;
}
std::shared_ptr<flag_impl> invert() {
this->is_inverted_ = true;
return this->shared_from_this();
}
[[nodiscard]] bool consumes_value() const override {
return false;
}
void validate(const parse_result &) const override {
}
[[nodiscard]] bool get(const parse_result &pr) const {
bool val = pr.has_opt(name);
if (is_inverted_) {
val = !val;
}
return val;
}
[[nodiscard]] bool has(const parse_result &pr) const {
return pr.has_opt(name);
}
private:
void do_parse(std::optional<std::string> arg, std::any &val) override {
assert(!arg.has_value());
if (!val.has_value()) {
val = std::make_any<bool>(true);
}
}
bool is_inverted_{};
};
using flag_handle_impl = std::shared_ptr<flag_impl>;
}// namespace argparser
#endif//ARGPARSER_SINGLE_FLAG_H

84
src/single-opt.h Normal file
View File

@ -0,0 +1,84 @@
#ifndef ARGPARSER_SINGLE_OPT_H
#define ARGPARSER_SINGLE_OPT_H
#include <cassert>
#include <format>
#include <memory>
#include <optional>
#include <string>
#include "option.h"
#include "parse-result.h"
#include "type.h"
namespace argparser {
template<typename T>
class option_impl : public option, public std::enable_shared_from_this<option_impl<T>> {
public:
option_impl(std::string name, type_handle_impl<T> type) : option(std::move(name)), type(std::move(type)) {}
std::shared_ptr<option_impl<T>> required() {
required_ = true;
return this->shared_from_this();
}
std::shared_ptr<option_impl<T>> default_value(T val) {
this->default_value_ = val;
this->has_default_value = true;
return this->shared_from_this();
}
void validate(const parse_result &res) const override {
if (this->required_) {
if (!res.has_opt(this->name)) {
throw errors::missing_option_error(this->name);
}
}
}
[[nodiscard]] bool consumes_value() const override {
return true;
}
[[nodiscard]] T get(const parse_result &pr) const {
auto v = pr.get_opt(name);
if (!v.has_value()) {
if (has_default_value) {
return default_value_;
}
throw std::runtime_error(std::format("option {} was not provided", name));
}
return std::any_cast<T>(v);
}
[[nodiscard]] bool has(const parse_result &pr) const {
return pr.has_opt(name);
}
private:
void do_parse(std::optional<std::string> arg, std::any &val) override {
assert(arg.has_value());
const char *parse_end;
auto begin = &*arg->begin();
auto end = &*arg->end();
val = this->type->parse(begin, end, parse_end, internal::parser_allow_undelimited::Any);
if (parse_end != end) {
throw errors::type_parsing_error(this->type->get_name(), std::string(begin, end), parse_end - begin, "unexpected input");
}
}
type_handle_impl<T> type;
bool required_ = false;
bool has_default_value = false;
T default_value_;
};
template<typename T>
using option_handle_impl = std::shared_ptr<option_impl<T>>;
}// namespace argparser
#endif//ARGPARSER_SINGLE_OPT_H

30
src/tuple-iteration.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef ARGPARSER_TUPLE_ITERATION_H
#define ARGPARSER_TUPLE_ITERATION_H
#include <tuple>
// adapted from https://blog.tartanllama.xyz/exploding-tuples-fold-expressions/
namespace argparser::internal::tuple_foreach_impl {
template<std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) {
return [](auto &&f) { (f(std::integral_constant<std::size_t, Idx>{}), ...); };
}
template<std::size_t N>
auto make_index_dispatcher() {
return make_index_dispatcher(std::make_index_sequence<N>{});
}
}// namespace argparser::internal::tuple_foreach_impl
namespace argparser::internal {
template<typename Tuple, typename Func>
void tuple_foreach(Tuple &&t, Func &&f) {
constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
auto dispatcher = internal::tuple_foreach_impl::make_index_dispatcher<n>();
dispatcher([&f, &t](auto idx) { f(std::get<idx>(std::forward<Tuple>(t)), idx); });
}
}// namespace argparser::internal
#endif//ARGPARSER_TUPLE_ITERATION_H

127
src/tuple-type.h Normal file
View File

@ -0,0 +1,127 @@
#ifndef ARGPARSER_TUPLE_TYPE_H
#define ARGPARSER_TUPLE_TYPE_H
#include "errors.h"
#include "tuple-iteration.h"
#include "type.h"
#include <any>
#include <functional>
#include <string>
#include <tuple>
namespace argparser {
template<typename R, typename... Ts>
class base_tuple_type : public type_impl<R> {
public:
base_tuple_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : type_impl<R>(std::move(name)), types(types) {}
protected:
void parse_into_tuple(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited, std::tuple<Ts...> &result) {
auto cur_pos = begin;
size_t tuple_size = std::tuple_size_v<std::tuple<Ts...>>;
bool requires_delimiter = tuple_size > 1 && !internal::enum_flag_contains(allow_undelimited, internal::parser_allow_undelimited::Comma);
bool is_delimited;
if (std::string_view(begin, end).starts_with(this->name)) {
cur_pos += this->name.size();
requires_delimiter = true;
}
if (*cur_pos == '(') {
is_delimited = true;
cur_pos++;
} else if (requires_delimiter) {
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected '('");
} else {
is_delimited = false;
}
internal::parser_allow_undelimited sub_parse_allow_undelimited = internal::parser_allow_undelimited::Brackets;
if (tuple_size == 1) {
sub_parse_allow_undelimited = sub_parse_allow_undelimited | internal::parser_allow_undelimited::Comma;
}
if (!is_delimited) {
sub_parse_allow_undelimited = sub_parse_allow_undelimited & allow_undelimited;
}
internal::tuple_foreach(types, [&cur_pos, &end, &result, this, &begin, &sub_parse_allow_undelimited]<typename T>(type_handle_impl<T> &type, auto idx) {
const char *this_parse_end;
std::get<idx>(result) = type->parse(cur_pos, end, this_parse_end, sub_parse_allow_undelimited);
cur_pos = this_parse_end;
while (std::isspace(*cur_pos) && cur_pos < end)
cur_pos++;
if (idx < std::tuple_size_v<std::tuple<Ts...>> - 1) {
if (*cur_pos == ',') {
cur_pos += 1;
} else {
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected ','");
}
}
});
if (*cur_pos == ')') {
if (is_delimited) {
cur_pos++;
}
} else {
if (is_delimited) {
throw errors::type_parsing_error(this->name, std::string(begin, end), cur_pos - begin, "expected ')'");
}
}
parse_end = cur_pos;
}
private:
std::tuple<type_handle_impl<Ts>...>
types;
};
template<typename... Ts>
class tuple_type : public base_tuple_type<std::tuple<Ts...>, Ts...> {
public:
tuple_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : base_tuple_type<std::tuple<Ts...>, Ts...>(std::move(name), types) {}
std::tuple<Ts...> parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
std::tuple<Ts...> result{};
this->parse_into_tuple(begin, end, parse_end, allow_undelimited, result);
return result;
}
};
template<typename R, typename... Ts>
class custom_tuple_type : public base_tuple_type<R, Ts...> {
public:
custom_tuple_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : base_tuple_type<R, Ts...>(std::move(name), types) {}
R parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
std::tuple<Ts...> result{};
this->parse_into_tuple(begin, end, parse_end, allow_undelimited, result);
return std::apply(construct_return_type, result);
}
private:
static R construct_return_type(Ts... args) {
return R(args...);
}
};
template<typename R, typename... Ts>
class custom_tuple_type_with_constructor : public base_tuple_type<R, Ts...> {
public:
custom_tuple_type_with_constructor(std::string name, std::tuple<type_handle_impl<Ts>...> types, std::function<R(Ts...)> constructor)
: base_tuple_type<R, Ts...>(std::move(name), types), constructor(constructor) {}
R parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
std::tuple<Ts...> result{};
this->parse_into_tuple(begin, end, parse_end, allow_undelimited, result);
return std::apply(constructor, result);
}
private:
std::function<R(Ts...)> constructor;
};
}// namespace argparser
#endif//ARGPARSER_TUPLE_TYPE_H

41
src/type.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef ARGPARSER_TYPE_H
#define ARGPARSER_TYPE_H
#include "defs.h"
#include <any>
#include <memory>
#include <string>
#include <utility>
namespace argparser {
class type {
public:
virtual ~type() = default;
[[nodiscard]] std::string get_name() const {
return name;
}
protected:
explicit type(std::string name) : name(std::move(name)) {}
std::string name;
};
using type_handle = std::shared_ptr<type>;
template<typename T>
class type_impl : public type {
public:
virtual T parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited = internal::parser_allow_undelimited::None) = 0;
protected:
explicit type_impl(std::string name) : type(std::move(name)) {}
};
template<typename T>
using type_handle_impl = std::shared_ptr<type_impl<T>>;
}// namespace argparser
#endif//ARGPARSER_TYPE_H

131
src/union-type.h Normal file
View File

@ -0,0 +1,131 @@
#ifndef ARGPARSER_UNION_TYPE_H
#define ARGPARSER_UNION_TYPE_H
#include "distinct-types.h"
#include "errors.h"
#include "tuple-iteration.h"
#include "type.h"
#include <any>
#include <string>
#include <tuple>
#include <vector>
namespace argparser {
namespace internal {
template<typename... Ts>
std::string construct_string_type_name_list_from_tuple(std::tuple<Ts...> types) {
std::string expected_types;
expected_types = std::get<0>(types)->get_name();
if (std::tuple_size_v<decltype(types)> > 1) {
internal::tuple_foreach(types, [&expected_types]<typename T>(type_handle_impl<T> &type, auto idx) {
if (idx == std::tuple_size_v<decltype(types)> - 1 || idx == 0) {
return;
}
expected_types += ", " + type->get_name();
});
expected_types += " or " + std::get<std::tuple_size_v<decltype(types)> - 1>(types)->get_name();
}
return expected_types;
}
}// namespace internal
template<typename... Ts>
class union_type;
template<typename... Ts>
requires(std::tuple_size<internal::distinct_types_tuple<Ts...>>::value > 1)
class union_type<Ts...> : public type_impl<internal::distinct_types_variant<Ts...>> {
using variant_type = internal::distinct_types_variant<Ts...>;
public:
union_type(std::string name, std::tuple<type_handle_impl<Ts>...> types) : type_impl<variant_type>(std::move(name)), types(std::move(types)) {}
variant_type parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited allow_undelimited) override {
std::vector<variant_type> results{};
std::vector<std::string> result_types{};
size_t length = 0;
internal::tuple_foreach(types, [&begin, &end, &length, &results, &result_types, &allow_undelimited]<typename T>(type_handle_impl<T> &type, auto) {
const char *this_parse_end;
try {
auto val = type->parse(begin, end, this_parse_end, allow_undelimited);
size_t this_length = this_parse_end - begin;
if (this_length >= length) {
variant_type variant_wrapped_val = val;
if (this_length == length) {
results.push_back(variant_wrapped_val);
result_types.push_back(type->get_name());
} else {
length = this_length;
results = {variant_wrapped_val};
result_types = {type->get_name()};
}
}
} catch (errors::type_parsing_error &e) {
// ignore errors
}
});
if (length == 0) {
std::string expected_types = internal::construct_string_type_name_list_from_tuple(types);
throw errors::type_parsing_error(this->name, std::string(begin, end), 0, std::format("expected {}", expected_types));
}
if (results.size() > 1) {
throw errors::ambiguous_parse_error(this->name, std::string(begin, end), 0, result_types);
}
parse_end = begin + length;
return results[0];
}
private:
std::tuple<type_handle_impl<Ts>...> types;
};
template<typename T, typename... Ts>
requires(std::tuple_size<internal::distinct_types_tuple<T, Ts...>>::value == 1)
class union_type<T, Ts...> : public type_impl<T> {
public:
union_type(std::string name, std::tuple<type_handle_impl<T>, type_handle_impl<Ts>...> types) : type_impl<T>(std::move(name)), types(std::move(types)) {}
T parse(const char *begin, const char *end, const char *&parse_end, internal::parser_allow_undelimited) override {
std::vector<T> results{};
std::vector<std::string> result_types{};
size_t length = 0;
internal::tuple_foreach(types, [&begin, &end, &length, &results, &result_types](type_handle_impl<T> &type, auto) {
const char *this_parse_end;
try {
auto val = type->parse(begin, end, this_parse_end);
size_t this_length = this_parse_end - begin;
if (this_length >= length) {
if (this_length == length) {
results.push_back(val);
result_types.push_back(type->get_name());
} else {
length = this_length;
results = {val};
result_types = {type->get_name()};
}
}
} catch (errors::type_parsing_error &e) {
// ignore errors
}
});
if (length == 0) {
std::string expected_types = internal::construct_string_type_name_list_from_tuple(types);
throw errors::type_parsing_error(this->name, std::string(begin, end), 0, std::format("expected {}", expected_types));
}
if (results.size() > 1 && !std::equal(results.begin() + 1, results.end(), results.begin())) {
throw errors::ambiguous_parse_error(this->name, std::string(begin, end), 0, result_types);
}
parse_end = begin + length;
return results[0];
}
private:
std::tuple<type_handle_impl<T>, type_handle_impl<Ts>...> types;
};
}// namespace argparser
#endif//ARGPARSER_UNION_TYPE_H

565
tests/parser.cpp Normal file
View File

@ -0,0 +1,565 @@
#include "argparser/argparser.h"
#include "gtest/gtest.h"
// TODO: test whitespace in tuples and arrays
// TODO: test empty values for everything
// TODO: test option->has(result), flag->has(result) and arg->has(result)
// TODO: test enum types and commas
// TODO: fuzz testing (blocked on clang 16 being available on arch)
class Parser : public ::testing::Test {
protected:
argparser::parser p{};
argparser::type_handle_impl<uint32_t> uintType;
Parser() = default;
~Parser() override = default;
void SetUp() override {
uintType = p.basic_type<uint32_t>("uint");
}
void TearDown() override {
}
};
TEST_F(Parser, SingleOption) {
auto fooOption = p.option("--foo", uintType);
auto parsed = p.parse({"--foo", "123"});
auto foo = fooOption->get(parsed);
EXPECT_EQ(foo, 123);
}
TEST_F(Parser, OptionWithEqualsSign) {
auto fooOption = p.option("--foo", uintType);
auto parsed = p.parse({"--foo=123"});
auto foo = fooOption->get(parsed);
EXPECT_EQ(foo, 123);
}
TEST_F(Parser, MultipleOptions) {
auto fooOption = p.option("--foo", uintType);
auto barOption = p.option("--bar", uintType);
auto parsed = p.parse({"--foo", "234", "--bar", "345"});
auto foo = fooOption->get(parsed);
auto bar = barOption->get(parsed);
EXPECT_EQ(foo, 234);
EXPECT_EQ(bar, 345);
}
TEST_F(Parser, MultipleOptionsDifferentOrder) {
auto fooOption = p.option("--foo", uintType);
auto barOption = p.option("--bar", uintType);
auto parsed = p.parse({"--bar", "345", "--foo", "234"});
auto foo = fooOption->get(parsed);
auto bar = barOption->get(parsed);
EXPECT_EQ(foo, 234);
EXPECT_EQ(bar, 345);
}
TEST_F(Parser, OptionsAreNotRequiredByDefault) {
auto fooOption = p.option("--foo", uintType);
auto barOption = p.option("--bar", uintType);
auto parsed = p.parse({"--foo", "234"});
auto foo = fooOption->get(parsed);
auto has_bar = barOption->has(parsed);
EXPECT_EQ(foo, 234);
EXPECT_EQ(has_bar, false);
}
TEST_F(Parser, RequiredOption) {
auto _1 = p.option("--foo", uintType);
[[maybe_unused]] auto _2 = p.option("--bar", uintType)->required();
EXPECT_THROW(auto _3 = p.parse({"--foo", "234"});, argparser::errors::missing_option_error);
}
TEST_F(Parser, MissingValue) {
auto _1 = p.option("--foo", uintType);
EXPECT_THROW(auto _2 = p.parse({"--foo"});, argparser::errors::missing_option_value_error);
}
TEST_F(Parser, MissingValueWithNextOption) {
auto _1 = p.option("--foo", uintType);
auto _2 = p.option("--bar", uintType);
EXPECT_THROW(auto _3 = p.parse({"--foo", "--bar", "123"});, argparser::errors::missing_option_value_error);
}
TEST_F(Parser, InvalidValue) {
auto _1 = p.option("--foo", uintType);
EXPECT_THROW(auto _2 = p.parse({"--foo", "-12"});, argparser::errors::type_parsing_error);
}
TEST_F(Parser, InvalidEmptyValue) {
auto _1 = p.option("--foo", uintType);
EXPECT_THROW(auto _2 = p.parse({"--foo", ""});, argparser::errors::type_parsing_error);
}
TEST_F(Parser, OptionPassedMultipleTimesReturnsLastValue) {
auto fooOption = p.option("--foo", uintType);
auto parsed = p.parse({"--foo", "23", "--foo", "45"});
EXPECT_EQ(fooOption->get(parsed), 45);
}
TEST_F(Parser, OptionNameMustStartWithTwoDashes) {
EXPECT_THROW(auto _1 = p.option("foo", uintType);, argparser::errors::invalid_option_name_error);
}
TEST_F(Parser, DefaultValue) {
auto fooOption = p.option("--foo", uintType)->default_value(123);
auto parsed = p.parse({});
EXPECT_EQ(fooOption->get(parsed), 123);
EXPECT_EQ(fooOption->has(parsed), false);
}
TEST_F(Parser, RepeatableOption) {
auto fooOption = p.repeatable_option("--foo", uintType);
auto parsed = p.parse({"--foo", "123", "--foo", "234"});
auto foo = fooOption->get(parsed);
EXPECT_EQ(foo.size(), 2);
EXPECT_EQ(foo[0], 123);
EXPECT_EQ(foo[1], 234);
}
TEST_F(Parser, RepeatableOptionDefaultValue) {
auto fooOption = p.repeatable_option("--foo", uintType)->default_value({1, 2, 3});
auto parsed = p.parse({});
auto foo = fooOption->get(parsed);
EXPECT_EQ(foo.size(), 3);
EXPECT_EQ(foo[0], 1);
EXPECT_EQ(foo[1], 2);
EXPECT_EQ(foo[2], 3);
}
TEST_F(Parser, RepeatableOptionLimits) {
auto fooFlag = p.repeatable_option("--foo", uintType)->min(2)->max(4);
auto parsed = p.parse({"--foo", "1", "--foo", "2"});
auto val = fooFlag->get(parsed);
EXPECT_EQ(val.size(), 2);
EXPECT_EQ(val[0], 1);
EXPECT_EQ(val[1], 2);
parsed = p.parse({"--foo", "1", "--foo", "2", "--foo", "3"});
val = fooFlag->get(parsed);
EXPECT_EQ(val.size(), 3);
EXPECT_EQ(val[0], 1);
EXPECT_EQ(val[1], 2);
EXPECT_EQ(val[2], 3);
parsed = p.parse({"--foo", "1", "--foo", "2", "--foo", "3", "--foo", "4"});
val = fooFlag->get(parsed);
EXPECT_EQ(val.size(), 4);
EXPECT_EQ(val[0], 1);
EXPECT_EQ(val[1], 2);
EXPECT_EQ(val[2], 3);
EXPECT_EQ(val[3], 4);
EXPECT_THROW(auto _1 = p.parse({});, argparser::errors::wrong_option_count_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1"});, argparser::errors::wrong_option_count_error);
EXPECT_THROW(auto _3 = p.parse({"--foo", "1", "--foo", "2", "--foo", "3", "--foo", "4", "--foo", "5"});, argparser::errors::wrong_option_count_error);
EXPECT_THROW(auto _4 = p.parse({"--foo", "1", "--foo", "2", "--foo", "3", "--foo", "4", "--foo", "5", "--foo", "6"});, argparser::errors::wrong_option_count_error);
}
TEST_F(Parser, Flag) {
auto fooFlag = p.flag("--foo");
auto parsed = p.parse({"--foo"});
EXPECT_EQ(fooFlag->get(parsed), true);
parsed = p.parse({});
EXPECT_EQ(fooFlag->get(parsed), false);
}
TEST_F(Parser, FlagInverted) {
auto fooFlag = p.flag("--foo")->invert();
auto parsed = p.parse({"--foo"});
EXPECT_EQ(fooFlag->get(parsed), false);
parsed = p.parse({});
EXPECT_EQ(fooFlag->get(parsed), true);
}
TEST_F(Parser, RepeatableFLag) {
auto fooFlag = p.repeatable_flag("--foo");
auto parsed = p.parse({});
EXPECT_EQ(fooFlag->get(parsed), 0);
parsed = p.parse({"--foo"});
EXPECT_EQ(fooFlag->get(parsed), 1);
parsed = p.parse({"--foo", "--foo", "--foo", "--foo", "--foo"});
EXPECT_EQ(fooFlag->get(parsed), 5);
}
TEST_F(Parser, RepeatableFlagLimits) {
auto fooFlag = p.repeatable_flag("--foo")->min(2)->max(4);
auto parsed = p.parse({"--foo", "--foo"});
EXPECT_EQ(fooFlag->get(parsed), 2);
parsed = p.parse({"--foo", "--foo", "--foo"});
EXPECT_EQ(fooFlag->get(parsed), 3);
parsed = p.parse({"--foo", "--foo", "--foo", "--foo"});
EXPECT_EQ(fooFlag->get(parsed), 4);
EXPECT_THROW(auto _1 = p.parse({});, argparser::errors::wrong_option_count_error);
EXPECT_THROW(auto _2 = p.parse({"--foo"});, argparser::errors::wrong_option_count_error);
EXPECT_THROW(auto _3 = p.parse({"--foo", "--foo", "--foo", "--foo", "--foo"});, argparser::errors::wrong_option_count_error);
EXPECT_THROW(auto _4 = p.parse({"--foo", "--foo", "--foo", "--foo", "--foo", "--foo"});, argparser::errors::wrong_option_count_error);
}
TEST_F(Parser, OptionAndFlagNamesMustBeUnique) {
EXPECT_THROW({
auto _1 = p.option("--optopt", uintType);
auto _2 = p.option("--optopt", uintType);
},
argparser::errors::duplicate_option_name);
EXPECT_THROW({
auto _1 = p.flag("--flagflag");
auto _2 = p.flag("--flagflag");
},
argparser::errors::duplicate_option_name);
EXPECT_THROW({
auto _1 = p.option("--optflag", uintType);
auto _2 = p.flag("--optflag");
},
argparser::errors::duplicate_option_name);
EXPECT_THROW({
auto _1 = p.option("--optrepflag", uintType);
auto _2 = p.repeatable_flag("--optrepflag");
},
argparser::errors::duplicate_option_name);
EXPECT_THROW({
auto _1 = p.flag("--flagopt");
auto _2 = p.option("--flagopt", uintType);
},
argparser::errors::duplicate_option_name);
}
TEST_F(Parser, OptionWithOnlyShortName) {
auto fooOpt = p.option("-f", uintType);
argparser::parse_result pr;
uint32_t fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, 123);
}
TEST_F(Parser, RepeatableOptionWithOnlyShortName) {
auto fooOpt = p.repeatable_option("-f", uintType);
argparser::parse_result pr;
std::vector<uint32_t> fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123}));
}
TEST_F(Parser, FlagWithOnlyShortName) {
auto fooFlag = p.flag("-f");
argparser::parse_result pr;
bool fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, true);
}
TEST_F(Parser, RepeatableFlagWithOnlyShortName) {
auto fooFlag = p.flag("-f");
argparser::parse_result pr;
unsigned int fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 1);
}
TEST_F(Parser, OptionWithLongAndShortName) {
auto fooOpt = p.option("--foo", "-f", uintType);
argparser::parse_result pr;
uint32_t fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, 123);
EXPECT_NO_THROW(pr = p.parse({"--foo", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, 123);
}
TEST_F(Parser, OptionWithShortAndLongName) {
auto fooOpt = p.option("-f", "--foo", uintType);
argparser::parse_result pr;
uint32_t fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, 123);
EXPECT_NO_THROW(pr = p.parse({"--foo", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, 123);
}
TEST_F(Parser, RepeatableOptionWithLongAndShortName) {
auto fooOpt = p.repeatable_option("--foo", "-f", uintType);
argparser::parse_result pr;
std::vector<uint32_t> fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123}));
EXPECT_NO_THROW(pr = p.parse({"--foo", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123}));
EXPECT_NO_THROW(pr = p.parse({"--foo", "123", "-f", "234"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123, 234}));
EXPECT_NO_THROW(pr = p.parse({"-f", "123", "--foo", "234"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123, 234}));
}
TEST_F(Parser, RepeatableOptionWithShortAndLongName) {
auto fooOpt = p.repeatable_option("-f", "--foo", uintType);
argparser::parse_result pr;
std::vector<uint32_t> fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123}));
EXPECT_NO_THROW(pr = p.parse({"--foo", "123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123}));
EXPECT_NO_THROW(pr = p.parse({"--foo", "123", "-f", "234"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123, 234}));
EXPECT_NO_THROW(pr = p.parse({"-f", "123", "--foo", "234"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, (decltype(fooVal){123, 234}));
}
TEST_F(Parser, FlagWithLongAndSHortName) {
auto fooFlag = p.flag("--foo", "-f");
argparser::parse_result pr;
bool fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, true);
EXPECT_NO_THROW(pr = p.parse({"--foo"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, true);
}
TEST_F(Parser, FlagWithShortAndLongName) {
auto fooFlag = p.flag("-f", "--foo");
argparser::parse_result pr;
bool fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, true);
EXPECT_NO_THROW(pr = p.parse({"--foo"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, true);
}
TEST_F(Parser, RepeatableFlagWithLongAndShortName) {
auto fooFlag = p.repeatable_flag("--foo", "-f");
argparser::parse_result pr;
unsigned int fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 1);
EXPECT_NO_THROW(pr = p.parse({"--foo"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 1);
EXPECT_NO_THROW(pr = p.parse({"-f", "--foo"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 2);
EXPECT_NO_THROW(pr = p.parse({"--foo", "-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 2);
}
TEST_F(Parser, RepeatableFlagWithShortAndLongName) {
auto fooFlag = p.repeatable_flag("-f", "--foo");
argparser::parse_result pr;
unsigned int fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 1);
EXPECT_NO_THROW(pr = p.parse({"--foo"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 1);
EXPECT_NO_THROW(pr = p.parse({"-f", "--foo"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 2);
EXPECT_NO_THROW(pr = p.parse({"--foo", "-f"}));
EXPECT_NO_THROW(fooVal = fooFlag->get(pr));
EXPECT_EQ(fooVal, 2);
}
TEST_F(Parser, ShortOptionCombinedWithValue) {
auto fooOpt = p.option("-f", uintType);
argparser::parse_result pr;
uint32_t fooVal;
EXPECT_NO_THROW(pr = p.parse({"-f123"}));
EXPECT_NO_THROW(fooVal = fooOpt->get(pr));
EXPECT_EQ(fooVal, 123);
}
TEST_F(Parser, MultipleShortFlags) {
auto flagA = p.flag("-a");
auto flagB = p.flag("-b");
auto flagC = p.flag("-c");
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"-cab"}));
EXPECT_EQ(flagA->get(pr), true);
EXPECT_EQ(flagB->get(pr), true);
EXPECT_EQ(flagC->get(pr), true);
}
TEST_F(Parser, MultipleShortFlagsCombinedWithShortOptionWithValue) {
auto flagA = p.flag("-a");
auto flagB = p.flag("-b");
auto flagC = p.flag("-c");
auto fooOpt = p.option("-f", uintType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"-cabf123"}));
EXPECT_EQ(flagA->get(pr), true);
EXPECT_EQ(flagB->get(pr), true);
EXPECT_EQ(flagC->get(pr), true);
EXPECT_EQ(fooOpt->get(pr), 123);
}
TEST_F(Parser, SingleArgument) {
auto fooArg = p.arg("foo", uintType);
auto parsed = p.parse({"123"});
EXPECT_EQ(fooArg->get(parsed), 123);
}
TEST_F(Parser, MissingArgumentThrows) {
auto fooArg = p.arg("foo", uintType);
EXPECT_THROW(auto _ = p.parse({});, argparser::errors::missing_argument_error);
}
TEST_F(Parser, MultipleArguments) {
auto fooArg = p.arg("foo", uintType);
auto barArg = p.arg("bar", uintType);
auto parsed = p.parse({"123", "234"});
EXPECT_EQ(fooArg->get(parsed), 123);
EXPECT_EQ(barArg->get(parsed), 234);
}
TEST_F(Parser, MixedArgumentsAndOptionsAndFlags) {
auto fooArg = p.arg("foo", uintType);
auto barArg = p.arg("bar", uintType);
auto asdfOpt = p.option("--asdf", uintType);
auto qwertyFlag = p.flag("--qwerty");
auto parsed = p.parse({"123", "--asdf", "1", "234", "--qwerty"});
EXPECT_EQ(fooArg->get(parsed), 123);
EXPECT_EQ(barArg->get(parsed), 234);
EXPECT_EQ(asdfOpt->get(parsed), 1);
EXPECT_EQ(qwertyFlag->get(parsed), true);
parsed = p.parse({"--asdf", "123", "234", "345"});
EXPECT_EQ(fooArg->get(parsed), 234);
EXPECT_EQ(barArg->get(parsed), 345);
EXPECT_EQ(asdfOpt->get(parsed), 123);
EXPECT_EQ(qwertyFlag->get(parsed), false);
}
TEST_F(Parser, OptionalArgument) {
auto fooArg = p.optional_arg("foo", uintType);
auto parsed = p.parse({"123"});
EXPECT_EQ(fooArg->get(parsed).has_value(), true);
EXPECT_EQ(fooArg->get(parsed).value(), 123);
EXPECT_EQ(fooArg->has(parsed), true);
parsed = p.parse({});
EXPECT_EQ(fooArg->get(parsed).has_value(), false);
EXPECT_EQ(fooArg->has(parsed), false);
}
TEST_F(Parser, OptionalArgumentDefaultValue) {
auto fooArg = p.optional_arg("foo", uintType)->default_value(99);
auto parsed = p.parse({"123"});
EXPECT_EQ(fooArg->get(parsed), 123);
EXPECT_EQ(fooArg->has(parsed), true);
parsed = p.parse({});
EXPECT_EQ(fooArg->get(parsed), 99);
EXPECT_EQ(fooArg->has(parsed), false);
}
TEST_F(Parser, RepeatedArgument) {
auto fooArg = p.repeatable_arg("foo", uintType);
auto parsed = p.parse({"123", "234", "345", "456"});
auto val = fooArg->get(parsed);
EXPECT_EQ(val.size(), 4);
EXPECT_EQ(val[0], 123);
EXPECT_EQ(val[1], 234);
EXPECT_EQ(val[2], 345);
EXPECT_EQ(val[3], 456);
}
TEST_F(Parser, RepeatedArgumentLimits) {
auto fooArg = p.repeatable_arg("foo", uintType)->min(2)->max(4);
argparser::parse_result parsed;
EXPECT_NO_THROW(parsed = p.parse({"1", "2"}););
EXPECT_EQ(fooArg->get(parsed), (std::vector<uint32_t>{1, 2}));
EXPECT_NO_THROW(parsed = p.parse({"1", "2", "3"}););
EXPECT_EQ(fooArg->get(parsed), (std::vector<uint32_t>{1, 2, 3}));
EXPECT_NO_THROW(parsed = p.parse({"1", "2", "3", "4"}););
EXPECT_EQ(fooArg->get(parsed), (std::vector<uint32_t>{1, 2, 3, 4}));
EXPECT_THROW(parsed = p.parse({}), argparser::errors::missing_argument_error);
EXPECT_THROW(parsed = p.parse({"1"}), argparser::errors::missing_argument_error);
EXPECT_THROW(parsed = p.parse({"1", "2", "3", "4", "5"}), argparser::errors::too_many_arguments_error);
EXPECT_THROW(parsed = p.parse({"1", "2", "3", "4", "5", "6"}), argparser::errors::too_many_arguments_error);
EXPECT_THROW(parsed = p.parse({"1", "2", "3", "4", "5", "6", "7"}), argparser::errors::too_many_arguments_error);
}
TEST_F(Parser, MixedArguments) {
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto tupleType = p.tuple_type("tuple", uintType, uintType);
auto argA = p.repeatable_arg("a", uintType);
auto argB = p.optional_arg("b", boolType);
auto argC = p.arg("c", tupleType);
auto argD = p.repeatable_arg("d", uintType);
auto parsed = p.parse({"1", "2", "3", "true", "1,2", "4", "5"});
auto valA = argA->get(parsed);
auto valB = argB->get(parsed);
auto valC = argC->get(parsed);
auto valD = argD->get(parsed);
EXPECT_EQ(valA.size(), 3);
EXPECT_EQ(valA[0], 1);
EXPECT_EQ(valA[1], 2);
EXPECT_EQ(valA[2], 3);
EXPECT_TRUE(valB.has_value());
EXPECT_EQ(valB.value(), true);
EXPECT_EQ(valC, (std::make_tuple<uint32_t, uint32_t>(1, 2)));
EXPECT_EQ(valD, (decltype(valD){4, 5}));
}
TEST_F(Parser, ArgumentWrongType) {
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto fooArg = p.arg("foo", boolType);
EXPECT_THROW(auto _ = p.parse({"123", "true"});, argparser::errors::type_parsing_error);
}
TEST_F(Parser, RemainingArguments) {
auto fooArg = p.arg("foo", uintType);
p.enable_remaining_args();
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"123", "234", "456"}));
EXPECT_EQ(fooArg->get(pr), 123);
EXPECT_EQ(pr.remaining(), (std::vector<std::string>{"234", "456"}));
}
TEST_F(Parser, OptionsAndFlagsAsRemainingArguments) {
auto fooOpt = p.option("--foo", uintType);
auto barFlag = p.flag("--bar");
p.enable_remaining_args();
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"123", "--foo", "456", "--bar"}));
EXPECT_EQ(pr.remaining(), (std::vector<std::string>{"123", "--foo", "456", "--bar"}));
EXPECT_FALSE(fooOpt->has(pr));
EXPECT_FALSE(barFlag->has(pr));
}
TEST_F(Parser, RemainingArgumentsWithDoubleDash) {
auto fooOpt = p.option("--foo", uintType);
auto barFlag = p.flag("--bar");
auto arg = p.arg("arg", uintType);
p.enable_remaining_args();
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"123", "--", "--foo", "456", "--bar"}));
EXPECT_EQ(pr.remaining(), (std::vector<std::string>{"--foo", "456", "--bar"}));
EXPECT_FALSE(fooOpt->has(pr));
EXPECT_FALSE(barFlag->has(pr));
EXPECT_EQ(arg->get(pr), 123);
}

778
tests/types.cpp Normal file
View File

@ -0,0 +1,778 @@
#include "argparser/argparser.h"
#include "gtest/gtest.h"
// TODO: test empty input for all types
TEST(Types, AutomaticUint8Type) {
argparser::parser p{};
argparser::type_handle_impl<uint8_t> uint8Type;
argparser::parse_result pr;
argparser::single_arg_handle<uint8_t> arg;
uint8_t val;
EXPECT_NO_THROW({
uint8Type = p.basic_type<uint8_t>("uint8");
arg = p.arg("a", uint8Type);
pr = p.parse({"250"});
val = arg->get(pr);
});
EXPECT_EQ(val, 250);
}
TEST(Types, AutomaticInt8Type) {
argparser::parser p{};
argparser::type_handle_impl<int8_t> int8Type;
argparser::parse_result pr;
argparser::single_arg_handle<int8_t> arg;
int8_t val;
EXPECT_NO_THROW({
int8Type = p.basic_type<int8_t>("int8");
arg = p.arg("a", int8Type);
pr = p.parse({"-126"});
val = arg->get(pr);
});
EXPECT_EQ(val, -126);
}
TEST(Types, AutomaticUint16Type) {
argparser::parser p{};
argparser::type_handle_impl<uint16_t> uint16Type;
argparser::parse_result pr;
argparser::single_arg_handle<uint16_t> arg;
uint16_t val;
EXPECT_NO_THROW({
uint16Type = p.basic_type<uint16_t>("uint16");
arg = p.arg("a", uint16Type);
pr = p.parse({"65530"});
val = arg->get(pr);
});
EXPECT_EQ(val, 65530);
}
TEST(Types, AutomaticInt16Type) {
argparser::parser p{};
argparser::type_handle_impl<int16_t> int16Type;
argparser::parse_result pr;
argparser::single_arg_handle<int16_t> arg;
int16_t val;
EXPECT_NO_THROW({
int16Type = p.basic_type<int16_t>("int16");
arg = p.arg("a", int16Type);
pr = p.parse({"-32760"});
val = arg->get(pr);
});
EXPECT_EQ(val, -32760);
}
TEST(Types, AutomaticUint32Type) {
argparser::parser p{};
argparser::type_handle_impl<uint32_t> uint32Type;
argparser::parse_result pr;
argparser::single_arg_handle<uint32_t> arg;
uint32_t val;
EXPECT_NO_THROW({
uint32Type = p.basic_type<uint32_t>("uint32");
arg = p.arg("a", uint32Type);
pr = p.parse({"4294967290"});
val = arg->get(pr);
});
EXPECT_EQ(val, 4294967290);
}
TEST(Types, AutomaticInt32Type) {
argparser::parser p{};
argparser::type_handle_impl<int32_t> int32Type;
argparser::parse_result pr;
argparser::single_arg_handle<int32_t> arg;
int32_t val;
EXPECT_NO_THROW({
int32Type = p.basic_type<int32_t>("int32");
arg = p.arg("a", int32Type);
pr = p.parse({"-2147483645"});
val = arg->get(pr);
});
EXPECT_EQ(val, -2147483645);
}
TEST(Types, AutomaticUint64Type) {
argparser::parser p{};
argparser::type_handle_impl<uint64_t> uint64Type;
argparser::parse_result pr;
argparser::single_arg_handle<uint64_t> arg;
uint64_t val;
EXPECT_NO_THROW({
uint64Type = p.basic_type<uint64_t>("uint64");
arg = p.arg("a", uint64Type);
pr = p.parse({"18446744073709551612"});
val = arg->get(pr);
});
EXPECT_EQ(val, 18446744073709551612ull);
}
TEST(Types, AutomaticInt64Type) {
argparser::parser p{};
argparser::type_handle_impl<int64_t> int64Type;
argparser::parse_result pr;
argparser::single_arg_handle<int64_t> arg;
int64_t val;
EXPECT_NO_THROW({
int64Type = p.basic_type<int64_t>("int64");
arg = p.arg("a", int64Type);
pr = p.parse({"-9223372036854775803"});
val = arg->get(pr);
});
EXPECT_EQ(val, -9223372036854775803);
}
TEST(Types, AutomaticFloatType) {
argparser::parser p{};
argparser::type_handle_impl<float> floatType;
argparser::parse_result pr;
argparser::single_arg_handle<float> arg;
float val;
EXPECT_NO_THROW({
floatType = p.basic_type<float>("float");
arg = p.arg("a", floatType);
pr = p.parse({"23.45"});
val = arg->get(pr);
});
EXPECT_EQ(val, 23.45f);
}
TEST(Types, AutomaticDoubleType) {
argparser::parser p{};
argparser::type_handle_impl<double> doubleType;
argparser::parse_result pr;
argparser::single_arg_handle<double> arg;
double val;
EXPECT_NO_THROW({
doubleType = p.basic_type<double>("double");
arg = p.arg("a", doubleType);
pr = p.parse({"23.45"});
val = arg->get(pr);
});
EXPECT_EQ(val, 23.45);
}
TEST(Types, AutomaticStringType) {
argparser::parser p{};
auto stringType = p.basic_type<std::string>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"foo bar"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_NO_THROW(pr = p.parse({"'foo bar'"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_NO_THROW(pr = p.parse({"\"foo bar\""}));
EXPECT_EQ(arg->get(pr), "foo bar");
}
TEST(Types, AutomaticStringTypeBareEmptyString) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::BareString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({""}));
EXPECT_EQ(arg->get(pr), "");
}
TEST(Types, AutomaticStringTypeSingleQuotedEmptyString) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::SingleQuotedString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"''"}));
EXPECT_EQ(arg->get(pr), "");
EXPECT_THROW(pr = p.parse({""}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringTypeDoubleQuotedEmptyString) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::DoubleQuotedString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"\"\""}));
EXPECT_EQ(arg->get(pr), "");
EXPECT_THROW(pr = p.parse({""}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringTypeBrokenQuote) {
argparser::parser p{};
auto stringType = p.basic_type<std::string>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_THROW(pr = p.parse({"'foobar"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"\"foobar"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"foo'bar"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"foo\"bar"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"foobar'"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"foobar\""}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringTypeBareComma) {
argparser::parser p{};
auto stringType = p.basic_type<std::string>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"foo,bar"}));
EXPECT_EQ(arg->get(pr), "foo,bar");
}
TEST(Types, AutomaticStringTypeNoBareCommaInTuple) {
argparser::parser p{};
auto stringType = p.basic_type<std::string>("string");
auto uintType = p.basic_type<uint32_t>("uint");
auto tupleType = p.tuple_type("tuple", stringType, uintType);
auto arg = p.arg("a", tupleType);
argparser::parse_result pr;
EXPECT_THROW(pr = p.parse({"tuple(foo,bar, 2)"}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringTypeBareWithWhitespaceInTuple) {
argparser::parser p{};
auto stringType = p.basic_type<std::string>("string");
auto uintType = p.basic_type<uint32_t>("uint");
auto tupleType = p.tuple_type("tuple", stringType, uintType);
auto arg = p.arg("a", tupleType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"tuple( foo bar , 2)"}));
EXPECT_EQ(arg->get(pr), std::make_tuple(" foo bar ", 2));
}
TEST(Types, AutomaticStringTypeExplicitAny) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::AnyString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"foo bar"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_NO_THROW(pr = p.parse({"'foo bar'"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_NO_THROW(pr = p.parse({"\"foo bar\""}));
EXPECT_EQ(arg->get(pr), "foo bar");
}
TEST(Types, AutomaticStringOnlyBare) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::BareString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"foo bar"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_THROW(pr = p.parse({"'foo bar'"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"\"foo bar\""}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringOnlySingleQuoted) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::SingleQuotedString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"'foo bar'"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_THROW(pr = p.parse({"foo bar"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"\"foo bar\""}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringOnlyDoubleQuoted) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::DoubleQuotedString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"\"foo bar\""}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_THROW(pr = p.parse({"foo bar"}), argparser::errors::type_parsing_error);
EXPECT_THROW(pr = p.parse({"'foo bar'"}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringOnlyQuoted) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::SingleQuotedString | argparser::internal::parse_opt::DoubleQuotedString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"\"foo bar\""}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_NO_THROW(pr = p.parse({"'foo bar'"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_THROW(pr = p.parse({"foo bar"}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringOnlyBareOrSingleQuoted) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::SingleQuotedString | argparser::internal::parse_opt::BareString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"'foo bar'"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_NO_THROW(pr = p.parse({"foo bar"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_THROW(pr = p.parse({"\"foo bar\""}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringOnlyBareOrDoubleQuoted) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::DoubleQuotedString | argparser::internal::parse_opt::BareString>("string");
auto arg = p.arg("a", stringType);
argparser::parse_result pr;
EXPECT_NO_THROW(pr = p.parse({"\"foo bar\""}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_NO_THROW(pr = p.parse({"foo bar"}));
EXPECT_EQ(arg->get(pr), "foo bar");
EXPECT_THROW(pr = p.parse({"'foo bar'"}), argparser::errors::type_parsing_error);
}
TEST(Types, AutomaticStringTypeUnquotedInTuple) {
argparser::parser p{};
auto stringType = p.basic_type<std::string>("string");
auto tupleType = p.tuple_type("tuple", stringType, stringType);
auto arg = p.arg("a", tupleType);
auto pr = p.parse({"(foo bar, asdf)"});
auto val = arg->get(pr);
EXPECT_EQ(val, std::make_tuple("foo bar", " asdf"));
}
TEST(Types, AutomaticStringTypeTrim) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::TrimString>("string");
auto arg = p.arg("a", stringType);
auto pr = p.parse({" foo "});
EXPECT_EQ(arg->get(pr), "foo");
pr = p.parse({"' foo '"});
EXPECT_EQ(arg->get(pr), "foo");
pr = p.parse({"\" foo \""});
EXPECT_EQ(arg->get(pr), "foo");
}
TEST(Types, AutomaticStringTypeTrimOnlyWhitespace) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::TrimString>("string");
auto arg = p.arg("a", stringType);
auto pr = p.parse({" "});
EXPECT_EQ(arg->get(pr), "");
pr = p.parse({"' '"});
EXPECT_EQ(arg->get(pr), "");
pr = p.parse({"\" \""});
EXPECT_EQ(arg->get(pr), "");
}
TEST(Types, AutomaticStringTypeTrimUnquoted) {
argparser::parser p{};
auto stringType = p.basic_type<std::string, argparser::internal::parse_opt::TrimBareString>("string");
auto arg = p.arg("a", stringType);
auto pr = p.parse({" foo "});
EXPECT_EQ(arg->get(pr), "foo");
pr = p.parse({"' foo '"});
EXPECT_EQ(arg->get(pr), " foo ");
pr = p.parse({"\" foo \""});
EXPECT_EQ(arg->get(pr), " foo ");
}
TEST(Types, EnumType) {
argparser::parser p;
auto boolType = p.enum_type<bool>("bool", {{"true", true},
{"yes", true},
{"false", false},
{"no", false}});
auto fooOption = p.option("--foo", boolType);
auto parsed = p.parse({"--foo", "true"});
EXPECT_EQ(fooOption->get(parsed), true);
parsed = p.parse({"--foo", "false"});
EXPECT_EQ(fooOption->get(parsed), false);
parsed = p.parse({"--foo", "yes"});
EXPECT_EQ(fooOption->get(parsed), true);
parsed = p.parse({"--foo", "no"});
EXPECT_EQ(fooOption->get(parsed), false);
}
TEST(Types, EnumTypeEmptyError) {
argparser::parser p;
EXPECT_THROW(p.enum_type("enum1", {}), argparser::errors::empty_enum_map_error);
EXPECT_THROW(p.enum_type<bool>("enum1", {}), argparser::errors::empty_enum_map_error);
}
TEST(Types, EnumTypeLongestMatching) {
argparser::parser p;
auto enumType = p.enum_type<int>("enum", {
{"a", 1},
{"ab", 2},
});
auto fooOption = p.option("--foo", enumType);
auto parsed = p.parse({"--foo", "a"});
EXPECT_EQ(fooOption->get(parsed), 1);
parsed = p.parse({"--foo", "ab"});
EXPECT_EQ(fooOption->get(parsed), 2);
}
TEST(Types, EnumTypeLongestMatchingReverseOrder) {
argparser::parser p;
auto enumType = p.enum_type<int>("enum", {
{"ab", 2},
{"a", 1},
});
auto fooOption = p.option("--foo", enumType);
auto parsed = p.parse({"--foo", "a"});
EXPECT_EQ(fooOption->get(parsed), 1);
parsed = p.parse({"--foo", "ab"});
EXPECT_EQ(fooOption->get(parsed), 2);
}
TEST(Types, EnumTypeInvalidValue) {
argparser::parser p;
auto enumType = p.enum_type<int>("enum", {{"a", 1}, {"b", 2}, {"c", 3}});
auto _1 = p.option("--foo", enumType);
EXPECT_THROW(auto _ = p.parse({"--foo", "x"}), argparser::errors::type_parsing_error);
EXPECT_THROW(auto _ = p.parse({"--foo", "aa"}), argparser::errors::type_parsing_error);
}
TEST(Types, StringEnumType) {
argparser::parser p;
auto enumType = p.enum_type("enum", {"foo", "bar"});
auto fooOption = p.option("--foo", enumType);
auto parsed = p.parse({"--foo", "foo"});
EXPECT_EQ(fooOption->get(parsed), "foo");
parsed = p.parse({"--foo", "bar"});
EXPECT_EQ(fooOption->get(parsed), "bar");
}
TEST(Types, UnionType) {
argparser::parser p;
auto enumType1 = p.enum_type("enum", {"foo", "bar"});
auto enumType2 = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto unionType = p.union_type("enum-or-bool", enumType1, enumType2);
auto fooOption = p.option("--foo", unionType);
auto parsed = p.parse({"--foo", "bar"});
auto val = fooOption->get(parsed);
EXPECT_EQ(std::holds_alternative<std::string>(val), true);
EXPECT_EQ(std::get<std::string>(val), "bar");
parsed = p.parse({"--foo", "true"});
val = fooOption->get(parsed);
EXPECT_EQ(std::holds_alternative<bool>(val), true);
EXPECT_EQ(std::get<bool>(val), true);
}
TEST(Types, UnionTypeWithSameUnderlyingBaseType) {
argparser::parser p;
auto enumType1 = p.enum_type("enum", {"foo", "bar"});
auto enumType2 = p.enum_type("enum", {"hello", "world"});
auto unionType = p.union_type("enum-or-bool", enumType1, enumType2);
auto fooOption = p.option("--foo", unionType);
auto parsed = p.parse({"--foo", "bar"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val, "bar");
parsed = p.parse({"--foo", "hello"});
val = fooOption->get(parsed);
EXPECT_EQ(val, "hello");
}
TEST(Types, UnionTypeError) {
argparser::parser p;
auto enumType1 = p.enum_type("enum", {"foo", "bar"});
auto enumType2 = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto unionType = p.union_type("union", enumType1, enumType2);
auto fooOption = p.option("--foo", unionType);
EXPECT_THROW(auto _ = p.parse({"--foo", "123"}), argparser::errors::type_parsing_error);
}
TEST(Types, UnionTypeErrorOnAmbiguity) {
argparser::parser p;
auto enumType1 = p.enum_type<int>("enum1", {{"foo", 1}, {"bar", 2}});
auto enumType2 = p.enum_type<bool>("enum2", {{"bar", true}, {"hello-world", false}});
auto unionType = p.union_type("union", enumType1, enumType2);
auto fooOption = p.option("--foo", unionType);
EXPECT_THROW(auto _1 = p.parse({"--foo", "bar"});, argparser::errors::ambiguous_parse_error);
auto enumType3 = p.enum_type<int>("enum3", {{"bar", 1}, {"hello-world", 2}});
auto unionType2 = p.union_type("union", enumType1, enumType3);
auto barOption = p.option("--bar", unionType2);
EXPECT_THROW(auto _1 = p.parse({"--bar", "bar"});, argparser::errors::ambiguous_parse_error);
}
TEST(Types, UnionTypeNoAmbiguityErrorIfPossibleValuesIdentical) {
argparser::parser p;
auto enumType1 = p.enum_type<int>("enum1", {{"foo", 1}, {"bar", 2}});
auto enumType2 = p.enum_type<int>("enum2", {{"foo", 1}, {"foobar", 3}});
auto unionType = p.union_type("union", enumType1, enumType2);
auto fooOption = p.option("--foo", unionType);
argparser::parse_result parsed;
EXPECT_NO_THROW(parsed = p.parse({"--foo", "foo"}));
ASSERT_EQ(fooOption->get(parsed), 1);
}
TEST(Types, ListType) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto listType = p.list_type("uint[]", uintType);
auto fooOption = p.option("--foo", listType);
auto parsed = p.parse({"--foo", "[1,2,3,4]"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val.size(), 4);
EXPECT_EQ(val[0], 1);
EXPECT_EQ(val[1], 2);
EXPECT_EQ(val[2], 3);
EXPECT_EQ(val[3], 4);
}
TEST(Types, ListTypeWithoutBracketsAtRoot) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto listType = p.list_type("uint[]", uintType);
auto fooOption = p.option("--foo", listType);
auto parsed = p.parse({"--foo", "1,2,3,4"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val.size(), 4);
EXPECT_EQ(val[0], 1);
EXPECT_EQ(val[1], 2);
EXPECT_EQ(val[2], 3);
EXPECT_EQ(val[3], 4);
}
TEST(Types, ListTypeTrailingComma) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto listType = p.list_type("uint[]", uintType);
auto fooOption = p.option("--foo", listType);
auto parsed = p.parse({"--foo", "[1,2,3,]"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val.size(), 3);
EXPECT_EQ(val[0], 1);
EXPECT_EQ(val[1], 2);
EXPECT_EQ(val[2], 3);
parsed = p.parse({"--foo", "1,2,3,"});
val = fooOption->get(parsed);
EXPECT_EQ(val.size(), 3);
EXPECT_EQ(val[0], 1);
EXPECT_EQ(val[1], 2);
EXPECT_EQ(val[2], 3);
}
TEST(Types, ListTypeOnlyOneTrailingComma) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto listType = p.list_type("uint[]", uintType);
auto fooOption = p.option("--foo", listType);
EXPECT_THROW(auto _1 = p.parse({"--foo", "[1,2,3,,]"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2,3,,"});, argparser::errors::type_parsing_error);
}
TEST(Types, ListTypeNoPrecedingComma) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto listType = p.list_type("uint[]", uintType);
auto fooOption = p.option("--foo", listType);
EXPECT_THROW(auto _1 = p.parse({"--foo", "[,1,2,3]"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", ",1,2,3"});, argparser::errors::type_parsing_error);
}
TEST(Types, ListTypeNoDoubleComma) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto listType = p.list_type("uint[]", uintType);
auto fooOption = p.option("--foo", listType);
EXPECT_THROW(auto _1 = p.parse({"--foo", "[1,,2,3]"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "[1, ,2,3]"});, argparser::errors::type_parsing_error);
}
TEST(Types, ListTypeInvalidBrackets) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto listType = p.list_type("uint[]", uintType);
auto fooOption = p.option("--foo", listType);
EXPECT_THROW(auto _1 = p.parse({"--foo", "[1,2,3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2,3]"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1[,2,3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,[2,3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2],3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2,]3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "[1,2],3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "[1,2,]3"});, argparser::errors::type_parsing_error);
}
TEST(Types, TupleType) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto tupleType = p.tuple_type("tuple", uintType, uintType, boolType);
auto fooOption = p.option("--foo", tupleType);
auto parsed = p.parse({"--foo", "(1,2,true)"});
auto val = fooOption->get(parsed);
EXPECT_EQ(std::get<0>(val), 1);
EXPECT_EQ(std::get<1>(val), 2);
EXPECT_EQ(std::get<2>(val), true);
}
TEST(Types, TupleTypeWithoutParenthesesAtRoot) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto tupleType = p.tuple_type("tuple", uintType, uintType, uintType);
auto fooOption = p.option("--foo", tupleType);
auto parsed = p.parse({"--foo", "1,2,3"});
auto val = fooOption->get(parsed);
EXPECT_EQ(std::get<0>(val), 1);
EXPECT_EQ(std::get<1>(val), 2);
EXPECT_EQ(std::get<2>(val), 3);
}
TEST(Types, TupleTypeInvalidParentheses) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto tupleType = p.tuple_type("tuple", uintType, uintType, uintType);
auto fooOption = p.option("--foo", tupleType);
EXPECT_THROW(auto _1 = p.parse({"--foo", "(1,2,3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2,3)"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1(,2,3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,(2,3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2),3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2,)3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "(1,2),3"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "(1,2,)3"});, argparser::errors::type_parsing_error);
}
TEST(Types, TupleTypeNoInvalidComma) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto tupleType = p.tuple_type("tuple", uintType, uintType, uintType);
auto fooOption = p.option("--foo", tupleType);
EXPECT_THROW(auto _2 = p.parse({"--foo", "(1,2,3,)"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "(,1,2,3)"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "(1,,2,3)"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "(1, ,2,3)"});, argparser::errors::type_parsing_error);
EXPECT_THROW(auto _2 = p.parse({"--foo", "1,2,3,"});, argparser::errors::type_parsing_error);
}
struct SomeStruct {
uint32_t foo;
uint32_t bar;
bool foobar;
};
TEST(Types, TupleTypeWithCustomType) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto tupleType = p.tuple_type<SomeStruct>("tuple", uintType, uintType, boolType);
auto fooOption = p.option("--foo", tupleType);
auto parsed = p.parse({"--foo", "(1,2,true)"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val.foo, 1);
EXPECT_EQ(val.bar, 2);
EXPECT_EQ(val.foobar, true);
}
TEST(Types, TupleWithCustomTypeAndCustomConstructor) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto tupleType = p.tuple_type<SomeStruct>(
"tuple",
std::make_tuple(uintType, uintType, boolType),
[](uint32_t a, uint32_t b, bool c) {
return SomeStruct{a * 2, b * 3, !c};
});
auto fooOption = p.option("--foo", tupleType);
auto parsed = p.parse({"--foo", "(1,2,true)"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val.foo, 2);
EXPECT_EQ(val.bar, 6);
EXPECT_EQ(val.foobar, false);
}
TEST(Types, TupleWithSharedPointerAndConstructor) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto tupleType = p.tuple_type<std::shared_ptr<SomeStruct>>(
"tuple",
std::make_tuple(uintType, uintType, boolType),
[](uint32_t a, uint32_t b, bool c) {
return std::make_shared<SomeStruct>(a, b, c);
});
auto fooOption = p.option("--foo", tupleType);
auto parsed = p.parse({"--foo", "(1,2,true)"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val->foo, 1);
EXPECT_EQ(val->bar, 2);
EXPECT_EQ(val->foobar, true);
}
class SomeVirtualClass {
public:
virtual int foo() = 0;
};
class SomeClass : public SomeVirtualClass {
public:
SomeClass(int a, int b) : a(a), b(b) {}
int a{};
int b{};
int foo() override {
return a + b;
}
};
TEST(Types, TupleWithSharedPointerToVirtualClass) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto tupleType = p.tuple_type<std::shared_ptr<SomeVirtualClass>>(
"tuple",
std::make_tuple(uintType, uintType),
[](uint32_t a, uint32_t b) {
return std::make_shared<SomeClass>(a, b);
});
auto fooOption = p.option("--foo", tupleType);
auto parsed = p.parse({"--foo", "(1,2)"});
auto val = fooOption->get(parsed);
EXPECT_EQ(val->foo(), 3);
}
TEST(Types, TuplesCanBeNamePrefixed) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto tupleType = p.tuple_type("tuple", uintType, uintType, boolType);
auto fooOption = p.option("--foo", tupleType);
auto parsed = p.parse({"--foo", "tuple(1,2,true)"});
auto val = fooOption->get(parsed);
EXPECT_EQ(std::get<0>(val), 1);
EXPECT_EQ(std::get<1>(val), 2);
EXPECT_EQ(std::get<2>(val), true);
}
TEST(Types, TupleNamePrefixMustBeCorrect) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto tupleType = p.tuple_type("tuple", uintType, uintType, boolType);
auto fooOption = p.option("--foo", tupleType);
EXPECT_THROW(auto _1 = p.parse({"--foo", "foobar(1,2,true)"});, argparser::errors::type_parsing_error);
}
TEST(Types, ComplexNestedTypes) {
argparser::parser p;
auto uintType = p.basic_type<uint32_t>("uint");
auto tupleType = p.tuple_type("uint-tuple", uintType, uintType, uintType);
auto enumType = p.enum_type("enum", {"foo", "bar", "hello", "world"});
auto unionType = p.union_type("union", tupleType, enumType);
auto listType = p.list_type("uint-tuple-list", unionType);
auto fooOption = p.option("--foo", listType);
auto parsed = p.parse({"--foo", "[foo,(1,2,3),hello,world,(2,3,4),(4,5,6)]"});
auto val = fooOption->get(parsed);
using tuplet = std::tuple<uint32_t, uint32_t, uint32_t>;
EXPECT_EQ(val.size(), 6);
EXPECT_TRUE(std::holds_alternative<std::string>(val[0]));
EXPECT_EQ(std::get<std::string>(val[0]), "foo");
EXPECT_TRUE(std::holds_alternative<tuplet>(val[1]));
EXPECT_EQ(std::get<0>(std::get<tuplet>(val[1])), 1);
EXPECT_EQ(std::get<1>(std::get<tuplet>(val[1])), 2);
EXPECT_EQ(std::get<2>(std::get<tuplet>(val[1])), 3);
EXPECT_TRUE(std::holds_alternative<std::string>(val[2]));
EXPECT_EQ(std::get<std::string>(val[2]), "hello");
EXPECT_TRUE(std::holds_alternative<std::string>(val[3]));
EXPECT_EQ(std::get<std::string>(val[3]), "world");
EXPECT_TRUE(std::holds_alternative<tuplet>(val[4]));
EXPECT_EQ(std::get<0>(std::get<tuplet>(val[4])), 2);
EXPECT_EQ(std::get<1>(std::get<tuplet>(val[4])), 3);
EXPECT_EQ(std::get<2>(std::get<tuplet>(val[4])), 4);
EXPECT_TRUE(std::holds_alternative<tuplet>(val[5]));
EXPECT_EQ(std::get<0>(std::get<tuplet>(val[5])), 4);
EXPECT_EQ(std::get<1>(std::get<tuplet>(val[5])), 5);
EXPECT_EQ(std::get<2>(std::get<tuplet>(val[5])), 6);
}