diff options
| -rw-r--r-- | .dir-locals.el | 12 | ||||
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | meson.build | 46 | ||||
| -rw-r--r-- | src/args.cc | 373 | ||||
| -rw-r--r-- | src/args.hh | 70 | ||||
| -rw-r--r-- | src/config.h.in | 1 | ||||
| -rw-r--r-- | src/main.cc | 33 | ||||
| -rw-r--r-- | subprojects/gtest.wrap | 16 | ||||
| -rw-r--r-- | test/args.cc | 358 |
9 files changed, 911 insertions, 0 deletions
diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..573c58e --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,12 @@ +;;; Directory Local Variables -*- no-byte-compile: t; -*- +;;; For more information see (info "(emacs) Directory Variables") + +((c++-mode . ((eval + . + (let ((project-path + (locate-dominating-file default-directory ".dir-locals.el"))) + (setq-local flycheck-clangcheck-build-path + (concat project-path "build")) + (setq-local flycheck-clang-language-standard "c++20") + (setq-local flycheck-clang-definitions '("HAVE_CONFIG_H")) + (setq-local flycheck-clang-include-path '("../src" "../build"))))))) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db9f3cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/ +/compile_commands.json diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..15dc548 --- /dev/null +++ b/meson.build @@ -0,0 +1,46 @@ +project( + 'jkc', + 'cpp', + version : '0.1', + meson_version : '>= 1.3.0', + default_options : ['warning_level=3', 'cpp_std=c++20'], +) + +conf_data = configuration_data() +conf_data.set('version', meson.project_version()) +configure_file(input: 'src/config.h.in', + output: 'config.h', + configuration : conf_data) + +dependencies = [ +] + +inc = include_directories('src') + +exe = executable( + 'jkc', + sources: [ + 'src/args.cc', + 'src/args.hh', + 'src/main.cc', + ], + include_directories: inc, + install : true, + dependencies : dependencies, +) + +gtest_main_dep = dependency('gtest_main', fallback : ['gtest_main']) + +test_dependencies = [ + gtest_main_dep, +] + +test('args', executable( + 'test_args', + sources: [ + 'src/args.cc', + 'src/args.hh', + 'test/args.cc', + ], + include_directories: inc, + dependencies : test_dependencies)) diff --git a/src/args.cc b/src/args.cc new file mode 100644 index 0000000..68f296b --- /dev/null +++ b/src/args.cc @@ -0,0 +1,373 @@ +#include "args.hh" + +#include <cassert> +#include <format> +#include <iostream> +#include <map> +#include <optional> +#include <utility> + +namespace { + +std::string kEmpty; + +class OptionImpl : public Args::OptionArgument { + public: + enum Type { + NoArgument, + RequiredArgument, + OptionalArgument, + }; + + OptionImpl(Type type, char shortname, std::string longname, + std::string arg, std::string help) + : type(type), shortname(shortname), longname(std::move(longname)), + arg(std::move(arg)), help(std::move(help)) {} + + const Type type; + const char shortname; + const std::string longname; + const std::string arg; + const std::string help; + + bool is_set() const override { return is_set_; } + + bool has_argument() const override { return value_.has_value(); } + + const std::string& argument() const override { + if (value_.has_value()) + return value_.value(); + assert(false); + return kEmpty; + } + + void clear() { + is_set_ = false; + value_.reset(); + } + + void set_argument(std::string value) { + assert(type == Type::RequiredArgument || type == Type::OptionalArgument); + is_set_= true; + value_ = std::move(value); + } + + void set_no_argument() { + assert(type == Type::NoArgument || type == Type::OptionalArgument); + is_set_= true; + value_.reset(); + } + + private: + bool is_set_{false}; + std::optional<std::string> value_; +}; + +class ArgsImpl : public Args { + public: + explicit ArgsImpl(std::string prgname) + : prgname_(std::move(prgname)) {} + + std::shared_ptr<Option> option( + char shortname, std::string longname, std::string help) override { + auto opt = std::make_shared<OptionImpl>( + OptionImpl::Type::NoArgument, shortname, std::move(longname), + /* arg */ std::string(), std::move(help)); + add(opt); + return opt; + } + + std::shared_ptr<OptionArgument> option_argument( + char shortname, std::string longname, std::string arg, + std::string help, bool required) override { + auto opt = std::make_shared<OptionImpl>( + required ? OptionImpl::Type::RequiredArgument : + OptionImpl::Type::OptionalArgument, shortname, std::move(longname), + std::move(arg), std::move(help)); + add(opt); + return opt; + } + + bool run(int argc, char** argv, + std::vector<std::string_view>* arguments = nullptr) override { + last_error_.clear(); + for (auto& opt : options_) { + opt->clear(); + } + + std::string_view prgname; + if (prgname_.empty()) { + if (argc > 0) + prgname = argv[0]; + } else { + prgname = prgname_; + } + + for (int a = 1; a < argc; ++a) { + assert(argv[a]); + if (argv[a][0] == '-' && argv[a][1] != '\0') { + if (argv[a][1] == '-') { + // long option + size_t eq = 2; + while (argv[a][eq] != '=' && argv[a][eq] != '\0') ++eq; + size_t end = eq; + while (argv[a][end] != '\0') ++end; + + if (end == 2) { + // "--", no more options signal + if (arguments) { + for (++a; a < argc; ++a) + arguments->push_back(argv[a]); + } + break; + } + + auto name = std::string_view(argv[a] + 2, eq - 2); + auto it = long_.find(name); + if (it == long_.end()) { + last_error_ = std::format("{}: unrecognized option '--{}'", + prgname, name); + return false; + } + auto& opt = options_[it->second]; + + if (eq < end) { + // long option with argument after equal sign + switch (opt->type) { + case OptionImpl::Type::NoArgument: + last_error_ = std::format( + "{}: option '--{}' doesn't allow an argument", + prgname, name); + return false; + case OptionImpl::Type::RequiredArgument: + case OptionImpl::Type::OptionalArgument: + opt->set_argument( + std::string(argv[a] + eq + 1, end - (eq + 1))); + break; + } + } else { + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + opt->set_no_argument(); + break; + case OptionImpl::Type::RequiredArgument: + if (++a >= argc) { + last_error_ = std::format( + "{}: option '--{}' requires an argument", + prgname, name); + return false; + } + opt->set_argument(argv[a]); + break; + } + } + } else { + // short options + char* current = argv[a] + 1; + for (; *current; ++current) { + auto it = short_.find(*current); + if (it == short_.end()) { + last_error_ = std::format("{}: invalid option -- '{}'", + prgname, *current); + return false; + } + + auto& opt = options_[it->second]; + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + opt->set_no_argument(); + break; + case OptionImpl::Type::RequiredArgument: + if (++a >= argc) { + last_error_ = std::format( + "{}: option requires an argument -- '{}'", + prgname, *current); + return false; + } + opt->set_argument(argv[a]); + break; + } + } + } + } else { + if (arguments) + arguments->push_back(argv[a]); + } + } + return true; + } + + void print_error(std::ostream& out) const override { + if (last_error_.empty()) return; + + out << last_error_ << std::endl; + } + + void print_help(std::ostream& out, uint32_t width = 79) const override { + if (options_.empty()) return; + + uint32_t indent = 0; + const uint32_t max_need = width / 2; + std::vector<uint32_t> option_need; + for (auto const& opt : options_) { + uint32_t need; + if (opt->longname.empty()) { + need = 4; // -O + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + break; + case OptionImpl::Type::RequiredArgument: + need += 1 + (opt->arg.empty() ? 3 : opt->arg.size()); + break; + } + } else { + need = 8 + opt->longname.size(); // -O, --option + switch (opt->type) { + case OptionImpl::Type::NoArgument: + break; + case OptionImpl::Type::RequiredArgument: + // =ARG + need += 1 + (opt->arg.empty() ? 3 : opt->arg.size()); + break; + case OptionImpl::Type::OptionalArgument: + // [=ARG] + need += 3 + (opt->arg.empty() ? 3 : opt->arg.size()); + break; + } + } + need += 2; // margin + + option_need.push_back(need); + if (need <= max_need) { + indent = std::max(indent, need); + } + } + + print_wrap(out, width, /* indent */ 0, "Mandatory arguments to long options" + " are mandatory for short options too."); + auto need_it = option_need.begin(); + for (auto const& opt : options_) { + if (opt->longname.empty()) { + out << " -" << opt->shortname; + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + break; + case OptionImpl::Type::RequiredArgument: + out << " " << (opt->arg.empty() ? "ARG" : opt->arg); + break; + } + } else { + if (opt->shortname != '\0') { + out << " -" << opt->shortname << ", --"; + } else { + out << " --"; + } + out << opt->longname; + switch (opt->type) { + case OptionImpl::Type::NoArgument: + break; + case OptionImpl::Type::RequiredArgument: + out << "=" << (opt->arg.empty() ? "ARG" : opt->arg); + break; + case OptionImpl::Type::OptionalArgument: + out << "=[" << (opt->arg.empty() ? "ARG" : opt->arg) << ']'; + break; + } + } + + auto need = *need_it++; + if (need > max_need) { + out << '\n'; + if (!opt->help.empty()) { + print_wrap(out, width, 0, opt->help); + } + } else { + if (opt->help.empty()) { + out << '\n'; + } else { + out << " "; // add margin, already included in need + while (need++ < indent) + out << ' '; + print_wrap(out, width, indent, opt->help); + } + } + } + } + + private: + void add(std::shared_ptr<OptionImpl> opt) { + if (opt->shortname == '\0' && opt->longname.empty()) { + assert(false); + } else { + auto idx = options_.size(); + if (opt->shortname != '\0') + short_.emplace(opt->shortname, idx); + if (!opt->longname.empty()) + long_.emplace(opt->longname, idx); + } + options_.push_back(std::move(opt)); + } + + static inline bool is_whitespace(char c) { + return c == ' ' || c == '\t'; + } + + static void print_wrap(std::ostream& out, uint32_t width, uint32_t indent, + const std::string& str) { + if (indent + str.size() <= width) { + out << str << '\n'; + return; + } + if (width <= indent || indent + 10 > width) { + out << '\n'; + out << str << '\n'; + return; + } + const std::string indent_str(indent, ' '); + const uint32_t avail = width - indent; + size_t offset = 0; + while (offset + avail < str.size()) { + uint32_t i = avail; + while (i > 0 && !is_whitespace(str[offset + i])) --i; + if (i == 0) { + out << str.substr(offset, avail - 1); + out << "-\n"; + offset += avail - 1; + } else { + out << str.substr(offset, i); + out << '\n'; + offset += i; + } + out << indent_str; + } + out << str.substr(offset); + out << '\n'; + } + + const std::string prgname_; + std::vector<std::shared_ptr<OptionImpl>> options_; + std::map<char, size_t> short_; + std::map<std::string_view, size_t> long_; + std::string last_error_; +}; + +} // namespace + +std::shared_ptr<Args::Option> Args::option( + std::string longname, std::string help) { + return option(/* shortname */ '\0', std::move(longname), std::move(help)); +} + +std::shared_ptr<Args::OptionArgument> Args::option_argument( + std::string longname, std::string arg, std::string help, bool required) { + return option_argument(/* shortname */ '\0', std::move(longname), + std::move(arg), std::move(help), required); +} + +std::unique_ptr<Args> Args::create(std::string prgname) { + return std::make_unique<ArgsImpl>(std::move(prgname)); +} diff --git a/src/args.hh b/src/args.hh new file mode 100644 index 0000000..75c31b1 --- /dev/null +++ b/src/args.hh @@ -0,0 +1,70 @@ +#ifndef ARGS_HH +#define ARGS_HH + +#include <cstdint> +#include <iosfwd> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +class Args { + public: + virtual ~Args() = default; + + class Option { + public: + virtual ~Option() = default; + + virtual bool is_set() const = 0; + + protected: + Option() = default; + Option(Option const&) = delete; + Option& operator=(Option const&) = delete; + }; + + class OptionArgument : public Option { + public: + virtual bool has_argument() const = 0; + virtual const std::string& argument() const = 0; + }; + + static std::unique_ptr<Args> create(std::string prgname = std::string()); + + virtual std::shared_ptr<Option> option( + char shortname, + std::string longname = std::string(), + std::string help = std::string()) = 0; + + std::shared_ptr<Option> option( + std::string longname, + std::string help = std::string()); + + virtual std::shared_ptr<OptionArgument> option_argument( + char shortname, + std::string longname = std::string(), + std::string arg = std::string(), + std::string help = std::string(), + bool required = true) = 0; + + std::shared_ptr<OptionArgument> option_argument( + std::string longname, + std::string arg = std::string(), + std::string help = std::string(), + bool required = true); + + virtual bool run(int argc, char** argv, + std::vector<std::string_view>* arguments = nullptr) = 0; + + virtual void print_error(std::ostream& out) const = 0; + + virtual void print_help(std::ostream& out, uint32_t width = 79) const = 0; + + protected: + Args() = default; + Args(Args const&) = delete; + Args& operator=(Args const&) = delete; +}; + +#endif // ARGS_HH diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..eaab018 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1 @@ +#define VERSION "@version@" diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..9abed21 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,33 @@ +#include <iostream> + +#include "args.hh" +#include "config.h" + +#ifndef VERSION +# define VERSION "unknown" +#endif + +int main(int argc, char** argv) { + auto args = Args::create(); + auto opt_help = args->option('h', "help", "display this text and exit."); + auto opt_version = args->option('V', "version", "display version and exit."); + if (!args->run(argc, argv)) { + args->print_error(std::cerr); + std::cerr << "Try 'jkc --help' for more information." << std::endl; + return 1; + } + if (opt_help->is_set()) { + std::cout << "Usage: jkc [OPTION...] SOURCE...\n" + << "Java and Kotlin Compiler\n" + << "\n"; + args->print_help(std::cout); + std::cout << std::flush; + return 0; + } + if (opt_version->is_set()) { + std::cout << "jkc " << VERSION + << " written by Joel Klinghed <the_jk@spawned.biz>." << std::endl; + return 0; + } + return 0; +} diff --git a/subprojects/gtest.wrap b/subprojects/gtest.wrap new file mode 100644 index 0000000..3a97714 --- /dev/null +++ b/subprojects/gtest.wrap @@ -0,0 +1,16 @@ +[wrap-file] +directory = googletest-1.17.0 +source_url = https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz +source_filename = googletest-1.17.0.tar.gz +source_hash = 65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c +patch_filename = gtest_1.17.0-3_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.17.0-3/get_patch +patch_hash = 3e2799683f27c6dce138b7bae823416581c467ddde755c9a516c0863225f0ceb +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.17.0-3/googletest-1.17.0.tar.gz +wrapdb_version = 1.17.0-3 + +[provide] +gtest = gtest_dep +gtest_main = gtest_main_dep +gmock = gmock_dep +gmock_main = gmock_main_dep diff --git a/test/args.cc b/test/args.cc new file mode 100644 index 0000000..8c8c06c --- /dev/null +++ b/test/args.cc @@ -0,0 +1,358 @@ +#include <gtest/gtest.h> + +#include "args.hh" + +#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: + int c() const { return static_cast<int>(str_.size()); }; + char** v() const { return ptr_.get(); } + + explicit Arguments(std::vector<std::string> str) + : str_(std::move(str)) { + ptr_ = std::make_unique<char*[]>(str_.size()); + for (size_t i = 0; i < str_.size(); ++i) { + ptr_[i] = const_cast<char*>(str_[i].c_str()); + } + } + + Arguments(Arguments const&) = delete; + Arguments& operator=(Arguments const&) = delete; + + private: + std::unique_ptr<char*[]> ptr_; + std::vector<std::string> str_; +}; + +class Builder { + public: + Arguments build() { + return Arguments(std::move(str_)); + } + + Builder& add(std::string str) { + str_.push_back(std::move(str)); + return *this; + } + + private: + std::vector<std::string> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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_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_short_dash_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo") + .add("-d").add("-") + .build(); + std::vector<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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()); +} |
