From c6023cd642a8ada8a7f2ad4e562ab3ea756414f5 Mon Sep 17 00:00:00 2001 From: /nick haya <74699483+The-SGPT@users.noreply.github.com> Date: Fri, 11 Feb 2022 10:10:15 +0800 Subject: [PATCH] Make arg parser work actually --- CMakeLists.txt | 6 +- src/Main.cpp | 98 ++++++++++- src/argh.h | 434 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 531 insertions(+), 7 deletions(-) create mode 100644 src/argh.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 23c5728..455d556 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,10 @@ set(OUT3_INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/src/toml/") # Someone make a pull request to support MingW # Way to lazy to do that right now if (${CMAKE_SIZEOF_VOID_P} MATCHES 8) - set(MAIN_LIBRARIES "${CMAKE_CURRENT_LIST_DIR}/lib/x64/soloud_static.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x64/SDL2.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x64/SDL2main.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x64/SDL2_ttf.lib") + set(MAIN_LIBRARIES "${CMAKE_CURRENT_LIST_DIR}/lib/x64/soloud_static.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x64/SDL2.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x64/SDL2_ttf.lib") set(MAIN_LIBRARIES_E "${CMAKE_CURRENT_LIST_DIR}/lib/x64/") else () - set(MAIN_LIBRARIES "${CMAKE_CURRENT_LIST_DIR}/lib/x86/soloud_static.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x86/SDL2.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x86/SDL2main.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x86/SDL2_ttf.lib") + set(MAIN_LIBRARIES "${CMAKE_CURRENT_LIST_DIR}/lib/x86/soloud_static.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x86/SDL2.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x86/SDL2_ttf.lib") set(MAIN_LIBRARIES_E "${CMAKE_CURRENT_LIST_DIR}/lib/x86/") endif () @@ -85,7 +85,7 @@ FILE(GLOB SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp" set(CMAKE_CXX_STANDARD 17) # edit executable properties part 1 -add_executable(Skateboard ${SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources/res.rc") +add_executable(Skateboard WIN32 ${SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources/res.rc") # LINK EVERYTHING target_link_libraries(Skateboard ${MAIN_LIBRARIES}) diff --git a/src/Main.cpp b/src/Main.cpp index 971058b..1600613 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -8,17 +8,20 @@ * @copyright Copyright (c) 2022 * */ -#define SDL_MAIN_HANDLED // needs to be here so that SDL doesnt hijack our main function #define SDL_STBIMAGE_IMPLEMENTATION // to use sdl_stbimage #undef _HAS_STD_BYTE // avoid std::byte hijacking native byte type #include #include "SDL2/SDL.h" +#undef main #include "Render.hpp" #include #include +#include +#include "argh.h" using namespace std; using namespace Render; +using namespace argh; class MainState : public State { AnimatedObject objs; @@ -141,14 +144,101 @@ class SplashScreen : public State { SplashScreen m; -int main() { +int main(int argc, char* argv[]) +{ if (!Init("SDfmL :))))))")){ - return ERROR_CODE; + return EXIT_FAILURE; } + parser pa(argc, argv); + if (pa[{"-v", "--verbose"}]) + log("", "Verbose mode enabled.", NORMAL, __FILENAME__, __LINE__); + SwitchState(&m); if (!Update()) { - return NO_ERROR_CODE; + return EXIT_SUCCESS; } + return EXIT_SUCCESS; +} + +// This is the main entry point of a program. Currently all this does is parse arguments and call main(). +int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) +{ + int argc; + char** argv; + + char* arg; + int index; + int result; + + // count the arguments + + argc = 1; + arg = cmdline; + + while (arg[0] != 0) { + + while (arg[0] != 0 && arg[0] == ' ') { + arg++; + } + + if (arg[0] != 0) { + + argc++; + + while (arg[0] != 0 && arg[0] != ' ') { + arg++; + } + + } + + } + + // tokenize the arguments + + argv = (char**)malloc(argc * sizeof(char*)); + + arg = cmdline; + index = 1; + + while (arg[0] != 0) { + + while (arg[0] != 0 && arg[0] == ' ') { + arg++; + } + + if (arg[0] != 0) { + + argv[index] = arg; + index++; + + while (arg[0] != 0 && arg[0] != ' ') { + arg++; + } + + if (arg[0] != 0) { + arg[0] = 0; + arg++; + } + + } + + } + + // put the program name into argv[0] + + char filename[_MAX_PATH]; + + GetModuleFileName(NULL, filename, _MAX_PATH); + argv[0] = filename; + + // call the user specified main function + + log("", to_string(argc)); + + result = main(argc, argv); + + free(argv); + return result; } \ No newline at end of file diff --git a/src/argh.h b/src/argh.h new file mode 100644 index 0000000..07b76e9 --- /dev/null +++ b/src/argh.h @@ -0,0 +1,434 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace argh +{ + // Terminology: + // A command line is composed of 2 types of args: + // 1. Positional args, i.e. free standing values + // 2. Options: args beginning with '-'. We identify two kinds: + // 2.1: Flags: boolean options => (exist ? true : false) + // 2.2: Parameters: a name followed by a non-option value + +#if !defined(__GNUC__) || (__GNUC__ >= 5) + using string_stream = std::istringstream; +#else + // Until GCC 5, istringstream did not have a move constructor. + // stringstream_proxy is used instead, as a workaround. + class stringstream_proxy + { + public: + stringstream_proxy() = default; + + // Construct with a value. + stringstream_proxy(std::string const& value) : + stream_(value) + {} + + // Copy constructor. + stringstream_proxy(const stringstream_proxy& other) : + stream_(other.stream_.str()) + { + stream_.setstate(other.stream_.rdstate()); + } + + void setstate(std::ios_base::iostate state) { stream_.setstate(state); } + + // Stream out the value of the parameter. + // If the conversion was not possible, the stream will enter the fail state, + // and operator bool will return false. + template + stringstream_proxy& operator >> (T& thing) + { + stream_ >> thing; + return *this; + } + + + // Get the string value. + std::string str() const { return stream_.str(); } + + std::stringbuf* rdbuf() const { return stream_.rdbuf(); } + + // Check the state of the stream. + // False when the most recent stream operation failed + explicit operator bool() const { return !!stream_; } + + ~stringstream_proxy() = default; + private: + std::istringstream stream_; + }; + using string_stream = stringstream_proxy; +#endif + + class parser + { + public: + enum Mode { PREFER_FLAG_FOR_UNREG_OPTION = 1 << 0, + PREFER_PARAM_FOR_UNREG_OPTION = 1 << 1, + NO_SPLIT_ON_EQUALSIGN = 1 << 2, + SINGLE_DASH_IS_MULTIFLAG = 1 << 3, + }; + + parser() = default; + + parser(std::initializer_list pre_reg_names) + { add_params(pre_reg_names); } + + parser(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION) + { parse(argv, mode); } + + parser(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION) + { parse(argc, argv, mode); } + + void add_param(std::string const& name); + void add_params(std::initializer_list init_list); + + void parse(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION); + void parse(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION); + + std::multiset const& flags() const { return flags_; } + std::map const& params() const { return params_; } + std::vector const& pos_args() const { return pos_args_; } + + // begin() and end() for using range-for over positional args. + std::vector::const_iterator begin() const { return pos_args_.cbegin(); } + std::vector::const_iterator end() const { return pos_args_.cend(); } + size_t size() const { return pos_args_.size(); } + + ////////////////////////////////////////////////////////////////////////// + // Accessors + + // flag (boolean) accessors: return true if the flag appeared, otherwise false. + bool operator[](std::string const& name) const; + + // multiple flag (boolean) accessors: return true if at least one of the flag appeared, otherwise false. + bool operator[](std::initializer_list init_list) const; + + // returns positional arg string by order. Like argv[] but without the options + std::string const& operator[](size_t ind) const; + + // returns a std::istream that can be used to convert a positional arg to a typed value. + string_stream operator()(size_t ind) const; + + // same as above, but with a default value in case the arg is missing (index out of range). + template + string_stream operator()(size_t ind, T&& def_val) const; + + // parameter accessors, give a name get an std::istream that can be used to convert to a typed value. + // call .str() on result to get as string + string_stream operator()(std::string const& name) const; + + // accessor for a parameter with multiple names, give a list of names, get an std::istream that can be used to convert to a typed value. + // call .str() on result to get as string + // returns the first value in the list to be found. + string_stream operator()(std::initializer_list init_list) const; + + // same as above, but with a default value in case the param was missing. + // Non-string def_val types must have an operator<<() (output stream operator) + // If T only has an input stream operator, pass the string version of the type as in "3" instead of 3. + template + string_stream operator()(std::string const& name, T&& def_val) const; + + // same as above but for a list of names. returns the first value to be found. + template + string_stream operator()(std::initializer_list init_list, T&& def_val) const; + + private: + string_stream bad_stream() const; + std::string trim_leading_dashes(std::string const& name) const; + bool is_number(std::string const& arg) const; + bool is_option(std::string const& arg) const; + bool got_flag(std::string const& name) const; + bool is_param(std::string const& name) const; + + private: + std::vector args_; + std::map params_; + std::vector pos_args_; + std::multiset flags_; + std::set registeredParams_; + std::string empty_; + }; + + + ////////////////////////////////////////////////////////////////////////// + + inline void parser::parse(const char * const argv[], int mode) + { + int argc = 0; + for (auto argvp = argv; *argvp; ++argc, ++argvp); + parse(argc, argv, mode); + } + + ////////////////////////////////////////////////////////////////////////// + + inline void parser::parse(int argc, const char* const argv[], int mode /*= PREFER_FLAG_FOR_UNREG_OPTION*/) + { + // convert to strings + args_.resize(static_cast(argc)); + std::transform(argv, argv + argc, args_.begin(), [](const char* const arg) { return arg; }); + + // parse line + for (auto i = 0u; i < args_.size(); ++i) + { + if (!is_option(args_[i])) + { + pos_args_.emplace_back(args_[i]); + continue; + } + + auto name = trim_leading_dashes(args_[i]); + + if (!(mode & NO_SPLIT_ON_EQUALSIGN)) + { + auto equalPos = name.find('='); + if (equalPos != std::string::npos) + { + params_.insert({ name.substr(0, equalPos), name.substr(equalPos + 1) }); + continue; + } + } + + // if the option is unregistered and should be a multi-flag + if (1 == (args_[i].size() - name.size()) && // single dash + argh::parser::SINGLE_DASH_IS_MULTIFLAG & mode && // multi-flag mode + !is_param(name)) // unregistered + { + std::string keep_param; + + if (!name.empty() && is_param(std::string(1ul, name.back()))) // last char is param + { + keep_param += name.back(); + name.resize(name.size() - 1); + } + + for (auto const& c : name) + { + flags_.emplace(std::string{ c }); + } + + if (!keep_param.empty()) + { + name = keep_param; + } + else + { + continue; // do not consider other options for this arg + } + } + + // any potential option will get as its value the next arg, unless that arg is an option too + // in that case it will be determined a flag. + if (i == args_.size() - 1 || is_option(args_[i + 1])) + { + flags_.emplace(name); + continue; + } + + // if 'name' is a pre-registered option, then the next arg cannot be a free parameter to it is skipped + // otherwise we have 2 modes: + // PREFER_FLAG_FOR_UNREG_OPTION: a non-registered 'name' is determined a flag. + // The following value (the next arg) will be a free parameter. + // + // PREFER_PARAM_FOR_UNREG_OPTION: a non-registered 'name' is determined a parameter, the next arg + // will be the value of that option. + + assert(!(mode & argh::parser::PREFER_FLAG_FOR_UNREG_OPTION) + || !(mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION)); + + bool preferParam = mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION; + + if (is_param(name) || preferParam) + { + params_.insert({ name, args_[i + 1] }); + ++i; // skip next value, it is not a free parameter + continue; + } + else + { + flags_.emplace(name); + } + } + } + + ////////////////////////////////////////////////////////////////////////// + + inline string_stream parser::bad_stream() const + { + string_stream bad; + bad.setstate(std::ios_base::failbit); + return bad; + } + + ////////////////////////////////////////////////////////////////////////// + + inline bool parser::is_number(std::string const& arg) const + { + // inefficient but simple way to determine if a string is a number (which can start with a '-') + std::istringstream istr(arg); + double number; + istr >> number; + return !(istr.fail() || istr.bad()); + } + + ////////////////////////////////////////////////////////////////////////// + + inline bool parser::is_option(std::string const& arg) const + { + assert(0 != arg.size()); + if (is_number(arg)) + return false; + return '-' == arg[0]; + } + + ////////////////////////////////////////////////////////////////////////// + + inline std::string parser::trim_leading_dashes(std::string const& name) const + { + auto pos = name.find_first_not_of('-'); + return std::string::npos != pos ? name.substr(pos) : name; + } + + ////////////////////////////////////////////////////////////////////////// + + inline bool argh::parser::got_flag(std::string const& name) const + { + return flags_.end() != flags_.find(trim_leading_dashes(name)); + } + + ////////////////////////////////////////////////////////////////////////// + + inline bool argh::parser::is_param(std::string const& name) const + { + return registeredParams_.count(name); + } + + ////////////////////////////////////////////////////////////////////////// + + inline bool parser::operator[](std::string const& name) const + { + return got_flag(name); + } + + ////////////////////////////////////////////////////////////////////////// + + inline bool parser::operator[](std::initializer_list init_list) const + { + return std::any_of(init_list.begin(), init_list.end(), [&](char const* const name) { return got_flag(name); }); + } + + ////////////////////////////////////////////////////////////////////////// + + inline std::string const& parser::operator[](size_t ind) const + { + if (ind < pos_args_.size()) + return pos_args_[ind]; + return empty_; + } + + ////////////////////////////////////////////////////////////////////////// + + inline string_stream parser::operator()(std::string const& name) const + { + auto optIt = params_.find(trim_leading_dashes(name)); + if (params_.end() != optIt) + return string_stream(optIt->second); + return bad_stream(); + } + + ////////////////////////////////////////////////////////////////////////// + + inline string_stream parser::operator()(std::initializer_list init_list) const + { + for (auto& name : init_list) + { + auto optIt = params_.find(trim_leading_dashes(name)); + if (params_.end() != optIt) + return string_stream(optIt->second); + } + return bad_stream(); + } + + ////////////////////////////////////////////////////////////////////////// + + template + string_stream parser::operator()(std::string const& name, T&& def_val) const + { + auto optIt = params_.find(trim_leading_dashes(name)); + if (params_.end() != optIt) + return string_stream(optIt->second); + + std::ostringstream ostr; + ostr.precision(std::numeric_limits::max_digits10); + ostr << def_val; + return string_stream(ostr.str()); // use default + } + + ////////////////////////////////////////////////////////////////////////// + + // same as above but for a list of names. returns the first value to be found. + template + string_stream parser::operator()(std::initializer_list init_list, T&& def_val) const + { + for (auto& name : init_list) + { + auto optIt = params_.find(trim_leading_dashes(name)); + if (params_.end() != optIt) + return string_stream(optIt->second); + } + std::ostringstream ostr; + ostr.precision(std::numeric_limits::max_digits10); + ostr << def_val; + return string_stream(ostr.str()); // use default + } + + ////////////////////////////////////////////////////////////////////////// + + inline string_stream parser::operator()(size_t ind) const + { + if (pos_args_.size() <= ind) + return bad_stream(); + + return string_stream(pos_args_[ind]); + } + + ////////////////////////////////////////////////////////////////////////// + + template + string_stream parser::operator()(size_t ind, T&& def_val) const + { + if (pos_args_.size() <= ind) + { + std::ostringstream ostr; + ostr.precision(std::numeric_limits::max_digits10); + ostr << def_val; + return string_stream(ostr.str()); + } + + return string_stream(pos_args_[ind]); + } + + ////////////////////////////////////////////////////////////////////////// + + inline void parser::add_param(std::string const& name) + { + registeredParams_.insert(trim_leading_dashes(name)); + } + + ////////////////////////////////////////////////////////////////////////// + + inline void parser::add_params(std::initializer_list init_list) + { + for (auto& name : init_list) + registeredParams_.insert(trim_leading_dashes(name)); + } +} \ No newline at end of file