Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test #303

Closed
wants to merge 4 commits into from
Closed

Test #303

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); });
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -1392,6 +1391,7 @@ class ArgumentParser {
.help("prints version information and exits")
.implicit_value(true)
.nargs(0);
m_default_version_argument_added = true;
}
}

Expand All @@ -1400,7 +1400,11 @@ 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),
m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed),
m_positional_arguments(other.m_positional_arguments),
Expand All @@ -1414,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);
Expand Down Expand Up @@ -1941,7 +1971,7 @@ class ArgumentParser {
"Failed to parse '" + current_argument + "', did you mean '" +
std::string{details::get_most_similar_string(
m_subparser_map, current_argument)} +
"'");
"'?");
}

// Ask the user if they meant to use a specific optional argument
Expand All @@ -1951,8 +1981,8 @@ class ArgumentParser {
// not a flag, requires a value
if (!opt.m_is_used) {
throw std::runtime_error(
"Zero positional arguments expected, did you mean " +
opt.get_usage_full());
"Zero positional arguments expected, did you mean '" +
opt.get_usage_full() + "'?");
}
}
}
Expand Down Expand Up @@ -2106,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;
Expand Down
8 changes: 0 additions & 8 deletions include/argparse/main.cpp

This file was deleted.

Binary file removed include/argparse/test
Binary file not shown.
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_compound_arguments.cpp
test_container_arguments.cpp
test_const_correct.cpp
test_copy_constructor.cpp
test_default_args.cpp
test_default_value.cpp
test_error_reporting.cpp
Expand All @@ -50,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
Expand Down
157 changes: 157 additions & 0 deletions test/test_copy_constructor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <test_utility.hpp>

using doctest::test_suite;

TEST_CASE("Parse positional arguments using a copy of an ArgumentParser" *
test_suite("copy_constructor")) {

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", "rocket.mesh", "thrust_profile.csv", "config.json"}));

auto first = program.get<std::string>("first");
REQUIRE(first == "rocket.mesh");
auto second = program.get<std::vector<std::string>>("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("copy_constructor")) {

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<std::string>("--first");
REQUIRE(first == "rocket.mesh");
auto second = program.get<std::vector<std::string>>("--second");
REQUIRE(second.size() == 2);
REQUIRE(second[0] == "thrust_profile.csv");
REQUIRE(second[1] == "config.json");
}

TEST_CASE("Segmentation fault on help (Issue #260)" *
test_suite("copy_constructor") * doctest::skip()) {

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)" *
test_suite("copy_constructor") * doctest::skip()) {

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);
}

TEST_CASE("Assign a new subparser with assignment operator (Issue #260)") {
argparse::ArgumentParser baseParser{"program", "0.0.0.0"};
argparse::ArgumentParser testCommand{"file-unsafe"};

// This is what causes references to be invalidated.
testCommand = argparse::ArgumentParser{"file-safe"};

testCommand.add_description("File generator command description.");
testCommand.add_argument("-p", "--path")
.default_value("some/path/on/system")
.required()
.help("Specifies the path to the target output file.");
baseParser.add_subparser(testCommand);
REQUIRE_NOTHROW(baseParser.parse_args(
{"program", "file-safe", "-p", "\"/home/foo/bar\""}));
REQUIRE(testCommand.get<std::string>("-p") ==
std::string{"\"/home/foo/bar\""});
REQUIRE(baseParser.at<argparse::ArgumentParser>("file-safe")
.get<std::string>("-p") == std::string{"\"/home/foo/bar\""});
}
16 changes: 8 additions & 8 deletions test/test_error_reporting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ TEST_CASE("Missing optional argument name" * test_suite("error_reporting")) {
SUBCASE("Bad case") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "-a", "1", "2"}),
"Zero positional arguments expected, did you mean -b VAR",
"Zero positional arguments expected, did you mean '-b VAR'?",
std::runtime_error);
}

SUBCASE("Bad case 2") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "1", "2"}),
"Zero positional arguments expected, did you mean -a VAR",
"Zero positional arguments expected, did you mean '-a VAR'?",
std::runtime_error);
}
}
Expand All @@ -50,14 +50,14 @@ TEST_CASE("Missing optional argument name (some flag arguments)" *
SUBCASE("Bad case") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "-a", "-b", "2"}),
"Zero positional arguments expected, did you mean -c VAR",
"Zero positional arguments expected, did you mean '-c VAR'?",
std::runtime_error);
}

SUBCASE("Bad case 2") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "-abc", "1", "2"}),
"Zero positional arguments expected, did you mean -d VAR",
"Zero positional arguments expected, did you mean '-d VAR'?",
std::runtime_error);
}
}
Expand All @@ -71,7 +71,7 @@ TEST_CASE("Missing optional argument name (multiple names)" *
SUBCASE("Bad case 2") {
REQUIRE_THROWS_WITH_AS(parser.parse_args({"test", "1", "2"}),
"Zero positional arguments expected, did you mean "
"-a/--number-of-apples VAR",
"'-a/--number-of-apples VAR'?",
std::runtime_error);
}
}
Expand Down Expand Up @@ -106,19 +106,19 @@ TEST_CASE("Detect unknown subcommand" * test_suite("error_reporting")) {

SUBCASE("Typo for 'notes'") {
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "tote"}),
"Failed to parse 'tote', did you mean 'notes'",
"Failed to parse 'tote', did you mean 'notes'?",
std::runtime_error);
}

SUBCASE("Typo for 'add'") {
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "bad"}),
"Failed to parse 'bad', did you mean 'add'",
"Failed to parse 'bad', did you mean 'add'?",
std::runtime_error);
}

SUBCASE("Typo for 'log'") {
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "logic"}),
"Failed to parse 'logic', did you mean 'log'",
"Failed to parse 'logic', did you mean 'log'?",
std::runtime_error);
}
}
20 changes: 0 additions & 20 deletions test/test_stringstream.cpp

This file was deleted.

Loading