From d284fc655cf5fb9af3e306ac1fcbcb13c01430a5 Mon Sep 17 00:00:00 2001 From: Marcel Schneider Date: Thu, 21 Dec 2017 12:56:53 +0100 Subject: [PATCH 1/9] calling exit(int) does not play well with RAII valgrind shows memory as still reachable --- cmdparser.hpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cmdparser.hpp b/cmdparser.hpp index 65f1427..3a8f1e7 100644 --- a/cmdparser.hpp +++ b/cmdparser.hpp @@ -12,6 +12,9 @@ #include namespace cli { + class NormalExitFromCallback : std::exception{ + + }; /// Class used to wrap integer types to specify desired numerical base for specific argument parsing template class NumericalBase { public: @@ -106,7 +109,11 @@ namespace cli { CallbackArgs args { arguments, output, error }; value = callback(args); return true; - } catch (...) { + } + catch(NormalExitFromCallback const&){ + return true; + } + catch (...) { return false; } } @@ -130,7 +137,11 @@ namespace cli { try { value = Parser::parse(arguments, value); return true; - } catch (...) { + } + catch(NormalExitFromCallback const&){ + return true; + } + catch (...) { return false; } } @@ -296,7 +307,7 @@ namespace cli { void enable_help() { set_callback("h", "help", std::function([this](CallbackArgs& args){ args.output << this->usage(); - exit(0); + throw NormalExitFromCallback(); return false; }), "", true); } @@ -338,7 +349,7 @@ namespace cli { inline void run_and_exit_if_error() { if (run() == false) { - exit(1); + throw std::runtime_error(""); } } From 33fffb61c947a03c0cf9e1cd3b82bbcd65648464 Mon Sep 17 00:00:00 2001 From: Marcel Schneider Date: Thu, 21 Dec 2017 13:47:09 +0100 Subject: [PATCH 2/9] fixed TEST_CASE( "Parse help", "[help]" ) in this test value is expected to be false, but should this return true? --- cmdparser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdparser.hpp b/cmdparser.hpp index 3a8f1e7..c313ed4 100644 --- a/cmdparser.hpp +++ b/cmdparser.hpp @@ -111,7 +111,7 @@ namespace cli { return true; } catch(NormalExitFromCallback const&){ - return true; + return false;// should this return "true"? } catch (...) { return false; From 42320bf6587f8ba88e384ef0991358728238d506 Mon Sep 17 00:00:00 2001 From: Marcel Schneider Date: Thu, 21 Dec 2017 13:34:42 +0100 Subject: [PATCH 3/9] delete resource before erasing --- cmdparser.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cmdparser.hpp b/cmdparser.hpp index c313ed4..a2065f2 100644 --- a/cmdparser.hpp +++ b/cmdparser.hpp @@ -315,6 +315,7 @@ namespace cli { void disable_help() { for (auto command = _commands.begin(); command != _commands.end(); ++command) { if ((*command)->name == "h" && (*command)->alternative == "--help") { + delete(*command); _commands.erase(command); break; } From a0a3a99c66204cecebbc9890c80a950a662b15b5 Mon Sep 17 00:00:00 2001 From: Marcel Schneider Date: Thu, 21 Dec 2017 14:30:24 +0100 Subject: [PATCH 4/9] Replace raw pointer with std::unique_ptr now manual resource management anymore --- cmdparser.hpp | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/cmdparser.hpp b/cmdparser.hpp index a2065f2..1326201 100644 --- a/cmdparser.hpp +++ b/cmdparser.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace cli { @@ -288,14 +289,8 @@ namespace cli { enable_help(); } - ~Parser() { - for (int i = 0, n = _commands.size(); i < n; ++i) { - delete _commands[i]; - } - } - bool has_help() const { - for (const auto command : _commands) { + for (const auto &command : _commands) { if (command->name == "h" && command->alternative == "--help") { return true; } @@ -315,7 +310,6 @@ namespace cli { void disable_help() { for (auto command = _commands.begin(); command != _commands.end(); ++command) { if ((*command)->name == "h" && (*command)->alternative == "--help") { - delete(*command); _commands.erase(command); break; } @@ -330,22 +324,21 @@ namespace cli { template void set_required(const std::string& name, const std::string& alternative, const std::string& description = "", bool dominant = false) { - auto command = new CmdArgument { name, alternative, description, true, dominant }; - _commands.push_back(command); + _commands.emplace_back(new CmdArgument { name, alternative, description, true, dominant }); } template void set_optional(const std::string& name, const std::string& alternative, T defaultValue, const std::string& description = "", bool dominant = false) { auto command = new CmdArgument { name, alternative, description, false, dominant }; command->value = defaultValue; - _commands.push_back(command); + _commands.emplace_back(command); } template void set_callback(const std::string& name, const std::string& alternative, std::function callback, const std::string& description = "", bool dominant = false) { auto command = new CmdFunction { name, alternative, description, false, dominant }; command->callback = callback; - _commands.push_back(command); + _commands.emplace_back(command); } inline void run_and_exit_if_error() { @@ -392,25 +385,25 @@ namespace cli { // First, parse dominant arguments since they succeed even if required // arguments are missing. - for (auto command : _commands) { + for (auto &command : _commands) { if (command->handled && command->dominant && !command->parse(output, error)) { - error << howto_use(command); + error << howto_use(command.get()); return false; } } // Next, check for any missing arguments. - for (auto command : _commands) { + for (auto &command : _commands) { if (command->required && !command->handled) { - error << howto_required(command); + error << howto_required(command.get()); return false; } } // Finally, parse all remaining arguments. - for (auto command : _commands) { + for (auto &command : _commands) { if (command->handled && !command->dominant && !command->parse(output, error)) { - error << howto_use(command); + error << howto_use(command.get()); return false; } } @@ -422,7 +415,7 @@ namespace cli { T get(const std::string& name) const { for (const auto& command : _commands) { if (command->name == name) { - auto cmd = dynamic_cast*>(command); + auto cmd = dynamic_cast*>(command.get()); if (cmd == nullptr) { throw std::runtime_error("Invalid usage of the parameter " + name + " detected."); @@ -463,9 +456,9 @@ namespace cli { protected: CmdBase* find(const std::string& name) { - for (auto command : _commands) { + for (auto &command : _commands) { if (command->is(name)) { - return command; + return command.get(); } } @@ -473,9 +466,9 @@ namespace cli { } CmdBase* find_default() { - for (auto command : _commands) { + for (auto &command : _commands) { if (command->name == "") { - return command; + return command.get(); } } @@ -538,6 +531,6 @@ namespace cli { private: const std::string _appname; std::vector _arguments; - std::vector _commands; + std::vector> _commands; }; } From 09ea54b036354b15dc041d73210bce6aa04108d1 Mon Sep 17 00:00:00 2001 From: Marcel Schneider Date: Thu, 21 Dec 2017 19:22:11 +0100 Subject: [PATCH 5/9] added target sample for make this helped me figuring out that Catch itself includes and the tests compiled without including memory in CmdParser.hpp to compile this, type make sample now cmdparser.hpp is the first include in tests --- cmdparser.Test/CMakeLists.txt | 1 + cmdparser.Test/sample.cpp | 15 +++++++++++++++ cmdparser.Test/tests.cpp | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 cmdparser.Test/sample.cpp diff --git a/cmdparser.Test/CMakeLists.txt b/cmdparser.Test/CMakeLists.txt index 9dee61c..a7a815a 100644 --- a/cmdparser.Test/CMakeLists.txt +++ b/cmdparser.Test/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCE_FILES TestMain.cpp catch.hpp tests.cpp) add_executable(cmdparserTest ${SOURCE_FILES}) +add_executable(sample sample.cpp) IF(APPLE) TARGET_COMPILE_OPTIONS(cmdparserTest PUBLIC INTERFACE "-stdlib=libc++") ENDIF(APPLE) \ No newline at end of file diff --git a/cmdparser.Test/sample.cpp b/cmdparser.Test/sample.cpp new file mode 100644 index 0000000..00eebb8 --- /dev/null +++ b/cmdparser.Test/sample.cpp @@ -0,0 +1,15 @@ +// +// Created by marcel on 12/21/17. +// + +#include +#include "../cmdparser.hpp" + +int main(int argc, char**argv) +{ + cli::Parser parser(argc, argv); + parser.disable_help(); + const auto value = parser.run(std::cout, std::cerr); + + return 0; +} diff --git a/cmdparser.Test/tests.cpp b/cmdparser.Test/tests.cpp index 5a44fb7..f1cbd09 100644 --- a/cmdparser.Test/tests.cpp +++ b/cmdparser.Test/tests.cpp @@ -3,9 +3,9 @@ Copyright (c) 2015 - 2016 Florian Rappl */ -#include "catch.hpp" #include #include "../cmdparser.hpp" +#include "catch.hpp" using namespace cli; From f8b185b7bf786e45c0670945d18aec2092bcb239 Mon Sep 17 00:00:00 2001 From: haschke-felix Date: Sat, 12 Oct 2024 12:12:42 +0000 Subject: [PATCH 6/9] Solution for the exit() problem: Policy class --- cmdparser.hpp | 52 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/cmdparser.hpp b/cmdparser.hpp index 1326201..db49724 100644 --- a/cmdparser.hpp +++ b/cmdparser.hpp @@ -13,10 +13,7 @@ #include namespace cli { - class NormalExitFromCallback : std::exception{ - - }; - /// Class used to wrap integer types to specify desired numerical base for specific argument parsing + /// Class used to wrap integer types to specify desired numerical base for specific argument parsing template class NumericalBase { public: @@ -43,7 +40,10 @@ namespace cli { std::ostream& output; std::ostream& error; }; - class Parser { + + + template + class ParserWithPolicy { private: class CmdBase { public: @@ -107,14 +107,10 @@ namespace cli { virtual bool parse(std::ostream& output, std::ostream& error) { try { - CallbackArgs args { arguments, output, error }; + CallbackArgs args { CmdBase::arguments, output, error }; value = callback(args); return true; - } - catch(NormalExitFromCallback const&){ - return false;// should this return "true"? - } - catch (...) { + } catch (...) { return false; } } @@ -136,13 +132,9 @@ namespace cli { virtual bool parse(std::ostream&, std::ostream&) { try { - value = Parser::parse(arguments, value); + value = ParserWithPolicy::parse(CmdBase::arguments, value); return true; - } - catch(NormalExitFromCallback const&){ - return true; - } - catch (...) { + } catch (...) { return false; } } @@ -273,7 +265,7 @@ namespace cli { } public: - explicit Parser(int argc, const char** argv) : + explicit ParserWithPolicy(int argc, const char** argv) : _appname(argv[0]) { for (int i = 1; i < argc; ++i) { _arguments.push_back(argv[i]); @@ -281,7 +273,7 @@ namespace cli { enable_help(); } - explicit Parser(int argc, char** argv) : + explicit ParserWithPolicy(int argc, char** argv) : _appname(argv[0]) { for (int i = 1; i < argc; ++i) { _arguments.push_back(argv[i]); @@ -302,7 +294,7 @@ namespace cli { void enable_help() { set_callback("h", "help", std::function([this](CallbackArgs& args){ args.output << this->usage(); - throw NormalExitFromCallback(); + HelpExitPolicy::Exit(); return false; }), "", true); } @@ -343,7 +335,7 @@ namespace cli { inline void run_and_exit_if_error() { if (run() == false) { - throw std::runtime_error(""); + exit(1); } } @@ -533,4 +525,22 @@ namespace cli { std::vector _arguments; std::vector> _commands; }; + + struct SystemExit + { + public: + static void Exit(){ + exit(0); + } + }; + + struct NoExit + { + public: + static void Exit(){ + + } + }; + + using Parser = ParserWithPolicy; } From 3a23b24cc7949b1e7ba3266ca3a1c88d64669826 Mon Sep 17 00:00:00 2001 From: haschke-felix Date: Sat, 12 Oct 2024 12:17:10 +0000 Subject: [PATCH 7/9] cleanup RAII usign make_unique --- cmdparser.hpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cmdparser.hpp b/cmdparser.hpp index db49724..80219ed 100644 --- a/cmdparser.hpp +++ b/cmdparser.hpp @@ -13,7 +13,7 @@ #include namespace cli { - /// Class used to wrap integer types to specify desired numerical base for specific argument parsing + /// Class used to wrap integer types to specify desired numerical base for specific argument parsing template class NumericalBase { public: @@ -310,25 +310,26 @@ namespace cli { template void set_default(bool is_required, const std::string& description = "") { - auto command = new CmdArgument { "", "", description, is_required, false }; - _commands.push_back(command); + auto command = std::make_unique>("", "", description, is_required, false); + _commands.emplace_back(command); } template void set_required(const std::string& name, const std::string& alternative, const std::string& description = "", bool dominant = false) { - _commands.emplace_back(new CmdArgument { name, alternative, description, true, dominant }); + auto command = std::make_unique>(name, alternative, description, false, dominant); + _commands.emplace_back(command); } template void set_optional(const std::string& name, const std::string& alternative, T defaultValue, const std::string& description = "", bool dominant = false) { - auto command = new CmdArgument { name, alternative, description, false, dominant }; + auto command = std::make_unique>(name, alternative, description, false, dominant); command->value = defaultValue; _commands.emplace_back(command); } template void set_callback(const std::string& name, const std::string& alternative, std::function callback, const std::string& description = "", bool dominant = false) { - auto command = new CmdFunction { name, alternative, description, false, dominant }; + auto command = std::make_unique>(name, alternative, description, false, dominant); command->callback = callback; _commands.emplace_back(command); } @@ -351,9 +352,9 @@ namespace cli { if (_arguments.size() > 0) { auto current = find_default(); - for (int i = 0, n = _arguments.size(); i < n; ++i) { - auto isarg = _arguments[i].size() > 0 && _arguments[i][0] == '-'; - auto associated = isarg ? find(_arguments[i]) : nullptr; + for (string const& argument : _arguments) { + auto isarg = argument.size() > 0 && argument[0] == '-'; + auto associated = isarg ? find(argument) : nullptr; if (associated != nullptr) { current = associated; @@ -362,7 +363,7 @@ namespace cli { error << no_default(); return false; } else { - current->arguments.push_back(_arguments[i]); + current->arguments.emplace_back(argument); current->handled = true; if (!current->variadic) { From 18f868a82cf522bed5c4a31f06932d25f31cc530 Mon Sep 17 00:00:00 2001 From: haschke-felix Date: Sat, 12 Oct 2024 12:55:48 +0000 Subject: [PATCH 8/9] use shared pointer --- cmdparser.hpp | 1229 ++++++++++++++++++++++++------------------------- 1 file changed, 610 insertions(+), 619 deletions(-) diff --git a/cmdparser.hpp b/cmdparser.hpp index 6ad4cc3..ad88920 100644 --- a/cmdparser.hpp +++ b/cmdparser.hpp @@ -13,636 +13,627 @@ #include namespace cli { - /// Class used to wrap integer types to specify desired numerical base for specific argument parsing - template class NumericalBase { - public: - - /// This constructor required for correct AgrumentCountChecker initialization - NumericalBase() : value(0), base(numericalBase) {} - - /// This constructor required for default value initialization - /// \param val comes from default value - NumericalBase(T val) : value(val), base(numericalBase) {} - - operator T () const { - return this->value; - } - operator T * () { - return this->value; - } - - T value; - unsigned int base; - }; - - struct CallbackArgs { - const std::vector& arguments; - std::ostream& output; - std::ostream& error; - }; - - - template - class ParserWithPolicy { - private: - class CmdBase { - public: - explicit CmdBase(const std::string& name, const std::string& alternative, const std::string& description, bool required, bool dominant, bool variadic) : - name(name), - command(name.size() > 0 ? "-" + name : ""), - alternative(alternative.size() > 0 ? "--" + alternative : ""), - description(description), - required(required), - handled(false), - arguments({}), - dominant(dominant), - variadic(variadic) { - } - - virtual ~CmdBase() { - } - - std::string name; - std::string command; - std::string alternative; - std::string description; - bool required; - bool handled; - std::vector arguments; - bool const dominant; - bool const variadic; - - virtual std::string print_value() const = 0; - virtual bool parse(std::ostream& output, std::ostream& error) = 0; - - bool is(const std::string& given) const { - return given == command || given == alternative; - } - }; - - template - struct ArgumentCountChecker - { - static constexpr bool Variadic = false; - }; - - template - struct ArgumentCountChecker> - { - static constexpr bool Variadic = false; - }; - - template - struct ArgumentCountChecker> - { - static constexpr bool Variadic = true; - }; - - template - class CmdFunction final : public CmdBase { - public: - explicit CmdFunction(const std::string& name, const std::string& alternative, const std::string& description, bool required, bool dominant) : - CmdBase(name, alternative, description, required, dominant, ArgumentCountChecker::Variadic) { - } - - virtual bool parse(std::ostream& output, std::ostream& error) { - try { - CallbackArgs args { CmdBase::arguments, output, error }; - value = callback(args); - return true; - } catch (...) { - return false; - } - } - - virtual std::string print_value() const { - return ""; - } - - std::function callback; - T value; - }; - - template - class CmdArgument final : public CmdBase { - public: - explicit CmdArgument(const std::string& name, const std::string& alternative, const std::string& description, bool required, bool dominant) : - CmdBase(name, alternative, description, required, dominant, ArgumentCountChecker::Variadic) { - } - - virtual bool parse(std::ostream&, std::ostream&) { - try { - value = ParserWithPolicy::parse(CmdBase::arguments, value); - return true; - } catch (...) { - return false; - } - } - - virtual std::string print_value() const { - return stringify(value); - } - - T value; - }; - - static int parse(const std::vector& elements, const int&, int numberBase = 0) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stoi(elements[0], 0, numberBase); - } - - static bool parse(const std::vector& elements, const bool& defval) { - if (elements.size() != 0) - throw std::runtime_error("A boolean command line parameter cannot have any arguments."); - - return !defval; - } - - static double parse(const std::vector& elements, const double&) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stod(elements[0]); - } - - static float parse(const std::vector& elements, const float&) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stof(elements[0]); - } - - static long double parse(const std::vector& elements, const long double&) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stold(elements[0]); - } - - static unsigned int parse(const std::vector& elements, const unsigned int&, int numberBase = 0) { - if (elements.size() != 1) - throw std::bad_cast(); - - return static_cast(std::stoul(elements[0], 0, numberBase)); - } - - static unsigned long parse(const std::vector& elements, const unsigned long&, int numberBase = 0) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stoul(elements[0], 0, numberBase); - } - - static unsigned long long parse(const std::vector& elements, const unsigned long long&, int numberBase = 0) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stoull(elements[0], 0, numberBase); - } - - static long long parse(const std::vector& elements, const long long&, int numberBase = 0) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stoll(elements[0], 0, numberBase); - } - - static long parse(const std::vector& elements, const long&, int numberBase = 0) { - if (elements.size() != 1) - throw std::bad_cast(); - - return std::stol(elements[0], 0, numberBase); - } - - static std::string parse(const std::vector& elements, const std::string&) { - if (elements.size() != 1) - throw std::bad_cast(); - - return elements[0]; - } - - template - static std::vector parse(const std::vector& elements, const std::vector&) { - const T defval = T(); - std::vector values { }; - std::vector buffer(1); + /// Class used to wrap integer types to specify desired numerical base for specific argument parsing + template class NumericalBase { + public: + + /// This constructor required for correct AgrumentCountChecker initialization + NumericalBase() : value(0), base(numericalBase) {} + + /// This constructor required for default value initialization + /// \param val comes from default value + NumericalBase(T val) : value(val), base(numericalBase) {} + + operator T () const { + return this->value; + } + operator T * () { + return this->value; + } + + T value; + unsigned int base; + }; + + struct CallbackArgs { + const std::vector& arguments; + std::ostream& output; + std::ostream& error; + }; + + + template + class ParserWithPolicy { + private: + class CmdBase { + public: + explicit CmdBase(const std::string& name, const std::string& alternative, const std::string& description, bool required, bool dominant, bool variadic) : + name(name), + command(name.size() > 0 ? "-" + name : ""), + alternative(alternative.size() > 0 ? "--" + alternative : ""), + description(description), + required(required), + handled(false), + arguments({}), + dominant(dominant), + variadic(variadic) { + } + + virtual ~CmdBase() { + } + + std::string name; + std::string command; + std::string alternative; + std::string description; + bool required; + bool handled; + std::vector arguments; + bool const dominant; + bool const variadic; + + virtual std::string print_value() const = 0; + virtual bool parse(std::ostream& output, std::ostream& error) = 0; + + bool is(const std::string& given) const { + return given == command || given == alternative; + } + }; + + template + struct ArgumentCountChecker + { + static constexpr bool Variadic = false; + }; + + template + struct ArgumentCountChecker> + { + static constexpr bool Variadic = false; + }; + + template + struct ArgumentCountChecker> + { + static constexpr bool Variadic = true; + }; + + template + class CmdFunction final : public CmdBase { + public: + explicit CmdFunction(const std::string& name, const std::string& alternative, const std::string& description, bool required, bool dominant) : + CmdBase(name, alternative, description, required, dominant, ArgumentCountChecker::Variadic) { + } + + virtual bool parse(std::ostream& output, std::ostream& error) { + try { + CallbackArgs args { CmdBase::arguments, output, error }; + value = callback(args); + return true; + } catch (...) { + return false; + } + } + + virtual std::string print_value() const { + return ""; + } + + std::function callback; + T value; + }; + + template + class CmdArgument final : public CmdBase { + public: + explicit CmdArgument(const std::string& name, const std::string& alternative, const std::string& description, bool required, bool dominant) : + CmdBase(name, alternative, description, required, dominant, ArgumentCountChecker::Variadic) { + } + + virtual bool parse(std::ostream&, std::ostream&) { + try { + value = ParserWithPolicy::parse(CmdBase::arguments, value); + return true; + } catch (...) { + return false; + } + } + + virtual std::string print_value() const { + return stringify(value); + } + + T value; + }; + + static int parse(const std::vector& elements, const int&, int numberBase = 0) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stoi(elements[0], 0, numberBase); + } + + static bool parse(const std::vector& elements, const bool& defval) { + if (elements.size() != 0) + throw std::runtime_error("A boolean command line parameter cannot have any arguments."); + + return !defval; + } + + static double parse(const std::vector& elements, const double&) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stod(elements[0]); + } + + static float parse(const std::vector& elements, const float&) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stof(elements[0]); + } + + static long double parse(const std::vector& elements, const long double&) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stold(elements[0]); + } + + static unsigned int parse(const std::vector& elements, const unsigned int&, int numberBase = 0) { + if (elements.size() != 1) + throw std::bad_cast(); + + return static_cast(std::stoul(elements[0], 0, numberBase)); + } + + static unsigned long parse(const std::vector& elements, const unsigned long&, int numberBase = 0) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stoul(elements[0], 0, numberBase); + } + + static unsigned long long parse(const std::vector& elements, const unsigned long long&, int numberBase = 0) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stoull(elements[0], 0, numberBase); + } + + static long long parse(const std::vector& elements, const long long&, int numberBase = 0) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stoll(elements[0], 0, numberBase); + } + + static long parse(const std::vector& elements, const long&, int numberBase = 0) { + if (elements.size() != 1) + throw std::bad_cast(); + + return std::stol(elements[0], 0, numberBase); + } + + static std::string parse(const std::vector& elements, const std::string&) { + if (elements.size() != 1) + throw std::bad_cast(); + + return elements[0]; + } + + template + static std::vector parse(const std::vector& elements, const std::vector&) { + const T defval = T(); + std::vector values { }; + std::vector buffer(1); - for (const auto& element : elements) { - buffer[0] = element; - values.push_back(parse(buffer, defval)); - } + for (const auto& element : elements) { + buffer[0] = element; + values.push_back(parse(buffer, defval)); + } - return values; - } + return values; + } - template static T parse(const std::vector& elements, const NumericalBase& wrapper) { - return parse(elements, wrapper.value, 0); - } + template static T parse(const std::vector& elements, const NumericalBase& wrapper) { + return parse(elements, wrapper.value, 0); + } - /// Specialization for number wrapped into numerical base - /// \tparam T base type of the argument - /// \tparam base numerical base - /// \param elements - /// \param wrapper - /// \return parsed number - template static T parse(const std::vector& elements, const NumericalBase& wrapper) { - return parse(elements, wrapper.value, wrapper.base); - } + /// Specialization for number wrapped into numerical base + /// \tparam T base type of the argument + /// \tparam base numerical base + /// \param elements + /// \param wrapper + /// \return parsed number + template static T parse(const std::vector& elements, const NumericalBase& wrapper) { + return parse(elements, wrapper.value, wrapper.base); + } - template - static std::string stringify(const T& value) { - return std::to_string(value); - } + template + static std::string stringify(const T& value) { + return std::to_string(value); + } - template - static std::string stringify(const NumericalBase& wrapper) { - return std::to_string(wrapper.value); - } + template + static std::string stringify(const NumericalBase& wrapper) { + return std::to_string(wrapper.value); + } - template - static std::string stringify(const std::vector& values) { - std::stringstream ss { }; - ss << "[ "; + template + static std::string stringify(const std::vector& values) { + std::stringstream ss { }; + ss << "[ "; - for (const auto& value : values) { - ss << stringify(value) << " "; - } + for (const auto& value : values) { + ss << stringify(value) << " "; + } - ss << "]"; - return ss.str(); - } + ss << "]"; + return ss.str(); + } - static std::string stringify(const std::string& str) { - return str; - } + static std::string stringify(const std::string& str) { + return str; + } - public: - - template - class ArgumentPromise - { - public: - explicit ArgumentPromise(CmdArgument *cmd) - : cmd_(cmd){ } + public: + + template + class ArgumentPromise + { + public: + explicit ArgumentPromise(std::shared_ptr> cmd) + : cmd_(cmd){ } T get() const { return cmd_->value;} - private: - CmdArgument * cmd_; - }; - - explicit ParserWithPolicy(int argc, const char** argv) { - init(argc, argv); - } - - explicit ParserWithPolicy(int argc, char** argv) { - init(argc, argv); - } - - - Parser(int argc, const char** argv, std::string generalProgramDescriptionForHelpText) : - _general_help_text(std::move(generalProgramDescriptionForHelpText)) { - init(argc, argv); - } - - Parser(int argc, char** argv, std::string generalProgramDescriptionForHelpText) : - _general_help_text(std::move(generalProgramDescriptionForHelpText)) { - init(argc, argv); - } - - - Parser() {} - - Parser(std::string generalProgramDescriptionForHelpText) : - _general_help_text(std::move(generalProgramDescriptionForHelpText)) {} - - - ~Parser() { - for (size_t i = 0, n = _commands.size(); i < n; ++i) { - delete _commands[i]; - } - } - - - void init(int argc, char** argv) { - _appname = argv[0]; - - for (int i = 1; i < argc; ++i) { - _arguments.push_back(argv[i]); - } - enable_help(); - } - - void init(int argc, const char** argv) { - _appname = argv[0]; - - for (int i = 1; i < argc; ++i) { - _arguments.push_back(argv[i]); - } - enable_help(); - } - - - bool has_help() const { - for (const auto& command : _commands) { - if (command->name == "h" && command->alternative == "--help") { - return true; - } - } - - return false; - } - - void enable_help() { - set_callback("h", "help", std::function([this](CallbackArgs& args){ - args.output << this->usage(); - HelpExitPolicy::Exit(); - return false; - #pragma warning(pop) - }), "", true); - } - - void disable_help() { - for (auto command = _commands.begin(); command != _commands.end(); ++command) { - if ((*command)->name == "h" && (*command)->alternative == "--help") { - _commands.erase(command); - delete *command; - break; - } - } - } - - template - ArgumentPromise set_default(bool is_required, const std::string& description = "", T defaultValue = T()) { - auto command = std::make_unique>("", "", description, is_required, false); - command->value = defaultValue; - _commands.emplace_back(command); - return ArgumentPromise(command); - } - - template - ArgumentPromise set_required(const std::string& name, const std::string& alternative, const std::string& description = "", bool dominant = false) { - auto command = std::make_unique>(name, alternative, description, false, dominant); - _commands.emplace_back(command); - return ArgumentPromise(command); - } - - template - ArgumentPromise set_optional(const std::string& name, const std::string& alternative, T defaultValue, const std::string& description = "", bool dominant = false) { - auto command = std::make_unique>(name, alternative, description, false, dominant); - command->value = defaultValue; - _commands.emplace_back(command); - return ArgumentPromise(command); - } - - template - void set_callback(const std::string& name, const std::string& alternative, std::function callback, const std::string& description = "", bool dominant = false) { - auto command = std::make_unique>(name, alternative, description, false, dominant); - command->callback = callback; - _commands.emplace_back(command); - } - - inline void run_and_exit_if_error() { - if (run() == false) { - exit(1); - } - } - - inline bool run() { - return run(std::cout, std::cerr); - } - - inline bool run(std::ostream& output) { - return run(output, std::cerr); - } - - bool doesArgumentExist(std::string name, std::string altName) - { - for (const auto& argument : _arguments) { - - if(argument == '-'+ name || argument == altName) - { - return true; - } - } - - return false; - } - - inline bool doesHelpExist() - { - return doesArgumentExist("h", "--help"); - } - - bool run(std::ostream& output, std::ostream& error) { - if (_arguments.size() > 0) { - auto current = find_default(); - - for (string const& argument : _arguments) { - auto isarg = argument.size() > 0 && argument[0] == '-'; - auto associated = isarg ? find(argument) : nullptr; - - if (associated != nullptr) { - current = associated; - associated->handled = true; - } else if (current == nullptr) { - error << no_default(); - return false; - } else { - current->arguments.emplace_back(argument); - current->handled = true; - if (!current->variadic) - { - // If the current command is not variadic, then no more arguments - // should be added to it. In this case, switch back to the default - // command. - current = find_default(); - } - } - } - } - - // First, parse dominant arguments since they succeed even if required - // arguments are missing. - for (auto &command : _commands) { - if (command->handled && command->dominant && !command->parse(output, error)) { - error << howto_use(command.get()); - return false; - } - } - - // Next, check for any missing arguments. - for (auto &command : _commands) { - if (command->required && !command->handled) { - error << howto_required(command.get()); - return false; - } - } - - // Finally, parse all remaining arguments. - for (auto &command : _commands) { - if (command->handled && !command->dominant && !command->parse(output, error)) { - error << howto_use(command.get()); - return false; - } - } - - return true; - } - - template - T get(const std::string& name) const { - for (const auto& command : _commands) { - if (command->name == name) { - auto cmd = dynamic_cast*>(command.get()); - - if (cmd == nullptr) { - throw std::runtime_error("Invalid usage of the parameter " + name + " detected."); - } - - return cmd->value; - } - } - - throw std::runtime_error("The parameter " + name + " could not be found."); - } - - template - T get_default() const { - return get(""); - } - - template - T get_if(const std::string& name, std::function callback) const { - auto value = get(name); - return callback(value); - } - - int requirements() const { - int count = 0; - - for (const auto& command : _commands) { - if (command->required) { - ++count; - } - } - - return count; - } - - int commands() const { - return static_cast(_commands.size()); - } - - inline const std::string& app_name() const { - return _appname; - } - - protected: - CmdBase* find(const std::string& name) { - for (auto &command : _commands) { - if (command->is(name)) { - return command.get(); - } - } - - return nullptr; - } - - CmdBase* find_default() { - for (auto &command : _commands) { - if (command->name == "") { - return command.get(); - } - } - - return nullptr; - } - - std::string usage() const { - std::stringstream ss { }; - ss << _general_help_text << "\n\n"; - ss << "Available parameters:\n\n"; - - for (const auto& command : _commands) { - ss << " " << command->command << "\t" << command->alternative; - - if (command->required == true) { - ss << "\t(required)"; - } - - ss << "\n " << command->description; - - if (command->required == false) { - ss << "\n " << "This parameter is optional. The default value is '" + command->print_value() << "'."; - } - - ss << "\n\n"; - } - - return ss.str(); - } - - void print_help(std::stringstream& ss) const { - if (has_help()) { - ss << "For more help use --help or -h.\n"; - } - } - - std::string howto_required(CmdBase* command) const { - std::stringstream ss { }; - ss << "The parameter " << command->name << " is required.\n"; - ss << command->description << '\n'; - print_help(ss); - return ss.str(); - } - - std::string howto_use(CmdBase* command) const { - std::stringstream ss { }; - ss << "The parameter " << command->name << " has invalid arguments.\n"; - ss << command->description << '\n'; - print_help(ss); - return ss.str(); - } - - std::string no_default() const { - std::stringstream ss { }; - ss << "No default parameter has been specified.\n"; - ss << "The given argument must be used with a parameter.\n"; - print_help(ss); - return ss.str(); - } - - const std::string &get_general_help_text() const { - return _general_help_text; - } - - void set_general_help_text(const std::string &generalHelpText) { - _general_help_text = generalHelpText; - } - private: - std::string _appname; - std::string _general_help_text; - std::vector _arguments; - std::vector> _commands; - }; - - struct SystemExit - { - public: - static void Exit(){ - exit(0); - } - }; - - struct NoExit - { - public: - static void Exit(){ - - } - }; - - using Parser = ParserWithPolicy; + private: + std::shared_ptr> cmd_; + }; + + explicit ParserWithPolicy(int argc, const char** argv) { + init(argc, argv); + } + + explicit ParserWithPolicy(int argc, char** argv) { + init(argc, argv); + } + + + ParserWithPolicy(int argc, const char** argv, std::string generalProgramDescriptionForHelpText) : + _general_help_text(std::move(generalProgramDescriptionForHelpText)) { + init(argc, argv); + } + + ParserWithPolicy(int argc, char** argv, std::string generalProgramDescriptionForHelpText) : + _general_help_text(std::move(generalProgramDescriptionForHelpText)) { + init(argc, argv); + } + + + ParserWithPolicy() {} + + ParserWithPolicy(std::string generalProgramDescriptionForHelpText) : + _general_help_text(std::move(generalProgramDescriptionForHelpText)) {} + + + void init(int argc, char** argv) { + _appname = argv[0]; + + for (int i = 1; i < argc; ++i) { + _arguments.push_back(argv[i]); + } + enable_help(); + } + + void init(int argc, const char** argv) { + _appname = argv[0]; + + for (int i = 1; i < argc; ++i) { + _arguments.push_back(argv[i]); + } + enable_help(); + } + + + bool has_help() const { + for (const auto& command : _commands) { + if (command->name == "h" && command->alternative == "--help") { + return true; + } + } + + return false; + } + + void enable_help() { + set_callback("h", "help", std::function([this](CallbackArgs& args){ + args.output << this->usage(); + HelpExitPolicy::Exit(); + return false; + }), "", true); + } + + void disable_help() { + for (auto command = _commands.begin(); command != _commands.end(); ++command) { + if ((*command)->name == "h" && (*command)->alternative == "--help") { + _commands.erase(command); + break; + } + } + } + + template + ArgumentPromise set_default(bool is_required, const std::string& description = "", T defaultValue = T()) { + auto command = std::make_shared>("", "", description, is_required, false); + command->value = defaultValue; + _commands.emplace_back(command); + return ArgumentPromise(command); + } + + template + ArgumentPromise set_required(const std::string& name, const std::string& alternative, const std::string& description = "", bool dominant = false) { + auto command = std::make_shared>(name, alternative, description, false, dominant); + _commands.emplace_back(command); + return ArgumentPromise(command); + } + + template + ArgumentPromise set_optional(const std::string& name, const std::string& alternative, T defaultValue, const std::string& description = "", bool dominant = false) { + auto command = std::make_shared>(name, alternative, description, false, dominant); + command->value = defaultValue; + _commands.emplace_back(command); + return ArgumentPromise(command); + } + + template + void set_callback(const std::string& name, const std::string& alternative, std::function callback, const std::string& description = "", bool dominant = false) { + auto command = std::make_shared>(name, alternative, description, false, dominant); + command->callback = callback; + _commands.emplace_back(command); + } + + inline void run_and_exit_if_error() { + if (run() == false) { + exit(1); + } + } + + inline bool run() { + return run(std::cout, std::cerr); + } + + inline bool run(std::ostream& output) { + return run(output, std::cerr); + } + + bool doesArgumentExist(std::string name, std::string altName) + { + for (const auto& argument : _arguments) { + + if(argument == '-'+ name || argument == altName) + { + return true; + } + } + + return false; + } + + inline bool doesHelpExist() + { + return doesArgumentExist("h", "--help"); + } + + bool run(std::ostream& output, std::ostream& error) { + if (_arguments.size() > 0) { + auto current = find_default(); + + for (std::string const& argument : _arguments) { + auto isarg = argument.size() > 0 && argument[0] == '-'; + auto associated = isarg ? find(argument) : nullptr; + + if (associated != nullptr) { + current = associated; + associated->handled = true; + } else if (current == nullptr) { + error << no_default(); + return false; + } else { + current->arguments.emplace_back(argument); + current->handled = true; + if (!current->variadic) + { + // If the current command is not variadic, then no more arguments + // should be added to it. In this case, switch back to the default + // command. + current = find_default(); + } + } + } + } + + // First, parse dominant arguments since they succeed even if required + // arguments are missing. + for (auto &command : _commands) { + if (command->handled && command->dominant && !command->parse(output, error)) { + error << howto_use(command.get()); + return false; + } + } + + // Next, check for any missing arguments. + for (auto &command : _commands) { + if (command->required && !command->handled) { + error << howto_required(command.get()); + return false; + } + } + + // Finally, parse all remaining arguments. + for (auto &command : _commands) { + if (command->handled && !command->dominant && !command->parse(output, error)) { + error << howto_use(command.get()); + return false; + } + } + + return true; + } + + template + T get(const std::string& name) const { + for (const auto& command : _commands) { + if (command->name == name) { + auto cmd = dynamic_cast*>(command.get()); + + if (cmd == nullptr) { + throw std::runtime_error("Invalid usage of the parameter " + name + " detected."); + } + + return cmd->value; + } + } + + throw std::runtime_error("The parameter " + name + " could not be found."); + } + + template + T get_default() const { + return get(""); + } + + template + T get_if(const std::string& name, std::function callback) const { + auto value = get(name); + return callback(value); + } + + int requirements() const { + int count = 0; + + for (const auto& command : _commands) { + if (command->required) { + ++count; + } + } + + return count; + } + + int commands() const { + return static_cast(_commands.size()); + } + + inline const std::string& app_name() const { + return _appname; + } + + protected: + CmdBase* find(const std::string& name) { + for (auto &command : _commands) { + if (command->is(name)) { + return command.get(); + } + } + + return nullptr; + } + + CmdBase* find_default() { + for (auto &command : _commands) { + if (command->name == "") { + return command.get(); + } + } + + return nullptr; + } + + std::string usage() const { + std::stringstream ss { }; + ss << _general_help_text << "\n\n"; + ss << "Available parameters:\n\n"; + + for (const auto& command : _commands) { + ss << " " << command->command << "\t" << command->alternative; + + if (command->required == true) { + ss << "\t(required)"; + } + + ss << "\n " << command->description; + + if (command->required == false) { + ss << "\n " << "This parameter is optional. The default value is '" + command->print_value() << "'."; + } + + ss << "\n\n"; + } + + return ss.str(); + } + + void print_help(std::stringstream& ss) const { + if (has_help()) { + ss << "For more help use --help or -h.\n"; + } + } + + std::string howto_required(CmdBase* command) const { + std::stringstream ss { }; + ss << "The parameter " << command->name << " is required.\n"; + ss << command->description << '\n'; + print_help(ss); + return ss.str(); + } + + std::string howto_use(CmdBase* command) const { + std::stringstream ss { }; + ss << "The parameter " << command->name << " has invalid arguments.\n"; + ss << command->description << '\n'; + print_help(ss); + return ss.str(); + } + + std::string no_default() const { + std::stringstream ss { }; + ss << "No default parameter has been specified.\n"; + ss << "The given argument must be used with a parameter.\n"; + print_help(ss); + return ss.str(); + } + + const std::string &get_general_help_text() const { + return _general_help_text; + } + + void set_general_help_text(const std::string &generalHelpText) { + _general_help_text = generalHelpText; + } + private: + std::string _appname; + std::string _general_help_text; + std::vector _arguments; + std::vector> _commands; + }; + + struct SystemExit + { + public: + static void Exit(){ + exit(0); + } + }; + + struct NoExit + { + public: + static void Exit(){ + + } + }; + + using Parser = ParserWithPolicy; } From e2b7ebc7b7ae8283884c4d0398c2dc31b9215b23 Mon Sep 17 00:00:00 2001 From: haschke-felix Date: Sat, 12 Oct 2024 13:02:41 +0000 Subject: [PATCH 9/9] fix test for exit problem --- cmdparser.Test/CMakeLists.txt | 1 - cmdparser.Test/sample.cpp | 15 --------------- cmdparser.Test/tests.cpp | 9 +++++---- 3 files changed, 5 insertions(+), 20 deletions(-) delete mode 100644 cmdparser.Test/sample.cpp diff --git a/cmdparser.Test/CMakeLists.txt b/cmdparser.Test/CMakeLists.txt index a7a815a..9dee61c 100644 --- a/cmdparser.Test/CMakeLists.txt +++ b/cmdparser.Test/CMakeLists.txt @@ -1,6 +1,5 @@ set(SOURCE_FILES TestMain.cpp catch.hpp tests.cpp) add_executable(cmdparserTest ${SOURCE_FILES}) -add_executable(sample sample.cpp) IF(APPLE) TARGET_COMPILE_OPTIONS(cmdparserTest PUBLIC INTERFACE "-stdlib=libc++") ENDIF(APPLE) \ No newline at end of file diff --git a/cmdparser.Test/sample.cpp b/cmdparser.Test/sample.cpp deleted file mode 100644 index 00eebb8..0000000 --- a/cmdparser.Test/sample.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by marcel on 12/21/17. -// - -#include -#include "../cmdparser.hpp" - -int main(int argc, char**argv) -{ - cli::Parser parser(argc, argv); - parser.disable_help(); - const auto value = parser.run(std::cout, std::cerr); - - return 0; -} diff --git a/cmdparser.Test/tests.cpp b/cmdparser.Test/tests.cpp index 4732912..79863b7 100644 --- a/cmdparser.Test/tests.cpp +++ b/cmdparser.Test/tests.cpp @@ -3,9 +3,9 @@ Copyright (c) 2015 - 2016 Florian Rappl */ +#include "catch.hpp" #include #include "../cmdparser.hpp" -#include "catch.hpp" using namespace cli; @@ -18,14 +18,15 @@ TEST_CASE( "Parse help", "[help]" ) { "--help" }; - Parser parser(2, args); + ParserWithPolicy parser(2, args); const auto value = parser.run(output, errors); const std::string prefix = "Available parameters:"; REQUIRE(parser.has_help() == true); REQUIRE(parser.app_name() == "myapp"); - REQUIRE(value == false); - REQUIRE(output.str().substr(0, prefix.size()) == prefix); + // true as it met all required arguments. + REQUIRE(value == true); + REQUIRE(output.str().find(prefix) != std::string::npos); } TEST_CASE( "No help", "[help]" ) {