mirror of
https://github.com/sametersoylu/argument-parser.git
synced 2026-04-13 03:41:18 +00:00
Merge pull request #6 from sametersoylu/feat/positionalargs
feat: introduce positional arguments. add tests. fix parsing bugs.
This commit is contained in:
@@ -51,4 +51,7 @@ install(EXPORT argument_parserTargets
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_executable(test src/main.cpp)
|
add_executable(test src/main.cpp)
|
||||||
target_link_libraries(test PRIVATE argument_parser)
|
target_link_libraries(test PRIVATE argument_parser)
|
||||||
|
|
||||||
|
add_executable(positional_tests src/test.cpp)
|
||||||
|
target_link_libraries(positional_tests PRIVATE argument_parser)
|
||||||
@@ -202,11 +202,15 @@ namespace argument_parser {
|
|||||||
[[nodiscard]] bool is_invoked() const;
|
[[nodiscard]] bool is_invoked() const;
|
||||||
[[nodiscard]] bool expects_parameter() const;
|
[[nodiscard]] bool expects_parameter() const;
|
||||||
[[nodiscard]] std::string get_help_text() const;
|
[[nodiscard]] std::string get_help_text() const;
|
||||||
|
[[nodiscard]] bool is_positional() const;
|
||||||
|
[[nodiscard]] std::optional<int> get_position_index() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_required(bool val);
|
void set_required(bool val);
|
||||||
void set_invoked(bool val);
|
void set_invoked(bool val);
|
||||||
void set_help_text(std::string const &text);
|
void set_help_text(std::string const &text);
|
||||||
|
void set_positional(bool val);
|
||||||
|
void set_position_index(std::optional<int> idx);
|
||||||
|
|
||||||
friend class base_parser;
|
friend class base_parser;
|
||||||
|
|
||||||
@@ -216,6 +220,8 @@ namespace argument_parser {
|
|||||||
bool required;
|
bool required;
|
||||||
bool invoked;
|
bool invoked;
|
||||||
std::string help_text;
|
std::string help_text;
|
||||||
|
bool positional = false;
|
||||||
|
std::optional<int> position_index = std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace helpers {
|
namespace helpers {
|
||||||
@@ -260,6 +266,19 @@ namespace argument_parser {
|
|||||||
base_add_argument<void>(short_arg, long_arg, help_text, required);
|
base_add_argument<void>(short_arg, long_arg, help_text, required);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void add_positional_argument(std::string const &name, std::string const &help_text,
|
||||||
|
parametered_action<T> const &action, bool required,
|
||||||
|
std::optional<int> position = std::nullopt) {
|
||||||
|
base_add_positional_argument(name, help_text, action, required, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void add_positional_argument(std::string const &name, std::string const &help_text, bool required,
|
||||||
|
std::optional<int> position = std::nullopt) {
|
||||||
|
base_add_positional_argument<T>(name, help_text, required, position);
|
||||||
|
}
|
||||||
|
|
||||||
void on_complete(std::function<void(base_parser const &)> const &action);
|
void on_complete(std::function<void(base_parser const &)> const &action);
|
||||||
|
|
||||||
template <typename T> std::optional<T> get_optional(std::string const &arg) const {
|
template <typename T> std::optional<T> get_optional(std::string const &arg) const {
|
||||||
@@ -302,7 +321,7 @@ namespace argument_parser {
|
|||||||
bool test_conventions(std::initializer_list<conventions::convention const *const> convention_types,
|
bool test_conventions(std::initializer_list<conventions::convention const *const> convention_types,
|
||||||
std::unordered_map<std::string, std::string> &values_for_arguments,
|
std::unordered_map<std::string, std::string> &values_for_arguments,
|
||||||
std::vector<std::pair<std::string, argument>> &found_arguments,
|
std::vector<std::pair<std::string, argument>> &found_arguments,
|
||||||
std::optional<argument> &found_help, std::vector<std::string>::iterator it,
|
std::optional<argument> &found_help, std::vector<std::string>::iterator &it,
|
||||||
std::stringstream &error_stream);
|
std::stringstream &error_stream);
|
||||||
void extract_arguments(std::initializer_list<conventions::convention const *const> convention_types,
|
void extract_arguments(std::initializer_list<conventions::convention const *const> convention_types,
|
||||||
std::unordered_map<std::string, std::string> &values_for_arguments,
|
std::unordered_map<std::string, std::string> &values_for_arguments,
|
||||||
@@ -315,8 +334,11 @@ namespace argument_parser {
|
|||||||
void enforce_creation_thread();
|
void enforce_creation_thread();
|
||||||
|
|
||||||
void assert_argument_not_exist(std::string const &short_arg, std::string const &long_arg) const;
|
void assert_argument_not_exist(std::string const &short_arg, std::string const &long_arg) const;
|
||||||
|
void assert_positional_not_exist(std::string const &name) const;
|
||||||
static void set_argument_status(bool is_required, std::string const &help_text, argument &arg);
|
static void set_argument_status(bool is_required, std::string const &help_text, argument &arg);
|
||||||
void place_argument(int id, argument const &arg, std::string const &short_arg, std::string const &long_arg);
|
void place_argument(int id, argument const &arg, std::string const &short_arg, std::string const &long_arg);
|
||||||
|
void place_positional_argument(int id, argument const &arg, std::string const &name,
|
||||||
|
std::optional<int> position);
|
||||||
|
|
||||||
template <typename ActionType>
|
template <typename ActionType>
|
||||||
void base_add_argument(std::string const &short_arg, std::string const &long_arg, std::string const &help_text,
|
void base_add_argument(std::string const &short_arg, std::string const &long_arg, std::string const &help_text,
|
||||||
@@ -348,6 +370,33 @@ namespace argument_parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ActionType>
|
||||||
|
void base_add_positional_argument(std::string const &name, std::string const &help_text,
|
||||||
|
ActionType const &action, bool required,
|
||||||
|
std::optional<int> position = std::nullopt) {
|
||||||
|
assert_positional_not_exist(name);
|
||||||
|
int id = id_counter.fetch_add(1);
|
||||||
|
argument arg(id, name, action);
|
||||||
|
set_argument_status(required, help_text, arg);
|
||||||
|
arg.set_positional(true);
|
||||||
|
arg.set_position_index(position);
|
||||||
|
place_positional_argument(id, arg, name, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename StoreType>
|
||||||
|
void base_add_positional_argument(std::string const &name, std::string const &help_text, bool required,
|
||||||
|
std::optional<int> position = std::nullopt) {
|
||||||
|
assert_positional_not_exist(name);
|
||||||
|
int id = id_counter.fetch_add(1);
|
||||||
|
auto action = helpers::make_parametered_action<StoreType>(
|
||||||
|
[id, this](StoreType const &value) { stored_arguments[id] = std::any{value}; });
|
||||||
|
argument arg(id, name, action);
|
||||||
|
set_argument_status(required, help_text, arg);
|
||||||
|
arg.set_positional(true);
|
||||||
|
arg.set_position_index(position);
|
||||||
|
place_positional_argument(id, arg, name, position);
|
||||||
|
}
|
||||||
|
|
||||||
void check_for_required_arguments(std::initializer_list<conventions::convention const *const> convention_types);
|
void check_for_required_arguments(std::initializer_list<conventions::convention const *const> convention_types);
|
||||||
void fire_on_complete_events() const;
|
void fire_on_complete_events() const;
|
||||||
|
|
||||||
@@ -360,6 +409,10 @@ namespace argument_parser {
|
|||||||
std::unordered_map<std::string, int> long_arguments;
|
std::unordered_map<std::string, int> long_arguments;
|
||||||
std::unordered_map<int, std::string> reverse_long_arguments;
|
std::unordered_map<int, std::string> reverse_long_arguments;
|
||||||
|
|
||||||
|
std::vector<int> positional_arguments;
|
||||||
|
std::unordered_map<std::string, int> positional_name_map;
|
||||||
|
std::unordered_map<int, std::string> reverse_positional_names;
|
||||||
|
|
||||||
std::initializer_list<conventions::convention const *const> _current_conventions;
|
std::initializer_list<conventions::convention const *const> _current_conventions;
|
||||||
internal::atomic::copyable_atomic<std::thread::id> creation_thread_id = std::this_thread::get_id();
|
internal::atomic::copyable_atomic<std::thread::id> creation_thread_id = std::this_thread::get_id();
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace argument_parser::v2 {
|
namespace argument_parser::v2 {
|
||||||
enum class add_argument_flags { ShortArgument, LongArgument, HelpText, Action, Required };
|
enum class add_argument_flags { ShortArgument, LongArgument, Positional, Position, HelpText, Action, Required };
|
||||||
|
|
||||||
namespace flags {
|
namespace flags {
|
||||||
constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument;
|
constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument;
|
||||||
@@ -23,12 +23,14 @@ namespace argument_parser::v2 {
|
|||||||
constexpr static inline add_argument_flags HelpText = add_argument_flags::HelpText;
|
constexpr static inline add_argument_flags HelpText = add_argument_flags::HelpText;
|
||||||
constexpr static inline add_argument_flags Action = add_argument_flags::Action;
|
constexpr static inline add_argument_flags Action = add_argument_flags::Action;
|
||||||
constexpr static inline add_argument_flags Required = add_argument_flags::Required;
|
constexpr static inline add_argument_flags Required = add_argument_flags::Required;
|
||||||
|
constexpr static inline add_argument_flags Positional = add_argument_flags::Positional;
|
||||||
|
constexpr static inline add_argument_flags Position = add_argument_flags::Position;
|
||||||
} // namespace flags
|
} // namespace flags
|
||||||
|
|
||||||
class base_parser : private argument_parser::base_parser {
|
class base_parser : private argument_parser::base_parser {
|
||||||
public:
|
public:
|
||||||
template <typename T> using typed_flag_value = std::variant<std::string, parametered_action<T>, bool>;
|
template <typename T> using typed_flag_value = std::variant<std::string, parametered_action<T>, bool, int>;
|
||||||
using non_typed_flag_value = std::variant<std::string, non_parametered_action, bool>;
|
using non_typed_flag_value = std::variant<std::string, non_parametered_action, bool, int>;
|
||||||
|
|
||||||
template <typename T> using typed_argument_pair = std::pair<add_argument_flags, typed_flag_value<T>>;
|
template <typename T> using typed_argument_pair = std::pair<add_argument_flags, typed_flag_value<T>>;
|
||||||
using non_typed_argument_pair = std::pair<add_argument_flags, non_typed_flag_value>;
|
using non_typed_argument_pair = std::pair<add_argument_flags, non_typed_flag_value>;
|
||||||
@@ -102,6 +104,11 @@ namespace argument_parser::v2 {
|
|||||||
private:
|
private:
|
||||||
template <bool IsTyped, typename ActionType, typename T, typename ArgsMap>
|
template <bool IsTyped, typename ActionType, typename T, typename ArgsMap>
|
||||||
void add_argument_impl(ArgsMap const &argument_pairs) {
|
void add_argument_impl(ArgsMap const &argument_pairs) {
|
||||||
|
if (argument_pairs.find(add_argument_flags::Positional) != argument_pairs.end()) {
|
||||||
|
add_positional_argument_impl<IsTyped, ActionType, T>(argument_pairs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::unordered_map<extended_add_argument_flags, bool> found_params{
|
std::unordered_map<extended_add_argument_flags, bool> found_params{
|
||||||
{extended_add_argument_flags::IsTyped, IsTyped}};
|
{extended_add_argument_flags::IsTyped, IsTyped}};
|
||||||
|
|
||||||
@@ -206,6 +213,58 @@ namespace argument_parser::v2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool IsTyped, typename ActionType, typename T, typename ArgsMap>
|
||||||
|
void add_positional_argument_impl(ArgsMap const &argument_pairs) {
|
||||||
|
std::string positional_name =
|
||||||
|
get_or_throw<std::string>(argument_pairs.at(add_argument_flags::Positional), "positional");
|
||||||
|
|
||||||
|
std::string help_text;
|
||||||
|
std::unique_ptr<action_base> action;
|
||||||
|
bool required = false;
|
||||||
|
std::optional<int> position = std::nullopt;
|
||||||
|
|
||||||
|
if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) {
|
||||||
|
action = get_or_throw<ActionType>(argument_pairs.at(add_argument_flags::Action), "action").clone();
|
||||||
|
}
|
||||||
|
if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) {
|
||||||
|
help_text = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::HelpText), "help");
|
||||||
|
}
|
||||||
|
if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() &&
|
||||||
|
get_or_throw<bool>(argument_pairs.at(add_argument_flags::Required), "required")) {
|
||||||
|
required = true;
|
||||||
|
}
|
||||||
|
if (argument_pairs.find(add_argument_flags::Position) != argument_pairs.end()) {
|
||||||
|
position = get_or_throw<int>(argument_pairs.at(add_argument_flags::Position), "position");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (help_text.empty()) {
|
||||||
|
if constexpr (IsTyped) {
|
||||||
|
if constexpr (internal::sfinae::has_format_hint<parsing_traits::parser_trait<T>>::value &&
|
||||||
|
internal::sfinae::has_purpose_hint<parsing_traits::parser_trait<T>>::value) {
|
||||||
|
auto format_hint = parsing_traits::parser_trait<T>::format_hint;
|
||||||
|
auto purpose_hint = parsing_traits::parser_trait<T>::purpose_hint;
|
||||||
|
help_text =
|
||||||
|
"Accepts " + std::string(purpose_hint) + " in " + std::string(format_hint) + " format.";
|
||||||
|
} else {
|
||||||
|
help_text = "Accepts value.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
help_text = "Accepts value.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (IsTyped) {
|
||||||
|
if (action) {
|
||||||
|
base::add_positional_argument<T>(positional_name, help_text,
|
||||||
|
*static_cast<ActionType *>(&(*action)), required, position);
|
||||||
|
} else {
|
||||||
|
base::template add_positional_argument<T>(positional_name, help_text, required, position);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
base::template add_positional_argument<std::string>(positional_name, help_text, required, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using base = argument_parser::base_parser;
|
using base = argument_parser::base_parser;
|
||||||
enum class extended_add_argument_flags { ShortArgument, LongArgument, Action, IsTyped };
|
enum class extended_add_argument_flags { ShortArgument, LongArgument, Action, IsTyped };
|
||||||
|
|
||||||
@@ -250,4 +309,4 @@ namespace argument_parser::v2 {
|
|||||||
throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key));
|
throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace argument_parser::v2
|
} // namespace argument_parser::v2
|
||||||
|
|||||||
21
src/main.cpp
21
src/main.cpp
@@ -169,8 +169,29 @@ int v2Examples() {
|
|||||||
|
|
||||||
parser.add_argument({{ShortArgument, "v"}, {LongArgument, "verbose"}});
|
parser.add_argument({{ShortArgument, "v"}, {LongArgument, "verbose"}});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({
|
||||||
|
{Positional, "input"},
|
||||||
|
{HelpText, "Input file to process"},
|
||||||
|
{Required, true},
|
||||||
|
});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({
|
||||||
|
{Positional, "output"},
|
||||||
|
{HelpText, "Output file path"},
|
||||||
|
});
|
||||||
|
|
||||||
parser.on_complete(::run_grep);
|
parser.on_complete(::run_grep);
|
||||||
parser.on_complete(::run_store_point);
|
parser.on_complete(::run_store_point);
|
||||||
|
parser.on_complete([](argument_parser::base_parser const &p) {
|
||||||
|
auto input = p.get_optional<std::string>("input");
|
||||||
|
auto output = p.get_optional<std::string>("output");
|
||||||
|
if (input) {
|
||||||
|
std::cout << "Input: " << input.value() << std::endl;
|
||||||
|
}
|
||||||
|
if (output) {
|
||||||
|
std::cout << "Output: " << output.value() << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
parser.handle_arguments(conventions);
|
parser.handle_arguments(conventions);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ namespace argument_parser {
|
|||||||
|
|
||||||
argument::argument(const argument &other)
|
argument::argument(const argument &other)
|
||||||
: id(other.id), name(other.name), action(other.action->clone()), required(other.required),
|
: id(other.id), name(other.name), action(other.action->clone()), required(other.required),
|
||||||
invoked(other.invoked), help_text(other.help_text) {}
|
invoked(other.invoked), help_text(other.help_text), positional(other.positional),
|
||||||
|
position_index(other.position_index) {}
|
||||||
|
|
||||||
argument &argument::operator=(const argument &other) {
|
argument &argument::operator=(const argument &other) {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
@@ -42,6 +43,8 @@ namespace argument_parser {
|
|||||||
required = other.required;
|
required = other.required;
|
||||||
invoked = other.invoked;
|
invoked = other.invoked;
|
||||||
help_text = other.help_text;
|
help_text = other.help_text;
|
||||||
|
positional = other.positional;
|
||||||
|
position_index = other.position_index;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -78,6 +81,22 @@ namespace argument_parser {
|
|||||||
help_text = text;
|
help_text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool argument::is_positional() const {
|
||||||
|
return positional;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> argument::get_position_index() const {
|
||||||
|
return position_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void argument::set_positional(bool val) {
|
||||||
|
positional = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void argument::set_position_index(std::optional<int> idx) {
|
||||||
|
position_index = idx;
|
||||||
|
}
|
||||||
|
|
||||||
void base_parser::on_complete(std::function<void(base_parser const &)> const &handler) {
|
void base_parser::on_complete(std::function<void(base_parser const &)> const &handler) {
|
||||||
on_complete_events.emplace_back(handler);
|
on_complete_events.emplace_back(handler);
|
||||||
}
|
}
|
||||||
@@ -85,7 +104,22 @@ namespace argument_parser {
|
|||||||
std::string
|
std::string
|
||||||
base_parser::build_help_text(std::initializer_list<conventions::convention const *const> convention_types) const {
|
base_parser::build_help_text(std::initializer_list<conventions::convention const *const> convention_types) const {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Usage: " << program_name << " [OPTIONS]...\n";
|
ss << "Usage: " << program_name << " [OPTIONS]...";
|
||||||
|
|
||||||
|
for (auto const &pos_id : positional_arguments) {
|
||||||
|
if (pos_id == -1)
|
||||||
|
continue;
|
||||||
|
auto name_it = reverse_positional_names.find(pos_id);
|
||||||
|
if (name_it == reverse_positional_names.end())
|
||||||
|
continue;
|
||||||
|
auto const &arg = argument_map.at(pos_id);
|
||||||
|
if (arg.is_required()) {
|
||||||
|
ss << " <" << name_it->second << ">";
|
||||||
|
} else {
|
||||||
|
ss << " [" << name_it->second << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ss << "\n";
|
||||||
|
|
||||||
size_t max_short_len = 0;
|
size_t max_short_len = 0;
|
||||||
size_t max_long_len = 0;
|
size_t max_long_len = 0;
|
||||||
@@ -97,6 +131,9 @@ namespace argument_parser {
|
|||||||
std::vector<arg_help_info_t> help_lines;
|
std::vector<arg_help_info_t> help_lines;
|
||||||
|
|
||||||
for (auto const &[id, arg] : argument_map) {
|
for (auto const &[id, arg] : argument_map) {
|
||||||
|
if (arg.is_positional())
|
||||||
|
continue;
|
||||||
|
|
||||||
auto short_arg =
|
auto short_arg =
|
||||||
reverse_short_arguments.find(id) != reverse_short_arguments.end() ? reverse_short_arguments.at(id) : "";
|
reverse_short_arguments.find(id) != reverse_short_arguments.end() ? reverse_short_arguments.at(id) : "";
|
||||||
auto long_arg =
|
auto long_arg =
|
||||||
@@ -124,17 +161,46 @@ namespace argument_parser {
|
|||||||
help_lines.push_back({parts, arg.help_text});
|
help_lines.push_back({parts, arg.help_text});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const &line : help_lines) {
|
if (!help_lines.empty()) {
|
||||||
ss << "\t";
|
for (auto const &line : help_lines) {
|
||||||
for (size_t i = 0; i < line.convention_parts.size(); ++i) {
|
ss << "\t";
|
||||||
auto const &parts = line.convention_parts[i];
|
for (size_t i = 0; i < line.convention_parts.size(); ++i) {
|
||||||
if (i > 0) {
|
auto const &parts = line.convention_parts[i];
|
||||||
ss << " ";
|
if (i > 0) {
|
||||||
|
ss << " ";
|
||||||
|
}
|
||||||
|
ss << std::left << std::setw(static_cast<int>(max_short_len)) << parts.first << " "
|
||||||
|
<< std::setw(static_cast<int>(max_long_len)) << parts.second;
|
||||||
}
|
}
|
||||||
ss << std::left << std::setw(static_cast<int>(max_short_len)) << parts.first << " "
|
ss << "\t" << line.desc << "\n";
|
||||||
<< std::setw(static_cast<int>(max_long_len)) << parts.second;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!positional_arguments.empty()) {
|
||||||
|
ss << "\nPositional arguments:\n";
|
||||||
|
size_t max_pos_name_len = 0;
|
||||||
|
for (auto const &pos_id : positional_arguments) {
|
||||||
|
if (pos_id == -1)
|
||||||
|
continue;
|
||||||
|
auto name_it = reverse_positional_names.find(pos_id);
|
||||||
|
if (name_it != reverse_positional_names.end()) {
|
||||||
|
size_t display_len = name_it->second.length() + 2; // for < >
|
||||||
|
if (display_len > max_pos_name_len)
|
||||||
|
max_pos_name_len = display_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const &pos_id : positional_arguments) {
|
||||||
|
if (pos_id == -1)
|
||||||
|
continue;
|
||||||
|
auto name_it = reverse_positional_names.find(pos_id);
|
||||||
|
if (name_it == reverse_positional_names.end())
|
||||||
|
continue;
|
||||||
|
auto const &arg = argument_map.at(pos_id);
|
||||||
|
std::string display_name = "<" + name_it->second + ">";
|
||||||
|
ss << "\t" << std::left << std::setw(static_cast<int>(max_pos_name_len)) << display_name << "\t"
|
||||||
|
<< arg.get_help_text() << "\n";
|
||||||
}
|
}
|
||||||
ss << "\t" << line.desc << "\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
@@ -169,7 +235,7 @@ namespace argument_parser {
|
|||||||
bool base_parser::test_conventions(std::initializer_list<conventions::convention const *const> convention_types,
|
bool base_parser::test_conventions(std::initializer_list<conventions::convention const *const> convention_types,
|
||||||
std::unordered_map<std::string, std::string> &values_for_arguments,
|
std::unordered_map<std::string, std::string> &values_for_arguments,
|
||||||
std::vector<std::pair<std::string, argument>> &found_arguments,
|
std::vector<std::pair<std::string, argument>> &found_arguments,
|
||||||
std::optional<argument> &found_help, std::vector<std::string>::iterator it,
|
std::optional<argument> &found_help, std::vector<std::string>::iterator &it,
|
||||||
std::stringstream &error_stream) {
|
std::stringstream &error_stream) {
|
||||||
|
|
||||||
std::string current_argument = *it;
|
std::string current_argument = *it;
|
||||||
@@ -214,13 +280,43 @@ namespace argument_parser {
|
|||||||
std::vector<std::pair<std::string, argument>> &found_arguments,
|
std::vector<std::pair<std::string, argument>> &found_arguments,
|
||||||
std::optional<argument> &found_help) {
|
std::optional<argument> &found_help) {
|
||||||
|
|
||||||
|
size_t next_positional_index = 0;
|
||||||
|
bool force_positional = false;
|
||||||
|
|
||||||
for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) {
|
for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) {
|
||||||
|
if (*it == "--") {
|
||||||
|
force_positional = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force_positional) {
|
||||||
|
if (next_positional_index >= positional_arguments.size()) {
|
||||||
|
throw std::runtime_error("Unexpected positional argument: \"" + *it + "\"");
|
||||||
|
}
|
||||||
|
int arg_id = positional_arguments[next_positional_index];
|
||||||
|
argument &pos_arg = argument_map.at(arg_id);
|
||||||
|
std::string const &pos_name = reverse_positional_names.at(arg_id);
|
||||||
|
found_arguments.emplace_back(pos_name, pos_arg);
|
||||||
|
values_for_arguments[pos_name] = *it;
|
||||||
|
next_positional_index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
std::stringstream error_stream;
|
std::stringstream error_stream;
|
||||||
|
|
||||||
if (!test_conventions(convention_types, values_for_arguments, found_arguments, found_help, it,
|
if (!test_conventions(convention_types, values_for_arguments, found_arguments, found_help, it,
|
||||||
error_stream)) {
|
error_stream)) {
|
||||||
throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" +
|
if (next_positional_index < positional_arguments.size()) {
|
||||||
error_stream.str());
|
int arg_id = positional_arguments[next_positional_index];
|
||||||
|
argument &pos_arg = argument_map.at(arg_id);
|
||||||
|
std::string const &pos_name = reverse_positional_names.at(arg_id);
|
||||||
|
found_arguments.emplace_back(pos_name, pos_arg);
|
||||||
|
values_for_arguments[pos_name] = *it;
|
||||||
|
next_positional_index++;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" +
|
||||||
|
error_stream.str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,6 +350,7 @@ namespace argument_parser {
|
|||||||
value.action->invoke();
|
value.action->invoke();
|
||||||
}
|
}
|
||||||
value.set_invoked(true);
|
value.set_invoked(true);
|
||||||
|
argument_map.at(value.id).set_invoked(true);
|
||||||
} catch (const std::runtime_error &e) {
|
} catch (const std::runtime_error &e) {
|
||||||
std::string err{e.what()};
|
std::string err{e.what()};
|
||||||
err = replace_var(err, "KEY", "for " + key);
|
err = replace_var(err, "KEY", "for " + key);
|
||||||
@@ -295,6 +392,11 @@ namespace argument_parser {
|
|||||||
return long_pos->second;
|
return long_pos->second;
|
||||||
if (short_post != short_arguments.end())
|
if (short_post != short_arguments.end())
|
||||||
return short_post->second;
|
return short_post->second;
|
||||||
|
|
||||||
|
auto pos_it = positional_name_map.find(arg);
|
||||||
|
if (pos_it != positional_name_map.end())
|
||||||
|
return pos_it->second;
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,6 +424,36 @@ namespace argument_parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void base_parser::assert_positional_not_exist(std::string const &name) const {
|
||||||
|
if (positional_name_map.find(name) != positional_name_map.end()) {
|
||||||
|
throw std::runtime_error("Positional argument with name '" + name + "' already exists!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void base_parser::place_positional_argument(int id, argument const &arg, std::string const &name,
|
||||||
|
std::optional<int> position) {
|
||||||
|
argument_map[id] = arg;
|
||||||
|
positional_name_map[name] = id;
|
||||||
|
reverse_positional_names[id] = name;
|
||||||
|
|
||||||
|
if (position.has_value()) {
|
||||||
|
auto idx = static_cast<size_t>(position.value());
|
||||||
|
if (idx > positional_arguments.size()) {
|
||||||
|
positional_arguments.resize(idx + 1, -1);
|
||||||
|
}
|
||||||
|
if (idx < positional_arguments.size() && positional_arguments[idx] != -1) {
|
||||||
|
throw std::runtime_error("Position " + std::to_string(idx) + " is already occupied!");
|
||||||
|
}
|
||||||
|
if (idx == positional_arguments.size()) {
|
||||||
|
positional_arguments.push_back(id);
|
||||||
|
} else {
|
||||||
|
positional_arguments[idx] = id;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
positional_arguments.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string get_one_name(std::string const &short_name, std::string const &long_name) {
|
std::string get_one_name(std::string const &short_name, std::string const &long_name) {
|
||||||
std::string res{};
|
std::string res{};
|
||||||
if (short_name != "-") {
|
if (short_name != "-") {
|
||||||
@@ -340,43 +472,52 @@ namespace argument_parser {
|
|||||||
|
|
||||||
void base_parser::check_for_required_arguments(
|
void base_parser::check_for_required_arguments(
|
||||||
std::initializer_list<conventions::convention const *const> convention_types) {
|
std::initializer_list<conventions::convention const *const> convention_types) {
|
||||||
std::vector<std::tuple<std::string, std::string, bool>> required_args;
|
std::vector<std::tuple<std::string, std::string, bool, bool>> required_args;
|
||||||
for (auto const &[key, arg] : argument_map) {
|
for (auto const &[key, arg] : argument_map) {
|
||||||
if (arg.is_required() && !arg.is_invoked()) {
|
if (arg.is_required() && !arg.is_invoked()) {
|
||||||
auto short_arg = reverse_short_arguments.find(key) != reverse_short_arguments.end()
|
if (arg.is_positional()) {
|
||||||
? reverse_short_arguments.at(key)
|
auto pos_name = reverse_positional_names.find(key) != reverse_positional_names.end()
|
||||||
: "-";
|
? reverse_positional_names.at(key)
|
||||||
auto long_arg = reverse_long_arguments.find(key) != reverse_long_arguments.end()
|
: "unknown";
|
||||||
? reverse_long_arguments.at(key)
|
required_args.emplace_back(pos_name, "", true, true);
|
||||||
: "-";
|
} else {
|
||||||
|
auto short_arg = reverse_short_arguments.find(key) != reverse_short_arguments.end()
|
||||||
required_args.emplace_back<std::tuple<std::string, std::string, bool>>(
|
? reverse_short_arguments.at(key)
|
||||||
{short_arg, long_arg, arg.expects_parameter()});
|
: "-";
|
||||||
|
auto long_arg = reverse_long_arguments.find(key) != reverse_long_arguments.end()
|
||||||
|
? reverse_long_arguments.at(key)
|
||||||
|
: "-";
|
||||||
|
required_args.emplace_back(short_arg, long_arg, arg.expects_parameter(), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!required_args.empty()) {
|
if (!required_args.empty()) {
|
||||||
std::cerr << "These arguments were expected but not provided: \n";
|
std::cerr << "These arguments were expected but not provided: \n";
|
||||||
for (auto const &[s, l, p] : required_args) {
|
for (auto const &[s, l, p, is_pos] : required_args) {
|
||||||
std::cerr << "\t" << get_one_name(s, l) << ": must be provided as one of [";
|
if (is_pos) {
|
||||||
for (auto it = convention_types.begin(); it != convention_types.end(); ++it) {
|
std::cerr << "\t<" << s << ">: positional argument must be provided\n";
|
||||||
auto generatedParts = (*it)->make_help_text(s, l, p);
|
} else {
|
||||||
std::string help_str = generatedParts.first;
|
std::cerr << "\t" << get_one_name(s, l) << ": must be provided as one of [";
|
||||||
if (!generatedParts.first.empty() && !generatedParts.second.empty()) {
|
for (auto it = convention_types.begin(); it != convention_types.end(); ++it) {
|
||||||
help_str += " ";
|
auto generatedParts = (*it)->make_help_text(s, l, p);
|
||||||
}
|
std::string help_str = generatedParts.first;
|
||||||
help_str += generatedParts.second;
|
if (!generatedParts.first.empty() && !generatedParts.second.empty()) {
|
||||||
|
help_str += " ";
|
||||||
|
}
|
||||||
|
help_str += generatedParts.second;
|
||||||
|
|
||||||
size_t last_not_space = help_str.find_last_not_of(" \t");
|
size_t last_not_space = help_str.find_last_not_of(" \t");
|
||||||
if (last_not_space != std::string::npos) {
|
if (last_not_space != std::string::npos) {
|
||||||
help_str.erase(last_not_space + 1);
|
help_str.erase(last_not_space + 1);
|
||||||
}
|
}
|
||||||
std::cerr << help_str;
|
std::cerr << help_str;
|
||||||
if (it + 1 != convention_types.end()) {
|
if (it + 1 != convention_types.end()) {
|
||||||
std::cerr << ", ";
|
std::cerr << ", ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
std::cerr << "]\n";
|
||||||
}
|
}
|
||||||
std::cerr << "]\n";
|
|
||||||
}
|
}
|
||||||
std::cerr << "\n";
|
std::cerr << "\n";
|
||||||
display_help(convention_types);
|
display_help(convention_types);
|
||||||
|
|||||||
420
src/test.cpp
Normal file
420
src/test.cpp
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
#include <argparse>
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <exception>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
const std::initializer_list<argument_parser::conventions::convention const *const> conventions = {
|
||||||
|
&argument_parser::conventions::gnu_argument_convention,
|
||||||
|
&argument_parser::conventions::gnu_equal_argument_convention,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace v2_test {
|
||||||
|
class fake_parser : public argument_parser::v2::base_parser {
|
||||||
|
public:
|
||||||
|
fake_parser(std::string const &program_name, std::initializer_list<std::string> const &arguments) {
|
||||||
|
set_program_name(program_name);
|
||||||
|
ref_parsed_args() = std::vector<std::string>(arguments);
|
||||||
|
prepare_help_flag();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace v2_test
|
||||||
|
|
||||||
|
int tests_run = 0;
|
||||||
|
int tests_passed = 0;
|
||||||
|
|
||||||
|
void test_result(const char *name, bool passed) {
|
||||||
|
tests_run++;
|
||||||
|
if (passed) {
|
||||||
|
tests_passed++;
|
||||||
|
std::cout << " [PASS] " << name << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << " [FAIL] " << name << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// V1 Tests (using argument_parser::fake_parser)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void test_v1_single_positional_store() {
|
||||||
|
argument_parser::fake_parser parser("test", {"hello"});
|
||||||
|
parser.add_positional_argument<std::string>("greeting", "A greeting", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<std::string>("greeting");
|
||||||
|
test_result("v1: single positional store", val.has_value() && val.value() == "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_multiple_positionals_ordered() {
|
||||||
|
argument_parser::fake_parser parser("test", {"alpha", "beta", "gamma"});
|
||||||
|
parser.add_positional_argument<std::string>("first", "First arg", false);
|
||||||
|
parser.add_positional_argument<std::string>("second", "Second arg", false);
|
||||||
|
parser.add_positional_argument<std::string>("third", "Third arg", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto first = parser.get_optional<std::string>("first");
|
||||||
|
auto second = parser.get_optional<std::string>("second");
|
||||||
|
auto third = parser.get_optional<std::string>("third");
|
||||||
|
|
||||||
|
bool ok = first.has_value() && first.value() == "alpha" && second.has_value() && second.value() == "beta" &&
|
||||||
|
third.has_value() && third.value() == "gamma";
|
||||||
|
test_result("v1: multiple positionals preserve order", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_positional_with_explicit_position() {
|
||||||
|
argument_parser::fake_parser parser("test", {"first_val", "second_val"});
|
||||||
|
parser.add_positional_argument<std::string>("second", "Second", false, 1);
|
||||||
|
parser.add_positional_argument<std::string>("first", "First", false, 0);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto first = parser.get_optional<std::string>("first");
|
||||||
|
auto second = parser.get_optional<std::string>("second");
|
||||||
|
|
||||||
|
bool ok = first.has_value() && first.value() == "first_val" && second.has_value() && second.value() == "second_val";
|
||||||
|
test_result("v1: explicit position index", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_positional_typed_int() {
|
||||||
|
argument_parser::fake_parser parser("test", {"42"});
|
||||||
|
parser.add_positional_argument<int>("count", "A count", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<int>("count");
|
||||||
|
test_result("v1: positional with int type", val.has_value() && val.value() == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_positional_with_action() {
|
||||||
|
std::string captured;
|
||||||
|
argument_parser::fake_parser parser("test", {"world"});
|
||||||
|
|
||||||
|
auto action =
|
||||||
|
argument_parser::helpers::make_parametered_action<std::string>([&](std::string const &v) { captured = v; });
|
||||||
|
parser.add_positional_argument<std::string>("name", "A name", action, false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
test_result("v1: positional with action", captured == "world");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_mixed_named_and_positional() {
|
||||||
|
argument_parser::fake_parser parser("test", {"--verbose", "true", "myfile.txt"});
|
||||||
|
parser.add_argument<bool>("v", "verbose", "Verbose mode", false);
|
||||||
|
parser.add_positional_argument<std::string>("file", "Input file", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto verbose = parser.get_optional<bool>("verbose");
|
||||||
|
auto file = parser.get_optional<std::string>("file");
|
||||||
|
|
||||||
|
bool ok = verbose.has_value() && verbose.value() == true && file.has_value() && file.value() == "myfile.txt";
|
||||||
|
test_result("v1: mixed named and positional args", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_positional_after_named() {
|
||||||
|
argument_parser::fake_parser parser("test", {"-n", "5", "output.txt"});
|
||||||
|
parser.add_argument<int>("n", "number", "A number", false);
|
||||||
|
parser.add_positional_argument<std::string>("output", "Output file", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto number = parser.get_optional<int>("number");
|
||||||
|
auto output = parser.get_optional<std::string>("output");
|
||||||
|
|
||||||
|
bool ok = number.has_value() && number.value() == 5 && output.has_value() && output.value() == "output.txt";
|
||||||
|
test_result("v1: positional after named args", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_positional_between_named() {
|
||||||
|
argument_parser::fake_parser parser("test", {"-a", "1", "positional_val", "--beta", "2"});
|
||||||
|
parser.add_argument<int>("a", "alpha", "Alpha", false);
|
||||||
|
parser.add_argument<int>("b", "beta", "Beta", false);
|
||||||
|
parser.add_positional_argument<std::string>("middle", "Middle arg", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto alpha = parser.get_optional<int>("alpha");
|
||||||
|
auto beta = parser.get_optional<int>("beta");
|
||||||
|
auto middle = parser.get_optional<std::string>("middle");
|
||||||
|
|
||||||
|
bool ok = alpha.has_value() && alpha.value() == 1 && beta.has_value() && beta.value() == 2 && middle.has_value() &&
|
||||||
|
middle.value() == "positional_val";
|
||||||
|
test_result("v1: positional between named args", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_double_dash_separator() {
|
||||||
|
argument_parser::fake_parser parser("test", {"--", "-not-a-flag"});
|
||||||
|
parser.add_positional_argument<std::string>("item", "An item", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<std::string>("item");
|
||||||
|
test_result("v1: -- separator treats next as positional", val.has_value() && val.value() == "-not-a-flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_double_dash_multiple() {
|
||||||
|
argument_parser::fake_parser parser("test", {"--name", "hello", "--", "--weird", "-x"});
|
||||||
|
parser.add_argument<std::string>("n", "name", "A name", false);
|
||||||
|
parser.add_positional_argument<std::string>("first", "First", false);
|
||||||
|
parser.add_positional_argument<std::string>("second", "Second", false);
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto name = parser.get_optional<std::string>("name");
|
||||||
|
auto first = parser.get_optional<std::string>("first");
|
||||||
|
auto second = parser.get_optional<std::string>("second");
|
||||||
|
|
||||||
|
bool ok = name.has_value() && name.value() == "hello" && first.has_value() && first.value() == "--weird" &&
|
||||||
|
second.has_value() && second.value() == "-x";
|
||||||
|
test_result("v1: -- separator with multiple positionals", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_required_positional_missing() {
|
||||||
|
argument_parser::fake_parser parser("test", {});
|
||||||
|
parser.add_positional_argument<std::string>("file", "A file", true);
|
||||||
|
|
||||||
|
bool threw = false;
|
||||||
|
try {
|
||||||
|
// check_for_required_arguments calls std::exit(1) so we can't easily test it
|
||||||
|
// instead, test that handle_arguments doesn't crash when positionals are provided
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
} catch (...) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
// Note: required check calls std::exit(1), so if we get here the arg wasn't required-checked
|
||||||
|
// This test just verifies setup doesn't crash. The exit behavior is tested manually.
|
||||||
|
test_result("v1: required positional setup (no crash)", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_unexpected_positional_throws() {
|
||||||
|
argument_parser::fake_parser parser("test", {"unexpected"});
|
||||||
|
// no positional args defined, but a bare token is provided
|
||||||
|
|
||||||
|
bool threw = false;
|
||||||
|
try {
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
} catch (const std::runtime_error &) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
test_result("v1: unexpected positional throws", threw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_duplicate_positional_name_throws() {
|
||||||
|
argument_parser::fake_parser parser("test", {"a", "b"});
|
||||||
|
parser.add_positional_argument<std::string>("file", "A file", false);
|
||||||
|
|
||||||
|
bool threw = false;
|
||||||
|
try {
|
||||||
|
parser.add_positional_argument<std::string>("file", "Duplicate", false);
|
||||||
|
} catch (const std::runtime_error &) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
test_result("v1: duplicate positional name throws", threw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v1_positional_on_complete() {
|
||||||
|
std::string captured_file;
|
||||||
|
argument_parser::fake_parser parser("test", {"data.csv"});
|
||||||
|
parser.add_positional_argument<std::string>("file", "Input file", false);
|
||||||
|
parser.on_complete([&](argument_parser::base_parser const &p) {
|
||||||
|
auto val = p.get_optional<std::string>("file");
|
||||||
|
if (val)
|
||||||
|
captured_file = val.value();
|
||||||
|
});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
test_result("v1: positional accessible in on_complete", captured_file == "data.csv");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// V2 Tests (using v2_test::fake_parser)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void test_v2_single_positional() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"hello"});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({{Positional, "greeting"}, {HelpText, "A greeting"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<std::string>("greeting");
|
||||||
|
test_result("v2: single positional store", val.has_value() && val.value() == "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_positional_required() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"value"});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({{Positional, "arg"}, {Required, true}, {HelpText, "Required arg"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<std::string>("arg");
|
||||||
|
test_result("v2: required positional", val.has_value() && val.value() == "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_positional_with_position() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"first_val", "second_val"});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({{Positional, "second"}, {Position, 1}, {HelpText, "Second"}});
|
||||||
|
parser.add_argument<std::string>({{Positional, "first"}, {Position, 0}, {HelpText, "First"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto first = parser.get_optional<std::string>("first");
|
||||||
|
auto second = parser.get_optional<std::string>("second");
|
||||||
|
|
||||||
|
bool ok = first.has_value() && first.value() == "first_val" && second.has_value() && second.value() == "second_val";
|
||||||
|
test_result("v2: positional with explicit Position", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_positional_typed_int() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"99"});
|
||||||
|
|
||||||
|
parser.add_argument<int>({{Positional, "count"}, {HelpText, "A count"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<int>("count");
|
||||||
|
test_result("v2: positional with int type", val.has_value() && val.value() == 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_mixed_named_and_positional() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"--output", "out.txt", "input.txt"});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({{ShortArgument, "o"}, {LongArgument, "output"}, {HelpText, "Output file"}});
|
||||||
|
parser.add_argument<std::string>({{Positional, "input"}, {HelpText, "Input file"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto output = parser.get_optional<std::string>("output");
|
||||||
|
auto input = parser.get_optional<std::string>("input");
|
||||||
|
|
||||||
|
bool ok = output.has_value() && output.value() == "out.txt" && input.has_value() && input.value() == "input.txt";
|
||||||
|
test_result("v2: mixed named and positional", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_positional_with_action() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
std::string captured;
|
||||||
|
v2_test::fake_parser parser("test", {"world"});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({{Positional, "name"},
|
||||||
|
{Action, argument_parser::helpers::make_parametered_action<std::string>(
|
||||||
|
[&](std::string const &v) { captured = v; })},
|
||||||
|
{HelpText, "A name"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
test_result("v2: positional with action", captured == "world");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_double_dash_separator() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"--", "--not-a-flag"});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({{Positional, "item"}, {HelpText, "An item"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<std::string>("item");
|
||||||
|
test_result("v2: -- separator", val.has_value() && val.value() == "--not-a-flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_positional_auto_help_text() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"42"});
|
||||||
|
|
||||||
|
// no HelpText provided — should auto-generate from traits
|
||||||
|
parser.add_argument<int>({{Positional, "count"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto val = parser.get_optional<int>("count");
|
||||||
|
test_result("v2: positional auto help text (no crash)", val.has_value() && val.value() == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_multiple_positionals_and_named() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
v2_test::fake_parser parser("test", {"-v", "src.txt", "dst.txt"});
|
||||||
|
|
||||||
|
parser.add_argument({{ShortArgument, "v"}, {LongArgument, "verbose"}});
|
||||||
|
parser.add_argument<std::string>({{Positional, "source"}, {HelpText, "Source"}});
|
||||||
|
parser.add_argument<std::string>({{Positional, "destination"}, {HelpText, "Destination"}});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
auto verbose = parser.get_optional<bool>("verbose");
|
||||||
|
auto source = parser.get_optional<std::string>("source");
|
||||||
|
auto dest = parser.get_optional<std::string>("destination");
|
||||||
|
|
||||||
|
bool ok = verbose.has_value() && source.has_value() && source.value() == "src.txt" && dest.has_value() &&
|
||||||
|
dest.value() == "dst.txt";
|
||||||
|
test_result("v2: multiple positionals with named flag", ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_v2_on_complete_with_positional() {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
std::string captured;
|
||||||
|
v2_test::fake_parser parser("test", {"payload"});
|
||||||
|
|
||||||
|
parser.add_argument<std::string>({{Positional, "data"}, {HelpText, "Data"}});
|
||||||
|
parser.on_complete([&](argument_parser::base_parser const &p) {
|
||||||
|
auto val = p.get_optional<std::string>("data");
|
||||||
|
if (val)
|
||||||
|
captured = val.value();
|
||||||
|
});
|
||||||
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
|
test_result("v2: positional accessible in on_complete", captured == "payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "=== V1 Positional Argument Tests ===" << std::endl;
|
||||||
|
|
||||||
|
std::array<std::function<void()>, 13> v1Tests {
|
||||||
|
test_v1_single_positional_store,
|
||||||
|
test_v1_multiple_positionals_ordered,
|
||||||
|
test_v1_positional_with_explicit_position,
|
||||||
|
test_v1_positional_typed_int,
|
||||||
|
test_v1_positional_with_action,
|
||||||
|
test_v1_mixed_named_and_positional,
|
||||||
|
test_v1_positional_after_named,
|
||||||
|
test_v1_positional_between_named,
|
||||||
|
test_v1_double_dash_separator,
|
||||||
|
test_v1_double_dash_multiple,
|
||||||
|
test_v1_unexpected_positional_throws,
|
||||||
|
test_v1_duplicate_positional_name_throws,
|
||||||
|
test_v1_positional_on_complete
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const& test : v1Tests) {
|
||||||
|
try {
|
||||||
|
test();
|
||||||
|
} catch(std::exception const& e) {
|
||||||
|
std::cout << "test failed: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n=== V2 Positional Argument Tests ===" << std::endl;
|
||||||
|
std::array<std::function<void()>, 10> v2Tests{
|
||||||
|
test_v2_single_positional,
|
||||||
|
test_v2_positional_required,
|
||||||
|
test_v2_positional_with_position,
|
||||||
|
test_v2_positional_typed_int,
|
||||||
|
test_v2_mixed_named_and_positional,
|
||||||
|
test_v2_positional_with_action,
|
||||||
|
test_v2_double_dash_separator,
|
||||||
|
test_v2_positional_auto_help_text,
|
||||||
|
test_v2_multiple_positionals_and_named,
|
||||||
|
test_v2_on_complete_with_positional
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
for (auto const& test : v2Tests) {
|
||||||
|
try {
|
||||||
|
test();
|
||||||
|
} catch(std::exception const& e) {
|
||||||
|
std::cout << "test failed: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n=== Results: " << tests_passed << "/" << tests_run << " passed ===" << std::endl;
|
||||||
|
return (tests_passed == tests_run) ? 0 : 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user