summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el12
-rw-r--r--.gitignore2
-rw-r--r--meson.build46
-rw-r--r--src/args.cc373
-rw-r--r--src/args.hh70
-rw-r--r--src/config.h.in1
-rw-r--r--src/main.cc33
-rw-r--r--subprojects/gtest.wrap16
-rw-r--r--test/args.cc358
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());
+}