Merge pull request #1 from sametersoylu/v2

PoC: better argument building.
This commit is contained in:
Abdüssamet ERSOYLU
2026-01-14 17:08:52 +04:00
committed by GitHub
7 changed files with 392 additions and 92 deletions

10
TODO.md
View File

@@ -38,3 +38,13 @@ Configure CMAKE to ensure the library can be built as a library and can be insta
# TODO 5: Display help # TODO 5: Display help
Display help doesn't reflect the conventions right now. Also it should come automatically, and should be allowed to overriden by user. Display help doesn't reflect the conventions right now. Also it should come automatically, and should be allowed to overriden by user.
# TODO 6: Accumulate repeated calls
Add support to letting users accumulate repeated calls to a flag. If the flag is called x times, the result should be x items stored in a vector,
instead of an action doing it.
# TODO 7: Defaults/Implicits
If given, an arguments default store value could be changed. If nothing was given use that value instead.
# TODO 8: Validators
If given, validate the argument before passing to the storage or action. If fail, let user decide fail loud or fail skip.

View File

@@ -360,8 +360,6 @@ namespace argument_parser {
friend class macos_parser; friend class macos_parser;
friend class fake_parser; friend class fake_parser;
}; };
} }
#endif // ARGUMENT_PARSER_HPP #endif // ARGUMENT_PARSER_HPP

View File

@@ -10,8 +10,10 @@
namespace argument_parser { namespace argument_parser {
class fake_parser : public base_parser { class fake_parser : public base_parser {
public: public:
fake_parser(std::string const& program_name, std::vector<std::string> const& arguments) { fake_parser() = default;
this->program_name = program_name;
fake_parser(std::string program_name, std::vector<std::string> const& arguments) {
this->program_name = std::move(program_name);
parsed_arguments = arguments; parsed_arguments = arguments;
} }
@@ -22,6 +24,14 @@ namespace argument_parser {
fake_parser(std::string const& program_name, std::initializer_list<std::string> const& arguments) : fake_parser(std::string const& program_name, std::initializer_list<std::string> const& arguments) :
fake_parser(program_name, std::vector<std::string>(arguments)) {} fake_parser(program_name, std::vector<std::string>(arguments)) {}
void set_program_name(std::string const& program_name) {
this->program_name = program_name;
}
void set_parsed_arguments(std::vector<std::string> const& parsed_arguments) {
this->parsed_arguments = parsed_arguments;
}
}; };
} }

View File

@@ -0,0 +1,249 @@
#pragma once
#include <argument_parser.hpp>
#include <cstdlib>
#include <exception>
#include <fake_parser.hpp>
#include <initializer_list>
#include <iostream>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>
namespace argument_parser::v2 {
namespace internal {
static inline fake_parser fake_parser{};
}
enum class add_argument_flags {
ShortArgument,
LongArgument,
HelpText,
Action,
Required
};
namespace flags {
constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument;
constexpr static inline add_argument_flags LongArgument = add_argument_flags::LongArgument;
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 Required = add_argument_flags::Required;
}
class base_parser : private argument_parser::base_parser {
public:
template<typename T>
using typed_flag_value = std::variant<std::string, parametered_action<T>, bool>;
using non_typed_flag_value = std::variant<std::string, non_parametered_action, bool>;
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>;
template<typename T>
void add_argument(std::unordered_map<add_argument_flags, typed_flag_value<T>> const& argument_pairs) {
std::unordered_map<extended_add_argument_flags, bool> found_params {{
extended_add_argument_flags::IsTyped, true
}};
std::string short_arg, long_arg, help_text;
std::unique_ptr<action_base> action;
bool required = false;
if (argument_pairs.contains(add_argument_flags::ShortArgument)) {
found_params[extended_add_argument_flags::ShortArgument] = true;
short_arg = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::ShortArgument), "short");
}
if (argument_pairs.contains(add_argument_flags::LongArgument)) {
found_params[extended_add_argument_flags::LongArgument] = true;
long_arg = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::LongArgument), "long");
if (short_arg.empty()) short_arg = long_arg;
} else {
if (!short_arg.empty()) long_arg = short_arg;
}
if (argument_pairs.contains(add_argument_flags::Action)) {
found_params[extended_add_argument_flags::Action] = true;
action = get_or_throw<parametered_action<T>>(argument_pairs.at(add_argument_flags::Action), "action").clone();
}
if (argument_pairs.contains(add_argument_flags::HelpText)) {
help_text = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::HelpText), "help");
} else {
help_text = short_arg + ", " + long_arg;
}
if (argument_pairs.contains(add_argument_flags::Required) && get_or_throw<bool>(argument_pairs.at(add_argument_flags::Required), "required")) {
required = true;
}
auto suggested_add = suggest_candidate(found_params);
if (suggested_add == candidate_type::unknown) {
throw std::runtime_error("Could not match any add argument overload to given parameters. Are you missing some required parameter?");
}
switch (suggested_add) {
case candidate_type::typed_action:
base::add_argument(short_arg, long_arg, help_text, *static_cast<parametered_action<T>*>(&(*action)), required);
break;
case candidate_type::store_other:
base::add_argument(short_arg, long_arg, help_text, required);
break;
default:
throw std::runtime_error("Could not match the arguments against any overload.");
}
}
template<typename T>
void add_argument(std::initializer_list<typed_argument_pair<T>> const& pairs) {
std::unordered_map<add_argument_flags, typed_flag_value<T>> args;
for (auto& [k, v]: pairs) {
args[k] = v;
}
add_argument<T>(args);
}
void add_argument(std::initializer_list<non_typed_argument_pair> const& pairs) {
std::unordered_map<add_argument_flags, non_typed_flag_value> args;
for (auto& [k, v] : pairs) {
args[k] = v;
}
add_argument(args);
}
void add_argument(std::unordered_map<add_argument_flags, non_typed_flag_value> const& argument_pairs) {
std::unordered_map<extended_add_argument_flags, bool> found_params {{
extended_add_argument_flags::IsTyped, false
}};
std::string short_arg, long_arg, help_text;
std::unique_ptr<action_base> action;
bool required = false;
if (argument_pairs.contains(add_argument_flags::ShortArgument)) {
found_params[extended_add_argument_flags::ShortArgument] = true;
short_arg = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::ShortArgument), "short");
}
if (argument_pairs.contains(add_argument_flags::LongArgument)) {
found_params[extended_add_argument_flags::LongArgument] = true;
long_arg = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::LongArgument), "long");
if (short_arg.empty()) short_arg = long_arg;
} else {
if (!short_arg.empty()) long_arg = short_arg;
}
if (argument_pairs.contains(add_argument_flags::Action)) {
found_params[extended_add_argument_flags::Action] = true;
action = get_or_throw<non_parametered_action>(argument_pairs.at(add_argument_flags::Action), "action").clone();
}
if (argument_pairs.contains(add_argument_flags::HelpText)) {
help_text = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::HelpText), "help");
} else {
help_text = short_arg + ", " + long_arg;
}
if (argument_pairs.contains(add_argument_flags::Required) && get_or_throw<bool>(argument_pairs.at(add_argument_flags::Required), "required")) {
required = true;
}
auto suggested_add = suggest_candidate(found_params);
if (suggested_add == candidate_type::unknown) {
throw std::runtime_error("Could not match any add argument overload to given parameters. Are you missing some required parameter?");
}
switch (suggested_add) {
case candidate_type::non_typed_action:
base::add_argument(short_arg, long_arg, help_text, *static_cast<non_parametered_action*>(&(*action)), required);
break;
case candidate_type::store_boolean:
base::add_argument(short_arg, long_arg, help_text, required);
break;
default:
throw std::runtime_error("Could not match the arguments against any overload. The suggested candidate was: " + std::to_string((int(suggested_add))));
}
}
argument_parser::base_parser& to_v1() {
return *this;
}
void handle_arguments(std::initializer_list<conventions::convention const* const> convention_types) {
base::handle_arguments(convention_types);
}
template<typename T>
std::optional<T> get_optional(std::string const& arg) {
return base::get_optional<T>(arg);
}
void on_complete(std::function<void(argument_parser::base_parser const&)> const& action) {
base::on_complete(action);
}
protected:
void set_program_name(std::string p) {
base::program_name = std::move(p);
}
std::vector<std::string>& ref_parsed_args() {
return base::parsed_arguments;
}
private:
using base = argument_parser::base_parser;
enum class extended_add_argument_flags {
ShortArgument,
LongArgument,
Action,
IsTyped
};
enum class candidate_type {
store_boolean,
store_other,
typed_action,
non_typed_action,
unknown
};
template<typename T, size_t S>
bool satisfies_at_least_one(std::array<T, S> const& arr, std::unordered_map<T, bool> const& map) {
for (const auto& req: arr) {
if (map.contains(req)) return true;
}
return false;
}
candidate_type suggest_candidate(std::unordered_map<extended_add_argument_flags, bool> const& available_vars) {
auto constexpr required_at_least_one = std::array<extended_add_argument_flags, 2> { extended_add_argument_flags::ShortArgument, extended_add_argument_flags::LongArgument };
if (!satisfies_at_least_one(required_at_least_one, available_vars)) return candidate_type::unknown;
if (available_vars.contains(extended_add_argument_flags::Action)) {
if (available_vars.at(extended_add_argument_flags::IsTyped))
return candidate_type::typed_action;
else return candidate_type::non_typed_action;
}
if (available_vars.at(extended_add_argument_flags::IsTyped)) return candidate_type::store_other;
return candidate_type::store_boolean;
}
template<typename T, typename I>
T get_or_throw(typed_flag_value<I> const& v, std::string_view key) {
if (auto p = std::get_if<T>(&v)) return *p;
throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key));
}
template<typename T>
T get_or_throw(non_typed_flag_value const& v, std::string_view key) {
if (auto p = std::get_if<T>(&v)) return *p;
throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key));
}
};
}

View File

@@ -5,6 +5,7 @@
#include <argument_parser.hpp> #include <argument_parser.hpp>
#include <crt_externs.h> #include <crt_externs.h>
#include <parser_v2.hpp>
#include <string> #include <string>
namespace argument_parser { namespace argument_parser {
@@ -13,13 +14,28 @@ namespace argument_parser {
macos_parser() { macos_parser() {
const int argc = *_NSGetArgc(); const int argc = *_NSGetArgc();
if (char **argv = *_NSGetArgv(); argc > 0 && argv != nullptr && argv[0] != nullptr) { if (char **argv = *_NSGetArgv(); argc > 0 && argv != nullptr && argv[0] != nullptr) {
program_name = argv[0]; program_name = (argv[0]);
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
if (argv[i] != nullptr) parsed_arguments.emplace_back(argv[i]); if (argv[i] != nullptr) parsed_arguments.emplace_back(argv[i]);
} }
} }
} }
}; };
namespace v2 {
class macos_parser : public v2::base_parser {
public:
macos_parser() {
const int argc = *_NSGetArgc();
if (char **argv = *_NSGetArgv(); argc > 0 && argv != nullptr && argv[0] != nullptr) {
set_program_name(argv[0]);
for (int i = 1; i < argc; ++i) {
if (argv[i] != nullptr) ref_parsed_args().emplace_back(argv[i]);
}
}
}
};
}
} }
#endif #endif

View File

@@ -1,98 +1,61 @@
#pragma once #pragma once
#ifdef _WIN32 #ifdef _WIN32
#ifndef WINDOWS_PARSER_HPP
// compiler bitches if you don't use lean and mean and remove shit ton of other macros that comes preloaded with the windows api.
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
// I'm speaking to you Microsoft, STOP MAKING EVERYTHING A FUCKING MACRO. IT BREAKES CPP!
#define NOGDI
#define NOHELP
#define NOMCX
#define NOIME
#define NOCOMM
#define NOKANJI
#define NOSERVICE
#define NOMDI
#define NOSOUND
// also this fixes error about no architecture being targeted (somehow)
#if defined(_M_AMD64) && !defined(_AMD64_)
# define _AMD64_
#endif
#if defined(_M_IX86) && !defined(_X86_)
# define _X86_
#endif
#if defined(_M_ARM64) && !defined(_ARM64_)
# define _ARM64_
#endif
#include <argument_parser.hpp> #include <argument_parser.hpp>
#include <fstream> #include <memory>
#include <string> #include <string>
// THIS HAS TO BE THE FIRST. DON'T CHANGE THEIR ORDER.
#include <windows.h> #include <windows.h>
#include <processenv.h>
#include <shellapi.h> #include <shellapi.h>
namespace argument_parser { namespace argument_parser {
class windows_parser : public base_parser {
public:
windows_parser() {
int nArgs;
LPWSTR commandLineA = GetCommandLineW();
auto argvW = std::unique_ptr<LPWSTR, local_free_deleter> { CommandLineToArgvW(commandLineA, &nArgs) };
if (argvW == nullptr) { class windows_parser : public base_parser {
throw std::runtime_error("Failed to get command line arguments."); public:
windows_parser() {
int nArgs = 0;
LPWSTR* raw = ::CommandLineToArgvW(::GetCommandLineW(), &nArgs);
if (!raw) {
throw std::runtime_error("CommandLineToArgvW failed (" +
std::to_string(::GetLastError()) + ")");
} }
std::unique_ptr<LPWSTR, void(*)(LPWSTR*)> argvW{ raw, [](LPWSTR* p){ if (p) ::LocalFree(p); } };
if (nArgs <= 0) { if (nArgs <= 0) {
throw std::runtime_error("No command line arguments found, including program name."); throw std::runtime_error("No command line arguments found.");
} }
program_name = utf8_from_wstring(argvW.get()[0]); {
parsed_arguments.reserve(static_cast<size_t>(nArgs - 1)); std::wstring w0 = argvW.get()[0];
auto pos = w0.find_last_of(L"\\/");
std::wstring base = (pos == std::wstring::npos) ? w0 : w0.substr(pos + 1);
program_name = utf8_from_wstring(base);
}
parsed_arguments.reserve(static_cast<size_t>(nArgs > 0 ? nArgs - 1 : 0));
for (int i = 1; i < nArgs; ++i) { for (int i = 1; i < nArgs; ++i) {
parsed_arguments.emplace_back(utf8_from_wstring(argvW.get()[i])); parsed_arguments.emplace_back(utf8_from_wstring(argvW.get()[i]));
} }
} }
private: private:
struct local_free_deleter {
void operator()(HLOCAL ptr) const noexcept {
if (ptr != nullptr) ::LocalFree(ptr);
}
};
static std::string utf8_from_wstring(const std::wstring& w) { static std::string utf8_from_wstring(const std::wstring& w) {
if (w.empty()) return {}; if (w.empty()) return {};
int needed = ::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr); int needed = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
if (needed == 0) { w.c_str(), -1, nullptr, 0, nullptr, nullptr);
throw std::runtime_error("WideCharToMultiByte sizing failed (" if (needed <= 0) {
+ std::to_string(GetLastError()) + ")"); throw std::runtime_error("WideCharToMultiByte sizing failed (" +
std::to_string(::GetLastError()) + ")");
} }
std::string out; std::string out;
out.resize(static_cast<size_t>(needed - 1)); out.resize(static_cast<size_t>(needed - 1));
int written = ::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, int written = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
out.data(), needed, nullptr, nullptr); w.c_str(), -1, out.data(), needed - 1, nullptr, nullptr);
if (written == 0) { if (written <= 0) {
throw std::runtime_error("WideCharToMultiByte convert failed (" throw std::runtime_error("WideCharToMultiByte convert failed (" +
+ std::to_string(GetLastError()) + ")"); std::to_string(::GetLastError()) + ")");
} }
return out; return out;
} }
}; };
using parser = windows_parser;
} }
#endif
#endif #endif

View File

@@ -1,7 +1,10 @@
#include "macos_parser.hpp"
#include <string>
#define ALLOW_DASH_FOR_WINDOWS 0 #define ALLOW_DASH_FOR_WINDOWS 0
#include <parser_v2.hpp>
#include <argparse> #include <argparse>
#include <cstdlib> #include <expected>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <regex> #include <regex>
@@ -135,7 +138,9 @@ void run_grep(argument_parser::base_parser const& parser) {
} }
} }
int main() {
int v1Examples() {
auto parser = argument_parser::parser{}; auto parser = argument_parser::parser{};
parser.add_argument("e", "echo", "echoes given variable", echo, false); parser.add_argument("e", "echo", "echoes given variable", echo, false);
@@ -174,6 +179,55 @@ int main() {
std::cout << item << std::endl; std::cout << item << std::endl;
} }
} }
return 0; return 0;
} }
int v2Examples() {
using namespace argument_parser::v2::flags;
argument_parser::v2::macos_parser parser;
parser.add_argument<std::string>({
{ ShortArgument, "e" },
{ LongArgument, "echo" },
{ Action, echo }
});
parser.add_argument<Point>({
{ ShortArgument, "ep" },
{ LongArgument, "echo-point" },
{ Action, echo_point }
});
parser.add_argument<std::string>({ // stores string for f/file flag
{ ShortArgument, "f" },
{ LongArgument, "file"},
// if no action, falls to store operation with given type.
});
parser.add_argument<std::regex>({ // stores string for g/grep flag
{ ShortArgument, "g" },
{ LongArgument, "grep" },
// same as 'file' flag
});
parser.add_argument<std::string>({
{ ShortArgument, "c" },
{ LongArgument, "cat" },
{ Action, cat }
});
parser.add_argument<Point>({
// { ShortArgument, "sp" }, // now if ShortArgument or LongArgument is missing, it will use it for the other.
{ LongArgument, "store-point" },
{ Required, true } // makes this flag required
});
parser.on_complete(::run_grep);
parser.handle_arguments(conventions);
return 0;
}
int main() {
return v2Examples();
}