diff --git a/include/blt/parse/argparse.h b/include/blt/parse/argparse.h index 7bb2f48..a2cad3a 100644 --- a/include/blt/parse/argparse.h +++ b/include/blt/parse/argparse.h @@ -158,6 +158,7 @@ namespace blt::parser { std::string a_default{}; std::string a_dest{}; std::string a_help{}; + std::string a_version{}; std::string a_metavar{}; bool a_required = false; }; @@ -208,7 +209,7 @@ namespace blt::parser { class argparse { public: - typedef std::variant> arg_data_t; + typedef std::variant> arg_data_t; private: struct { friend argparse; @@ -230,11 +231,12 @@ namespace blt::parser { HASHMAP flag_args; } loaded_args; private: + static std::string filename(const std::string& path); static bool validateArgument(const arg_properties_t& args); - bool consumeArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, std::vector& v); - bool consumeFlagArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, arg_data_t& arg_data); + static bool consumeArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, std::vector& v); void handlePositionalArgument(arg_tokenizer_t& arg_tokenizer, size_t& last_pos); void handleFlagArgument(arg_tokenizer_t& arg_tokenizer); + void processFlag(arg_tokenizer_t& arg_tokenizer, const std::string& flag); public: argparse() = default; diff --git a/include/blt/std/string.h b/include/blt/std/string.h index e2e1542..ae4a56f 100755 --- a/include/blt/std/string.h +++ b/include/blt/std/string.h @@ -140,6 +140,26 @@ namespace blt::string { return tokens; } + // https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string + bool replace(std::string& str, const std::string& from, const std::string& to) { + size_t start_pos = str.find(from); + if(start_pos == std::string::npos) + return false; + str.replace(start_pos, from.length(), to); + return true; + } + + void replaceAll(std::string& str, const std::string& from, const std::string& to) { + if(from.empty()) + return; + size_t start_pos = 0; + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + } + } + + // taken from https://stackoverflow.com/questions/216823/how-to-trim-an-stdstring // would've preferred to use boost lib but instructions said to avoid external libs // trim from start (in place) diff --git a/src/blt/parse/argparse.cpp b/src/blt/parse/argparse.cpp index 7bcc9c6..cf5a7d0 100644 --- a/src/blt/parse/argparse.cpp +++ b/src/blt/parse/argparse.cpp @@ -5,6 +5,7 @@ */ #include #include "blt/std/logging.h" +#include namespace blt::parser { @@ -169,9 +170,26 @@ namespace blt::parser { // token is a flag, find out special information about it auto flag = arg_tokenizer.next(); + if (flag.starts_with("--")) + processFlag(arg_tokenizer, flag); + else { + // handle special args like -vvv + if (!flag.starts_with('-')) + BLT_TRACE("Flag processed but does not start with '-'!"); + // size without - + auto len = flag.size()-1; + // get flag type + std::string str = "- "; + str[1] = flag[1]; + for (size_t i = 0; i < len; i++) + processFlag(arg_tokenizer, str); + } + } + + void argparse::processFlag(arg_tokenizer_t& arg_tokenizer, const std::string& flag) { auto loc = user_args.flag_associations.find(flag); if (loc == user_args.flag_associations.end()){ - BLT_WARN("Flag '%s' not an option!"); + BLT_WARN("Flag '%s' not an option!", flag.c_str()); printHelp(); return; } @@ -179,13 +197,21 @@ namespace blt::parser { auto flag_properties = loc->second; auto dest = flag_properties->a_dest.empty() ? flag_properties->a_flags : arg_vector_t{flag_properties->a_dest}; - arg_data_t data; + arg_data_t& data = loaded_args.flag_args[dest]; switch(flag_properties->a_action){ case arg_action_t::HELP: printHelp(); break; - case arg_action_t::STORE: + case arg_action_t::STORE: { + std::vector v; + if (!consumeArguments(arg_tokenizer, *flag_properties, v)) + return; + if (v.size() == 1) + data = v[0]; + else + data = v; break; + } case arg_action_t::STORE_CONST: data = flag_properties->a_const; break; @@ -195,35 +221,36 @@ namespace blt::parser { case arg_action_t::STORE_TRUE: data = true; break; - case arg_action_t::APPEND: - if (holds_alternative>(data)){ - auto& l = get>(data); - - } else - BLT_WARN("Requested append to data which doesn't contain a list!"); + case arg_action_t::COUNT: { + if (!std::holds_alternative(data)) + data = 0; + data = std::get(data) + 1; break; - case arg_action_t::APPEND_CONST: + } + case arg_action_t::EXTEND: { break; - case arg_action_t::COUNT: + } + case arg_action_t::VERSION: { + auto file = filename(loaded_args.program_name); + BLT_INFO("%s, %s", file.c_str(), flag_properties->a_version.c_str()); break; - case arg_action_t::EXTEND: + } + case arg_action_t::APPEND_CONST: { + if (!holds_alternative>(data)) { + data = std::vector(); + } + auto& l = get>(data); + l.emplace_back(flag_properties->a_const); break; - case arg_action_t::VERSION: + } + case arg_action_t::APPEND: { + if (!holds_alternative>(data)) + data = std::vector(); + auto& l = get>(data); + consumeArguments(arg_tokenizer, *flag_properties, l); break; + } } - loaded_args.flag_args[dest] = data; - } - - bool argparse::consumeFlagArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, arg_data_t& arg_data) { - // since the function is called after the flag is consumed, isFlag()/ will return information about the next arg - if (properties.a_nargs.flags) { - - } else { - if (arg_tokenizer.isFlag()) - return false; - - } - return false; } bool argparse::consumeArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, std::vector& v) { @@ -231,7 +258,7 @@ namespace blt::parser { case 0: for (int i = 0; i < properties.a_nargs.args; i++) { if (arg_tokenizer.isFlag()) { - BLT_WARN("Expected %d arguments, got flag instead!", properties.a_nargs.args); + BLT_WARN("Expected %d arguments, found flag instead!", properties.a_nargs.args); return false; } v.emplace_back(arg_tokenizer.next()); @@ -247,13 +274,28 @@ namespace blt::parser { v.emplace_back(arg_tokenizer.next()); return true; case arg_nargs_t::ALL: - - break; + while (arg_tokenizer.hasNext() && !arg_tokenizer.isFlag()) + v.emplace_back(arg_tokenizer.next()); + return true; case arg_nargs_t::ALL_REQUIRED: - break; + if (arg_tokenizer.isFlag()) { + BLT_WARN("At least one argument is required!"); + return false; + } + while (arg_tokenizer.hasNext() && !arg_tokenizer.isFlag()) + v.emplace_back(arg_tokenizer.next()); + return true; } return false; } + std::string argparse::filename(const std::string& path) { + auto paths = blt::string::split(path, "/"); + auto final = paths[paths.size()-1]; + if (final == "/") + return paths[paths.size()-2]; + return final; + } + } \ No newline at end of file