diff --git a/TODO.md b/TODO.md index 13843d0..2f5ca6a 100644 --- a/TODO.md +++ b/TODO.md @@ -37,4 +37,14 @@ Right now whole project is header only. Header only so far is fine but to avoid Configure CMAKE to ensure the library can be built as a library and can be installed via CMAKE. This should make adoptation of this to existing projects easier with less headache. # 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. \ No newline at end of file +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. diff --git a/include/parser/argument_parser.hpp b/include/parser/argument_parser.hpp index 98e9c83..c78972a 100644 --- a/include/parser/argument_parser.hpp +++ b/include/parser/argument_parser.hpp @@ -359,9 +359,7 @@ namespace argument_parser { friend class windows_parser; friend class macos_parser; friend class fake_parser; - }; - - + }; } #endif // ARGUMENT_PARSER_HPP \ No newline at end of file diff --git a/include/parser/fake_parser.hpp b/include/parser/fake_parser.hpp index 24bc45b..cba9de2 100644 --- a/include/parser/fake_parser.hpp +++ b/include/parser/fake_parser.hpp @@ -10,8 +10,10 @@ namespace argument_parser { class fake_parser : public base_parser { public: - fake_parser(std::string const& program_name, std::vector const& arguments) { - this->program_name = program_name; + fake_parser() = default; + + fake_parser(std::string program_name, std::vector const& arguments) { + this->program_name = std::move(program_name); parsed_arguments = arguments; } @@ -22,6 +24,14 @@ namespace argument_parser { fake_parser(std::string const& program_name, std::initializer_list const& arguments) : fake_parser(program_name, std::vector(arguments)) {} + + void set_program_name(std::string const& program_name) { + this->program_name = program_name; + } + + void set_parsed_arguments(std::vector const& parsed_arguments) { + this->parsed_arguments = parsed_arguments; + } }; } diff --git a/include/parser/parser_v2.hpp b/include/parser/parser_v2.hpp new file mode 100644 index 0000000..c0d7b6a --- /dev/null +++ b/include/parser/parser_v2.hpp @@ -0,0 +1,249 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + using typed_flag_value = std::variant, bool>; + using non_typed_flag_value = std::variant; + + template + using typed_argument_pair = std::pair>; + using non_typed_argument_pair = std::pair; + + template + void add_argument(std::unordered_map> const& argument_pairs) { + std::unordered_map found_params {{ + extended_add_argument_flags::IsTyped, true + }}; + + std::string short_arg, long_arg, help_text; + std::unique_ptr 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(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(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>(argument_pairs.at(add_argument_flags::Action), "action").clone(); + } + if (argument_pairs.contains(add_argument_flags::HelpText)) { + help_text = get_or_throw(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(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*>(&(*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 + void add_argument(std::initializer_list> const& pairs) { + std::unordered_map> args; + + for (auto& [k, v]: pairs) { + args[k] = v; + } + + add_argument(args); + } + + void add_argument(std::initializer_list const& pairs) { + std::unordered_map args; + + for (auto& [k, v] : pairs) { + args[k] = v; + } + + add_argument(args); + } + + void add_argument(std::unordered_map const& argument_pairs) { + std::unordered_map found_params {{ + extended_add_argument_flags::IsTyped, false + }}; + + std::string short_arg, long_arg, help_text; + std::unique_ptr 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(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(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(argument_pairs.at(add_argument_flags::Action), "action").clone(); + + } + if (argument_pairs.contains(add_argument_flags::HelpText)) { + help_text = get_or_throw(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(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(&(*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 convention_types) { + base::handle_arguments(convention_types); + } + + template + std::optional get_optional(std::string const& arg) { + return base::get_optional(arg); + } + + void on_complete(std::function const& action) { + base::on_complete(action); + } + + protected: + void set_program_name(std::string p) { + base::program_name = std::move(p); + } + + std::vector& 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 + bool satisfies_at_least_one(std::array const& arr, std::unordered_map const& map) { + for (const auto& req: arr) { + if (map.contains(req)) return true; + } + return false; + } + + candidate_type suggest_candidate(std::unordered_map const& available_vars) { + auto constexpr required_at_least_one = std::array { 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 + T get_or_throw(typed_flag_value const& v, std::string_view key) { + if (auto p = std::get_if(&v)) return *p; + throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); + } + + template + T get_or_throw(non_typed_flag_value const& v, std::string_view key) { + if (auto p = std::get_if(&v)) return *p; + throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); + } + }; +} \ No newline at end of file diff --git a/include/parser/platform_headers/macos_parser.hpp b/include/parser/platform_headers/macos_parser.hpp index 72bf98f..da55102 100644 --- a/include/parser/platform_headers/macos_parser.hpp +++ b/include/parser/platform_headers/macos_parser.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace argument_parser { @@ -13,13 +14,28 @@ namespace argument_parser { macos_parser() { const int argc = *_NSGetArgc(); 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) { 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 diff --git a/include/parser/platform_headers/windows_parser.hpp b/include/parser/platform_headers/windows_parser.hpp index 8691285..12524f2 100644 --- a/include/parser/platform_headers/windows_parser.hpp +++ b/include/parser/platform_headers/windows_parser.hpp @@ -1,98 +1,61 @@ #pragma once #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 -#include +#include #include - -// THIS HAS TO BE THE FIRST. DON'T CHANGE THEIR ORDER. #include -#include #include namespace argument_parser { - class windows_parser : public base_parser { - public: - windows_parser() { - int nArgs; - LPWSTR commandLineA = GetCommandLineW(); - auto argvW = std::unique_ptr { CommandLineToArgvW(commandLineA, &nArgs) }; - if (argvW == nullptr) { - throw std::runtime_error("Failed to get command line arguments."); - } +class windows_parser : public base_parser { +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 argvW{ raw, [](LPWSTR* p){ if (p) ::LocalFree(p); } }; - if (nArgs <= 0) { - throw std::runtime_error("No command line arguments found, including program name."); - } - - program_name = utf8_from_wstring(argvW.get()[0]); - parsed_arguments.reserve(static_cast(nArgs - 1)); - for (int i = 1; i < nArgs; ++i) { - parsed_arguments.emplace_back(utf8_from_wstring(argvW.get()[i])); - } + if (nArgs <= 0) { + throw std::runtime_error("No command line arguments found."); } - 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) { - if (w.empty()) return {}; - int needed = ::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr); - if (needed == 0) { - throw std::runtime_error("WideCharToMultiByte sizing failed (" - + std::to_string(GetLastError()) + ")"); - } - - std::string out; - out.resize(static_cast(needed - 1)); - int written = ::WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, - out.data(), needed, nullptr, nullptr); - if (written == 0) { - throw std::runtime_error("WideCharToMultiByte convert failed (" - + std::to_string(GetLastError()) + ")"); - } - return out; + { + 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); } - }; - using parser = windows_parser; + parsed_arguments.reserve(static_cast(nArgs > 0 ? nArgs - 1 : 0)); + for (int i = 1; i < nArgs; ++i) { + parsed_arguments.emplace_back(utf8_from_wstring(argvW.get()[i])); + } + } + +private: + static std::string utf8_from_wstring(const std::wstring& w) { + if (w.empty()) return {}; + int needed = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + w.c_str(), -1, nullptr, 0, nullptr, nullptr); + if (needed <= 0) { + throw std::runtime_error("WideCharToMultiByte sizing failed (" + + std::to_string(::GetLastError()) + ")"); + } + std::string out; + out.resize(static_cast(needed - 1)); + int written = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + w.c_str(), -1, out.data(), needed - 1, nullptr, nullptr); + if (written <= 0) { + throw std::runtime_error("WideCharToMultiByte convert failed (" + + std::to_string(::GetLastError()) + ")"); + } + return out; + } +}; + } - -#endif #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 07d21ae..e4465b5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,10 @@ +#include "macos_parser.hpp" +#include #define ALLOW_DASH_FOR_WINDOWS 0 +#include #include -#include +#include #include #include #include @@ -135,7 +138,9 @@ void run_grep(argument_parser::base_parser const& parser) { } } -int main() { + +int v1Examples() { + auto parser = argument_parser::parser{}; parser.add_argument("e", "echo", "echoes given variable", echo, false); @@ -174,6 +179,55 @@ int main() { std::cout << item << std::endl; } } - return 0; +} + +int v2Examples() { + using namespace argument_parser::v2::flags; + argument_parser::v2::macos_parser parser; + + parser.add_argument({ + { ShortArgument, "e" }, + { LongArgument, "echo" }, + { Action, echo } + }); + + parser.add_argument({ + { ShortArgument, "ep" }, + { LongArgument, "echo-point" }, + { Action, echo_point } + }); + + parser.add_argument({ // stores string for f/file flag + { ShortArgument, "f" }, + { LongArgument, "file"}, + // if no action, falls to store operation with given type. + }); + + parser.add_argument({ // stores string for g/grep flag + { ShortArgument, "g" }, + { LongArgument, "grep" }, + // same as 'file' flag + }); + + parser.add_argument({ + { ShortArgument, "c" }, + { LongArgument, "cat" }, + { Action, cat } + }); + + parser.add_argument({ + // { 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(); } \ No newline at end of file