#include "args.hh" #include #include #include #include #include #include #include #include #define SETUP_OPTIONS(args) \ auto short_only = (args)->option('a', "", "an option"); \ auto short_long = (args)->option('b', "bold", "set font style to bold"); \ auto long_only = (args)->option("cold", "use if it is cold outside"); \ auto short_only_req = (args)->option_argument('d', "", "", "distance"); \ auto short_long_req = (args)->option_argument( \ 'e', "eat", "FOOD", "what to order, what to eat?"); \ auto long_only_req = \ (args)->option_argument("form", "", "circle, shape or something else?"); \ auto short_only_opt = (args)->option_argument('g', "", "", "", false); \ auto short_long_opt = (args)->option_argument('h', "hold", "", "", false); \ auto long_only_opt = (args)->option_argument("invert", "", "", false); namespace { class Arguments { public: [[nodiscard]] int c() const { return static_cast(str_.size()); }; [[nodiscard]] char** v() const { return ptr_.get(); } explicit Arguments(std::vector str) : str_(std::move(str)) { ptr_ = std::make_unique(str_.size()); for (size_t i = 0; i < str_.size(); ++i) { ptr_[i] = const_cast(str_[i].c_str()); } } Arguments(Arguments const&) = delete; Arguments& operator=(Arguments const&) = delete; private: std::unique_ptr ptr_; std::vector str_; }; class Builder { public: Arguments build() { return Arguments(std::move(str_)); } Builder& add(std::string str) { str_.emplace_back(std::move(str)); return *this; } private: std::vector str_; }; } // namespace TEST(args, empty) { auto args = Args::create(); EXPECT_TRUE(args->run(0, nullptr)); { std::stringstream ss; args->print_error(ss); EXPECT_EQ("", ss.str()); } { std::stringstream ss; args->print_help(ss); EXPECT_EQ("", ss.str()); } } TEST(args, options_not_set) { auto args = Args::create(); SETUP_OPTIONS(args); EXPECT_TRUE(args->run(0, nullptr)); EXPECT_FALSE(short_only->is_set()); EXPECT_FALSE(short_long->is_set()); EXPECT_FALSE(long_only->is_set()); EXPECT_FALSE(short_only_req->is_set()); EXPECT_FALSE(short_long_req->is_set()); EXPECT_FALSE(long_only_req->is_set()); EXPECT_FALSE(short_only_opt->is_set()); EXPECT_FALSE(short_long_opt->is_set()); EXPECT_FALSE(long_only_opt->is_set()); } TEST(args, options_not_set_arguments) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("bar").add("fum").build(); std::vector out; EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); ASSERT_EQ(2, out.size()); EXPECT_EQ("bar", out[0]); EXPECT_EQ("fum", out[1]); } TEST(args, options_set) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo") .add("-a") .add("-b") .add("--cold") .add("-d") .add("10") .add("-e") .add("hamburger") .add("--form=circle") .add("-g") .add("-h") .add("--invert") .build(); std::vector out; EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); EXPECT_TRUE(out.empty()); EXPECT_TRUE(short_only->is_set()); EXPECT_TRUE(short_long->is_set()); EXPECT_TRUE(long_only->is_set()); EXPECT_TRUE(short_only_req->is_set()); EXPECT_TRUE(short_only_req->has_argument()); EXPECT_EQ("10", short_only_req->argument()); EXPECT_TRUE(short_long_req->is_set()); EXPECT_TRUE(short_long_req->has_argument()); EXPECT_EQ("hamburger", short_long_req->argument()); EXPECT_TRUE(long_only_req->is_set()); EXPECT_TRUE(long_only_req->has_argument()); EXPECT_EQ("circle", long_only_req->argument()); EXPECT_TRUE(short_only_opt->is_set()); EXPECT_FALSE(short_only_opt->has_argument()); EXPECT_TRUE(short_long_opt->is_set()); EXPECT_FALSE(short_long_opt->has_argument()); EXPECT_TRUE(long_only_opt->is_set()); EXPECT_FALSE(long_only_opt->has_argument()); } TEST(args, options_set_variant) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo") .add("-a") .add("--bold") .add("--cold") .add("-d") .add("10") .add("--eat=hamburger") .add("--form") .add("circle") .add("-g") .add("--hold=foo") .add("--invert=bar") .build(); std::vector out; EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); EXPECT_TRUE(out.empty()); EXPECT_TRUE(short_only->is_set()); EXPECT_TRUE(short_long->is_set()); EXPECT_TRUE(long_only->is_set()); EXPECT_TRUE(short_only_req->is_set()); EXPECT_TRUE(short_only_req->has_argument()); EXPECT_EQ("10", short_only_req->argument()); EXPECT_TRUE(short_long_req->is_set()); EXPECT_TRUE(short_long_req->has_argument()); EXPECT_EQ("hamburger", short_long_req->argument()); EXPECT_TRUE(long_only_req->is_set()); EXPECT_TRUE(long_only_req->has_argument()); EXPECT_EQ("circle", long_only_req->argument()); EXPECT_TRUE(short_only_opt->is_set()); EXPECT_FALSE(short_only_opt->has_argument()); EXPECT_TRUE(short_long_opt->is_set()); EXPECT_TRUE(short_long_opt->has_argument()); EXPECT_EQ("foo", short_long_opt->argument()); EXPECT_TRUE(long_only_opt->is_set()); EXPECT_TRUE(long_only_opt->has_argument()); EXPECT_EQ("bar", long_only_opt->argument()); } TEST(args, options_short_missing_value) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("-d").build(); EXPECT_FALSE(args->run(arg.c(), arg.v())); std::stringstream ss; args->print_error(ss); EXPECT_EQ("foo: option requires an argument -- 'd'\n", ss.str()); } TEST(args, options_short_unknown_value) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("-X").build(); EXPECT_FALSE(args->run(arg.c(), arg.v())); std::stringstream ss; args->print_error(ss); EXPECT_EQ("foo: invalid option -- 'X'\n", ss.str()); } TEST(args, options_long_missing_value) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("--form").build(); EXPECT_FALSE(args->run(arg.c(), arg.v())); std::stringstream ss; args->print_error(ss); EXPECT_EQ("foo: option '--form' requires an argument\n", ss.str()); } TEST(args, options_long_unsupported_value) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("--cold=feet").build(); EXPECT_FALSE(args->run(arg.c(), arg.v())); std::stringstream ss; args->print_error(ss); EXPECT_EQ("foo: option '--cold' doesn't allow an argument\n", ss.str()); } TEST(args, options_long_unknown_value) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("--experience").build(); EXPECT_FALSE(args->run(arg.c(), arg.v())); std::stringstream ss; args->print_error(ss); EXPECT_EQ("foo: unrecognized option '--experience'\n", ss.str()); } TEST(args, options_short_dash_value) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("-d").add("-").build(); std::vector out; EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); EXPECT_TRUE(out.empty()); EXPECT_TRUE(short_only_req->is_set()); EXPECT_TRUE(short_only_req->has_argument()); EXPECT_EQ("-", short_only_req->argument()); } TEST(args, options_long_dash_dash_value) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("--eat").add("--").build(); std::vector out; EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); EXPECT_TRUE(out.empty()); EXPECT_TRUE(short_long_req->is_set()); EXPECT_TRUE(short_long_req->has_argument()); EXPECT_EQ("--", short_long_req->argument()); } TEST(args, options_dash_dash) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo") .add("--") .add("-d") .add("10") .add("--eat=hamburger") .add("--form") .add("circle") .build(); std::vector out; EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); ASSERT_EQ(5, out.size()); EXPECT_EQ("-d", out[0]); EXPECT_EQ("10", out[1]); EXPECT_EQ("--eat=hamburger", out[2]); EXPECT_EQ("--form", out[3]); EXPECT_EQ("circle", out[4]); EXPECT_FALSE(short_only_req->is_set()); EXPECT_FALSE(short_long_req->is_set()); EXPECT_FALSE(long_only_req->is_set()); } TEST(args, options_dash_dash_end) { auto args = Args::create(); SETUP_OPTIONS(args); Builder builder; auto arg = builder.add("foo").add("--").build(); std::vector out; EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); EXPECT_TRUE(out.empty()); } TEST(args, help) { auto args = Args::create(); SETUP_OPTIONS(args); std::stringstream ss; args->print_help(ss, 50); EXPECT_EQ(R"(Mandatory arguments to long options are mandatory for short options too. -a an option -b, --bold set font style to bold --cold use if it is cold outside -d ARG distance -e, --eat=FOOD what to order, what to eat? --form=ARG circle, shape or something else? -g -h, --hold=[ARG] --invert=[ARG] )", ss.str()); } TEST(args, help_wide) { auto args = Args::create(); SETUP_OPTIONS(args); std::stringstream ss; args->print_help(ss, 100); EXPECT_EQ( R"(Mandatory arguments to long options are mandatory for short options too. -a an option -b, --bold set font style to bold --cold use if it is cold outside -d ARG distance -e, --eat=FOOD what to order, what to eat? --form=ARG circle, shape or something else? -g -h, --hold=[ARG] --invert=[ARG] )", ss.str()); } TEST(args, help_narrow) { auto args = Args::create(); SETUP_OPTIONS(args); std::stringstream ss; args->print_help(ss, 35); EXPECT_EQ(R"(Mandatory arguments to long options are mandatory for short options too. -a an option -b, --bold set font style to bold --cold use if it is cold outside -d ARG distance -e, --eat=FOOD what to order, what to eat? --form=ARG circle, shape or something else? -g -h, --hold=[ARG] --invert=[ARG] )", ss.str()); } TEST(args, help_very_narrow) { auto args = Args::create(); SETUP_OPTIONS(args); std::stringstream ss; args->print_help(ss, 20); EXPECT_EQ(R"(Mandatory arguments to long options are mandatory for short options too. -a an option -b, --bold set font style to bold --cold use if it is cold outside -d ARG distance -e, --eat=FOOD what to order, what to eat? --form=ARG circle, shape or something else? -g -h, --hold=[ARG] --invert=[ARG] )", ss.str()); } TEST(args, help_long_word) { auto args = Args::create(); std::stringstream ss; auto opt = args->option('a', "arg", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); args->print_help(ss, 20); EXPECT_EQ(R"(Mandatory arguments to long options are mandatory for short options too. -a, --arg aaaaaaaaaaaaaaaaaaa- aaaaaaaaaaaaaaaaaaa )", ss.str()); }