diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index cd5c982a..64d1b76c 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -906,7 +906,6 @@ class Argument { } std::size_t get_arguments_length() const { - std::size_t names_size = std::accumulate( std::begin(m_names), std::end(m_names), std::size_t(0), [](const auto &sum, const auto &s) { return sum + s.size(); }); @@ -1362,15 +1361,14 @@ class ArgumentParser { explicit ArgumentParser(std::string program_name = {}, std::string version = "1.0", default_arguments add_args = default_arguments::all, - bool exit_on_default_arguments = true, - std::ostream &os = std::cout) + bool exit_on_default_arguments = true) : m_program_name(std::move(program_name)), m_version(std::move(version)), m_exit_on_default_arguments(exit_on_default_arguments), m_parser_path(m_program_name) { if ((add_args & default_arguments::help) == default_arguments::help) { add_argument("-h", "--help") - .action([&](const auto & /*unused*/) { - os << help().str(); + .action([this](const auto & /*unused*/) { + std::cout << help().str(); if (m_exit_on_default_arguments) { std::exit(0); } @@ -1379,11 +1377,12 @@ class ArgumentParser { .help("shows help message and exits") .implicit_value(true) .nargs(0); + m_default_help_argument_added = true; } if ((add_args & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") - .action([&](const auto & /*unused*/) { - os << m_version << std::endl; + .action([this](const auto & /*unused*/) { + std::cout << m_version << std::endl; if (m_exit_on_default_arguments) { std::exit(0); } @@ -1392,6 +1391,7 @@ class ArgumentParser { .help("prints version information and exits") .implicit_value(true) .nargs(0); + m_default_version_argument_added = true; } } @@ -1400,6 +1400,9 @@ class ArgumentParser { ArgumentParser(const ArgumentParser &other) : m_program_name(other.m_program_name), m_version(other.m_version), + m_default_help_argument_added(other.m_default_help_argument_added), + m_default_version_argument_added( + other.m_default_version_argument_added), m_description(other.m_description), m_epilog(other.m_epilog), m_exit_on_default_arguments(other.m_exit_on_default_arguments), m_prefix_chars(other.m_prefix_chars), @@ -1415,6 +1418,32 @@ class ArgumentParser { it != std::end(m_optional_arguments); ++it) { index_argument(it); } + + // Redefine help action + if (m_default_help_argument_added) { + auto help_argument = m_argument_map["--help"]; + help_argument->action([this](const auto & /*unused*/) { + std::cout << help().str(); + if (m_exit_on_default_arguments) { + std::exit(0); + } + }); + } + + if (m_default_version_argument_added) { + add_argument("-v", "--version") + .action([this](const auto & /*unused*/) { + std::cout << m_version << std::endl; + if (m_exit_on_default_arguments) { + std::exit(0); + } + }) + .default_value(false) + .help("prints version information and exits") + .implicit_value(true) + .nargs(0); + } + for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers); ++it) { m_subparser_map.insert_or_assign(it->get().m_program_name, it); @@ -2107,6 +2136,8 @@ class ArgumentParser { std::string m_program_name; std::string m_version; + bool m_default_help_argument_added{false}; + bool m_default_version_argument_added{false}; std::string m_description; std::string m_epilog; bool m_exit_on_default_arguments = true; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9edda40d..4b4c5a2b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,7 +51,6 @@ file(GLOB ARGPARSE_TEST_SOURCES test_repr.cpp test_required_arguments.cpp test_scan.cpp - test_stringstream.cpp test_value_semantics.cpp test_version.cpp test_subparsers.cpp diff --git a/test/test_copy_constructor.cpp b/test/test_copy_constructor.cpp index e7369ec2..c7060785 100644 --- a/test/test_copy_constructor.cpp +++ b/test/test_copy_constructor.cpp @@ -8,17 +8,127 @@ import argparse; using doctest::test_suite; -TEST_CASE("Parse positional arguments using a copy of an ArgumentParser" * test_suite("vector")) { +TEST_CASE("Parse positional arguments using a copy of an ArgumentParser" * + test_suite("vector")) { auto maker = []() { - argparse::ArgumentParser program("test"); - program.add_argument("first"); - program.add_argument("second"); + argparse::ArgumentParser program("test"); + program.add_argument("first"); + program.add_argument("second").nargs(2); - return program; + return program; }; auto program = maker(); - program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"}); + REQUIRE_NOTHROW(program.parse_args( + {"test", "rocket.mesh", "thrust_profile.csv", "config.json"})); + + auto first = program.get("first"); + REQUIRE(first == "rocket.mesh"); + auto second = program.get>("second"); + REQUIRE(second.size() == 2); + REQUIRE(second[0] == "thrust_profile.csv"); + REQUIRE(second[1] == "config.json"); +} + +TEST_CASE("Parse optional arguments using a copy of an ArgumentParser" * + test_suite("vector")) { + + auto maker = []() { + argparse::ArgumentParser program("test"); + program.add_argument("--first"); + program.add_argument("--second").nargs(2); + + return program; + }; + + auto program = maker(); + + REQUIRE_NOTHROW( + program.parse_args({"test", "--first", "rocket.mesh", "--second", + "thrust_profile.csv", "config.json"})); + + auto first = program.get("--first"); + REQUIRE(first == "rocket.mesh"); + auto second = program.get>("--second"); + REQUIRE(second.size() == 2); + REQUIRE(second[0] == "thrust_profile.csv"); + REQUIRE(second[1] == "config.json"); +} + +TEST_CASE("Segmentation fault on help (Issue #260)") { + + struct SubparserContainer { + argparse::ArgumentParser parser; + }; + + auto get_container = []() { + SubparserContainer *container = nullptr; + if (container == nullptr) { + argparse::ArgumentParser parser("subcommand", "1.0", + argparse::default_arguments::all, false); + parser.add_description("Example"); + container = new SubparserContainer{parser}; + } + return container; + }; + + argparse::ArgumentParser program("program"); + auto *container = get_container(); + program.add_subparser(container->parser); + + std::ostringstream oss; + std::streambuf *p_cout_streambuf = std::cout.rdbuf(); + std::cout.rdbuf(oss.rdbuf()); + + program.parse_args({"program", "subcommand", "-h"}); + + std::cout.rdbuf(p_cout_streambuf); // restore + + auto cmdline_output = oss.str(); + REQUIRE(cmdline_output.size() > 0); + REQUIRE(cmdline_output.find("shows help message and exits") != + std::string::npos); +} + +TEST_CASE("Segmentation fault on custom help (Issue #260)") { + + struct SubparserContainer { + argparse::ArgumentParser parser; + }; + + auto get_container = []() { + SubparserContainer *container = nullptr; + if (container == nullptr) { + argparse::ArgumentParser parser("subcommand", "1.0", + argparse::default_arguments::none, false); + parser.add_description("Example"); + std::string temporary{"temp+string"}; + parser.add_argument("-h", "--help") + .flag() + .nargs(0) + .action( + [&](const auto &) -> void { std::cout << temporary << "\n"; }); + + container = new SubparserContainer{parser}; + } + return container; + }; + + argparse::ArgumentParser program("program"); + auto *container = get_container(); + program.add_subparser(container->parser); + + std::ostringstream oss; + std::streambuf *p_cout_streambuf = std::cout.rdbuf(); + std::cout.rdbuf(oss.rdbuf()); + + program.parse_args({"program", "subcommand", "-h"}); + + std::cout.rdbuf(p_cout_streambuf); // restore + + auto cmdline_output = oss.str(); + REQUIRE(cmdline_output.size() > 0); + REQUIRE(cmdline_output.find("temp+string") != std::string::npos); } \ No newline at end of file diff --git a/test/test_stringstream.cpp b/test/test_stringstream.cpp deleted file mode 100644 index 105bb409..00000000 --- a/test/test_stringstream.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifdef WITH_MODULE -import argparse; -#else -#include -#endif -#include - -#include -#include -#include - -using doctest::test_suite; - -TEST_CASE("Get Version String" * test_suite("stringstream")) { - std::stringstream os; - argparse::ArgumentParser program("test", "1.0", - argparse::default_arguments::all, false, os); - program.parse_args({"test", "--version"}); - REQUIRE(os.str() == "1.0\n"); -} \ No newline at end of file