mirror of
https://git.lynn.is/Gwen/argparser.git
synced 2024-05-23 16:59:33 +02:00
something
This commit is contained in:
commit
3bf3cfbc9c
67
.clang-format
Normal file
67
.clang-format
Normal 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
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/cmake-*
|
||||
/.idea
|
48
CMakeLists.txt
Normal file
48
CMakeLists.txt
Normal 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
24
build.sh
Normal 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
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
1
src/_include_order.h
Normal file
|
@ -0,0 +1 @@
|
|||
#include "parser.h"
|
59
src/argument.h
Normal file
59
src/argument.h
Normal 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
33
src/basic-type.h
Normal 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
172
src/builtin_parser.h
Normal 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
53
src/defs.h
Normal 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
51
src/distinct-types.h
Normal 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
116
src/errors.h
Normal 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
84
src/list-type.h
Normal 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
39
src/option.h
Normal 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
66
src/optional-arg.h
Normal 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
61
src/parse-result.h
Normal 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
364
src/parser.h
Normal 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> ¶ms) {
|
||||
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
14
src/parser_func.h
Normal 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
95
src/repeat-arg.h
Normal 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
72
src/repeat-flag.h
Normal 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
96
src/repeat-opt.h
Normal 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
54
src/single-arg.h
Normal 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
60
src/single-flag.h
Normal 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
84
src/single-opt.h
Normal 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
30
src/tuple-iteration.h
Normal 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
127
src/tuple-type.h
Normal 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
41
src/type.h
Normal 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
131
src/union-type.h
Normal 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
565
tests/parser.cpp
Normal 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
778
tests/types.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue