summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2025-03-04 09:41:00 +0100
committerBent Bisballe Nyeng <deva@aasimon.org>2025-03-04 09:48:49 +0100
commit0778d3c45d1aef18d9b2ae6769e372b2a3d83b8e (patch)
tree046baeb2da4940c4292a67416d047cf35c1b8b72
parent8a40f5f49d428bbc89f54144d893a9fa16e3156c (diff)
New cross-platform argparser to replace getopt and getoptpp.develop
-rw-r--r--.gitmodules3
-rw-r--r--src/argparser.h477
-rw-r--r--src/configure.cc213
m---------src/getoptpp0
-rw-r--r--src/libctor.cc184
-rw-r--r--test/argparser_test.cc1019
-rw-r--r--test/ctor.cc16
-rwxr-xr-xtest/suite/test.sh2
8 files changed, 1755 insertions, 159 deletions
diff --git a/.gitmodules b/.gitmodules
index c765825..c12c38f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
-[submodule "getoptpp"]
- path = src/getoptpp
- url = git://git.drumgizmo.org/getoptpp.git
[submodule "test/uunit"]
path = test/uunit
url = git://git.drumgizmo.org/uunit.git
diff --git a/src/argparser.h b/src/argparser.h
new file mode 100644
index 0000000..c5337e0
--- /dev/null
+++ b/src/argparser.h
@@ -0,0 +1,477 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#pragma once
+
+#include <functional>
+#include <variant>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <stdexcept>
+#include <iostream>
+#include <limits>
+
+namespace arg
+{
+struct noarg {};
+template<typename T>
+struct Opt
+{
+ char shortopt;
+ std::string longopt;
+ std::function<int(T)> cb;
+ std::string help;
+ T t{};
+};
+
+template<>
+struct Opt<noarg>
+{
+ char shortopt;
+ std::string longopt;
+ std::function<int()> cb;
+ std::string help;
+ noarg t{};
+};
+
+template<typename Callable, typename... Args>
+auto call_if(Callable cb, Args... args)
+{
+ using Ret = std::invoke_result_t<decltype(cb), Args&&...>;
+ if constexpr (std::is_same_v<Ret, void>)
+ {
+ if(cb)
+ {
+ return cb(std::forward<Args>(args)...);
+ }
+ }
+ else
+ {
+ if(cb)
+ {
+ return cb(std::forward<Args>(args)...);
+ }
+ return Ret{};
+ }
+}
+
+enum class error
+{
+ missing_arg,
+ invalid_arg,
+ invalid_opt,
+};
+
+template<typename... Ts>
+class Parser
+{
+public:
+ struct missing_arg{};
+
+ Parser(int argc_, const char* const* argv_)
+ : argc(argc_)
+ , argv(argv_)
+ {
+ }
+
+ int parse() const
+ {
+ bool demarcate{false};
+ for(int i = 1; i < argc; ++i) // skip argv[0] which is program name
+ {
+ std::string_view arg{argv[i]};
+ if(arg.size() == 0)
+ {
+ // Empty arg - This shouldn't happen
+ continue;
+ }
+
+ if(arg[0] != '-' || demarcate) // positional arg
+ {
+ auto res = call_if(pos_cb, arg);
+ if(res != 0)
+ {
+ return res;
+ }
+ continue;
+ }
+
+ if(arg == "--")
+ {
+ demarcate = true;
+ continue;
+ }
+
+ bool was_handled{false};
+ enum class state { handled, unhandled };
+
+ if(arg.size() > 1 && arg[0] == '-' && arg[1] == '-') // long
+ {
+ for(const auto& option : options)
+ {
+ auto ret =
+ std::visit([&](auto&& opt) -> std::pair<int, state>
+ {
+ if(opt.longopt != arg &&
+ !arg.starts_with(opt.longopt+'='))
+ {
+ return {0, state::unhandled};
+ }
+ try
+ {
+ using T = std::decay_t<decltype(opt)>;
+ if constexpr (std::is_same_v<T, Opt<noarg>>)
+ {
+ return {opt.cb(), state::handled};
+ }
+ else
+ {
+ return {opt.cb(convert(i, opt.t)),
+ state::handled};
+ }
+ }
+ catch(std::invalid_argument&)
+ {
+ call_if(err_cb, error::invalid_arg, argv[i]);
+ return {1, state::handled};
+ }
+ catch(missing_arg&)
+ {
+ call_if(err_cb, error::missing_arg, argv[i]);
+ return {1, state::handled};
+ }
+ }, option);
+ if(ret.second == state::handled && ret.first != 0)
+ {
+ return ret.first;
+ }
+ was_handled |= ret.second == state::handled;
+ if(was_handled)
+ {
+ break;
+ }
+ }
+ }
+ else
+ if(arg.size() > 1 && arg[0] == '-') // short
+ {
+ for(auto index = 1u; index < arg.size(); ++index)
+ {
+ was_handled = false;
+ for(const auto& option : options)
+ {
+ auto ret =
+ std::visit([&](auto&& opt) -> std::pair<int, state>
+ {
+ char c = arg[index];
+ if(opt.shortopt != c)
+ {
+ return {0, state::unhandled};
+ }
+ try
+ {
+ using T = std::decay_t<decltype(opt)>;
+ if constexpr (std::is_same_v<T, Opt<noarg>>)
+ {
+ return {opt.cb(), state::handled};
+ }
+ else
+ {
+ // Note: the rest of arg is converted to opt
+ auto idx = index;
+ // set index out of range all was eaten as arg
+ index = std::numeric_limits<int>::max();
+ return {opt.cb(convert_short(&arg[idx],
+ i, opt.t)),
+ state::handled};
+ }
+ }
+ catch(std::invalid_argument&)
+ {
+ call_if(err_cb, error::invalid_arg, argv[i]);
+ return {1, state::handled};
+ }
+ catch(missing_arg&)
+ {
+ call_if(err_cb, error::missing_arg, argv[i]);
+ return {1, state::handled};
+ }
+ }, option);
+ if(ret.second == state::handled && ret.first != 0)
+ {
+ return ret.first;
+ }
+ was_handled |= ret.second == state::handled;
+ if(was_handled)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if(!was_handled)
+ {
+ call_if(err_cb, error::invalid_opt, arg);
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ template<typename T>
+ void add(char shortopt,
+ const std::string& longopt,
+ std::function<int(T)> cb,
+ const std::string& help)
+ {
+ options.emplace_back(Opt<T>{shortopt, longopt, cb, help});
+ }
+
+ void add(char shortopt,
+ const std::string& longopt,
+ std::function<int()> cb,
+ const std::string& help)
+ {
+ options.emplace_back(Opt<noarg>{shortopt, longopt, cb, help});
+ }
+
+ void set_pos_cb(std::function<int(std::string_view)> cb)
+ {
+ pos_cb = cb;
+ }
+
+ void set_err_cb(std::function<void(error, std::string_view)> cb)
+ {
+ err_cb = cb;
+ }
+
+ std::string prog_name() const
+ {
+ if(argc < 1)
+ {
+ return {};
+ }
+ return argv[0];
+ }
+
+ void help() const
+ {
+ constexpr std::size_t width{26};
+ constexpr std::size_t column_width{80};
+
+ for(const auto& option : options)
+ {
+ std::visit(
+ [&](auto&& opt)
+ {
+ std::string _args;
+ using T = std::decay_t<decltype(opt)>;
+ if constexpr (std::is_same_v<T, Opt<noarg>>)
+ {
+ }
+ else if constexpr (std::is_same_v<T, Opt<int>>)
+ {
+ _args = "<int>";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::optional<int>>>)
+ {
+ _args = "[int]";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::string>>)
+ {
+ _args = "<str>";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::optional<std::string>>>)
+ {
+ _args = "[str]";
+ }
+ else if constexpr (std::is_same_v<T, Opt<double>>)
+ {
+ _args = "<real>";
+ }
+ else if constexpr (std::is_same_v<T, Opt<std::optional<double>>>)
+ {
+ _args = "[real]";
+ }
+ else
+ {
+ static_assert(std::is_same_v<T, void>, "missing");
+ }
+
+ std::string option_str;
+ if(opt.shortopt != '\0' && !opt.longopt.empty())
+ {
+ option_str = " -" + std::string(1, opt.shortopt) + ", " +
+ opt.longopt + " " + _args;
+ }
+ else if(opt.shortopt != '\0')
+ {
+ option_str = " -" + std::string(1, opt.shortopt) + _args;
+ }
+ else if(!opt.longopt.empty())
+ {
+ option_str = " " + std::string(opt.longopt) + " " + _args;
+ }
+
+ std::string padding;
+ if(option_str.size() < width)
+ {
+ padding.append(width - option_str.size(), ' ');
+ }
+ else
+ {
+ padding = "\n";
+ padding.append(width, ' ');
+ }
+
+ std::cout << option_str << padding;
+
+ auto i = width;
+ for(auto c : opt.help)
+ {
+ if((c == '\n') || (i > column_width && (c == ' ' || c == '\t')))
+ {
+ std::string _padding(width, ' ');
+ std::cout << '\n' << _padding;
+ i = width;
+ continue;
+ }
+ std::cout << c;
+ ++i;
+ }
+ std::cout << '\n';
+ }, option);
+ }
+ }
+
+private:
+ template<typename T>
+ T convert(int& i, T) const
+ {
+ auto opt = convert(i, std::optional<T>{});
+ if(!opt)
+ {
+ throw missing_arg{};
+ }
+ return *opt;
+ }
+
+ template<typename T>
+ std::optional<T> convert(int& i, std::optional<T>) const
+ {
+ std::string arg;
+ bool has_arg{false};
+ std::string opt = argv[i];
+ if(opt.starts_with("--"))
+ {
+ // long opt
+ auto equals_pos = opt.find('=');
+ if(equals_pos != std::string::npos)
+ {
+ arg = opt.substr(equals_pos + 1);
+ has_arg = true;
+ }
+ else if(i+1 < argc)
+ {
+ arg = argv[i+1];
+ has_arg = !arg.starts_with("-");
+ if(has_arg)
+ {
+ ++i;
+ }
+ }
+ }
+
+ if(!has_arg)
+ {
+ return {};
+ }
+
+ if constexpr (std::is_same_v<T, int>)
+ {
+ return std::stoi(arg);
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ return std::stod(arg);
+ }
+ else if constexpr (std::is_same_v<T, std::string>)
+ {
+ return arg;
+ }
+ else
+ {
+ static_assert(std::is_same_v<T, void>, "missing");
+ }
+ return {};
+ }
+
+ template<typename T>
+ T convert_short(const char* arg_, int& i, T) const
+ {
+ auto opt = convert_short(arg_, i, std::optional<T>{}, false);
+ if(!opt)
+ {
+ throw missing_arg{};
+ }
+ return *opt;
+ }
+
+ template<typename T>
+ std::optional<T> convert_short(const char* arg_, int& i,
+ std::optional<T>, bool optional = true) const
+ {
+ std::string arg;
+ bool has_arg{false};
+ std::string opt = arg_;
+ if(opt.length() > 1)
+ {
+ // arg in same token
+ arg = opt.substr(1);
+ has_arg = true;
+ }
+ else if(!optional && i+1 < argc)
+ {
+ arg = argv[i+1];
+ has_arg = true;//!arg.starts_with("-");
+ if(has_arg)
+ {
+ ++i;
+ }
+ }
+
+ if(!has_arg)
+ {
+ return {};
+ }
+
+ if constexpr (std::is_same_v<T, int>)
+ {
+ return std::stoi(arg);
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ return std::stod(arg);
+ }
+ else if constexpr (std::is_same_v<T, std::string>)
+ {
+ return arg;
+ }
+ else
+ {
+ static_assert(std::is_same_v<T, void>, "missing");
+ }
+ return {};
+ }
+
+ using Opts = std::variant<Opt<noarg>, Opt<Ts>...>;
+ std::vector<Opts> options;
+ std::function<int(std::string_view)> pos_cb;
+ int argc;
+ const char* const* argv;
+ std::function<void(error, std::string_view)> err_cb;
+};
+
+} // arg::
diff --git a/src/configure.cc b/src/configure.cc
index a43152f..3d73011 100644
--- a/src/configure.cc
+++ b/src/configure.cc
@@ -8,8 +8,7 @@
#include <fstream>
#include <optional>
#include <span>
-
-#include <getoptpp/getoptpp.hpp>
+#include <cstring>
#include "execute.h"
#include "ctor.h"
@@ -18,6 +17,7 @@
#include "externals.h"
#include "tools.h"
#include "util.h"
+#include "argparser.h"
const std::filesystem::path configurationFile("configuration.cc");
const std::filesystem::path configHeaderFile("config.h");
@@ -236,8 +236,7 @@ int regenerateCache(ctor::settings& settings,
{
Args vargs(args);
- dg::Options opt;
- int key{128};
+ arg::Parser<std::string> opt(static_cast<int>(vargs.size()), vargs.data());
std::string build_arch_prefix;
std::string build_path;
@@ -251,91 +250,103 @@ int regenerateCache(ctor::settings& settings,
std::string ctor_libdir;
std::string builddir;
- opt.add("build-dir", required_argument, 'b',
- "Set output directory for build files (default: '" +
- settings.builddir + "').",
- [&]() {
- settings.builddir = optarg;
- builddir = optarg;
+ opt.add('b', "--build-dir",
+ std::function([&](std::string arg)
+ {
+ settings.builddir = arg;
+ builddir = arg;
return 0;
- });
+ }),
+ "Set output directory for build files (default: '" +
+ settings.builddir + "').");
- opt.add("verbose", no_argument, 'v',
- "Be verbose. Add multiple times for more verbosity.",
- [&]() {
+ opt.add('v', "--verbose",
+ std::function([&]()
+ {
settings.verbose++;
return 0;
- });
+ }),
+ "Be verbose. Add multiple times for more verbosity.");
- opt.add("cc", required_argument, key++,
- "Use specified c-compiler instead of gcc.",
- [&]() {
- cc_prog = optarg;
+ opt.add({}, "--cc",
+ std::function([&](std::string arg)
+ {
+ cc_prog = arg;
return 0;
- });
+ }),
+ "Use specified c-compiler instead of gcc.");
- opt.add("cxx", required_argument, key++,
- "Use specified c++-compiler instead of g++.",
- [&]() {
- cxx_prog = optarg;
+ opt.add({}, "--cxx",
+ std::function([&](std::string arg)
+ {
+ cxx_prog = arg;
return 0;
- });
+ }),
+ "Use specified c++-compiler instead of g++.");
- opt.add("ar", required_argument, key++,
- "Use specified archiver instead of ar.",
- [&]() {
- ar_prog = optarg;
+ opt.add({}, "--ar",
+ std::function([&](std::string arg)
+ {
+ ar_prog = arg;
return 0;
- });
+ }),
+ "Use specified archiver instead of ar.");
- opt.add("ld", required_argument, key++,
- "Use specified linker instead of ld.",
- [&]() {
- ld_prog = optarg;
+ opt.add({}, "--ld",
+ std::function([&](std::string arg)
+ {
+ ld_prog = arg;
return 0;
- });
+ }),
+ "Use specified linker instead of ld.");
- opt.add("build", required_argument, key++,
- "Configure for building on specified architecture.",
- [&]() {
- build_arch_prefix = optarg;
+ opt.add({}, "--build",
+ std::function([&](std::string arg)
+ {
+ build_arch_prefix = arg;
return 0;
- });
+ }),
+ "Configure for building on specified architecture.");
- opt.add("build-path", required_argument, key++,
- "Set path to build tool-chain.",
- [&]() {
- build_path = optarg;
+ opt.add({}, "--build-path",
+ std::function([&](std::string arg)
+ {
+ build_path = arg;
return 0;
- });
+ }),
+ "Set path to build tool-chain.");
- opt.add("host", required_argument, key++,
- "Cross-compile to build programs to run on specified architecture.",
- [&]() {
- host_arch_prefix = optarg;
+ opt.add({}, "--host",
+ std::function([&](std::string arg)
+ {
+ host_arch_prefix = arg;
return 0;
- });
+ }),
+ "Cross-compile to build programs to run on specified architecture.");
- opt.add("host-path", required_argument, key++,
- "Set path to cross-compile tool-chain.",
- [&]() {
- host_path = optarg;
+ opt.add({}, "--host-path",
+ std::function([&](std::string arg)
+ {
+ host_path = arg;
return 0;
- });
+ }),
+ "Set path to cross-compile tool-chain.");
- opt.add("ctor-includedir", required_argument, key++,
- "Set path to ctor header file, used for re-compiling.",
- [&]() {
- ctor_includedir = optarg;
+ opt.add({}, "--ctor-includedir",
+ std::function([&](std::string arg)
+ {
+ ctor_includedir = arg;
return 0;
- });
+ }),
+ "Set path to ctor header file, used for re-compiling.");
- opt.add("ctor-libdir", required_argument, key++,
- "Set path to ctor library file, used for re-compiling.",
- [&]() {
- ctor_libdir = optarg;
+ opt.add({}, "--ctor-libdir",
+ std::function([&](std::string arg)
+ {
+ ctor_libdir = arg;
return 0;
- });
+ }),
+ "Set path to ctor library file, used for re-compiling.");
// Resolv externals
ctor::external_configurations externalConfigs;
@@ -350,19 +361,21 @@ int regenerateCache(ctor::settings& settings,
auto add_path_args =
[&](const std::string& arg_name)
{
- opt.add(arg_name + "-includedir", required_argument, key++,
- "Set path to " + arg_name + " header file.",
- [&]() {
- external_includedir[arg_name] = optarg;
+ opt.add({}, "--" + arg_name + "-includedir",
+ std::function([&](std::string arg)
+ {
+ external_includedir[arg_name] = arg;
return 0;
- });
+ }),
+ "Set path to " + arg_name + " header file.");
- opt.add(arg_name + "-libdir", required_argument, key++,
- "Set path to " + arg_name + " libraries.",
- [&]() {
- external_libdir[arg_name] = optarg;
+ opt.add({}, "--" + arg_name + "-libdir",
+ std::function([&](std::string arg)
+ {
+ external_libdir[arg_name] = arg;
return 0;
- });
+ }),
+ "Set path to " + arg_name + " libraries.");
};
for(const auto& ext : externalConfigs)
@@ -382,17 +395,57 @@ int regenerateCache(ctor::settings& settings,
}
- opt.add("help", no_argument, 'h',
- "Print this help text.",
- [&]() -> int {
+ opt.add('h', "--help",
+ std::function([&]() -> int
+ {
std::cout << "Configure how to build with " << name << "\n";
std::cout << "Usage: " << name << " configure [options]\n\n";
std::cout << "Options:\n";
opt.help();
exit(0);
- });
+ }),
+ "Print this help text.");
+
+ opt.set_err_cb(
+ [&](arg::error err, std::string_view arg)
+ {
+ switch(err)
+ {
+ case arg::error::invalid_arg:
+ std::cerr << opt.prog_name() <<
+ ": invalid argument for option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+
+ case arg::error::missing_arg:
+ std::cerr << opt.prog_name() << ": option requires and argument '" <<
+ arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
- opt.process(static_cast<int>(vargs.size()), vargs.data());
+ case arg::error::invalid_opt:
+ std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+ }
+ });
+
+ opt.set_pos_cb(
+ [&](std::string_view)
+ {
+ std::cerr <<
+ "The configure subcommand doesn't use positional arguments.\n";
+ return 1;
+ });
+
+ auto res = opt.parse();
+ if(res != 0)
+ {
+ return res;
+ }
if(host_arch_prefix.empty())
{
diff --git a/src/getoptpp b/src/getoptpp
deleted file mode 160000
-Subproject 5aba94355ec638c6f8612f86be309ed684979ae
diff --git a/src/libctor.cc b/src/libctor.cc
index 3bfa041..1538370 100644
--- a/src/libctor.cc
+++ b/src/libctor.cc
@@ -17,7 +17,6 @@
#include <cstdlib>
#include <span>
-#include <getoptpp/getoptpp.hpp>
#include "ctor.h"
#include "configure.h"
@@ -25,6 +24,7 @@
#include "tasks.h"
#include "build.h"
#include "unittest.h"
+#include "argparser.h"
int main(int argc, char* argv[])
{
@@ -58,115 +58,120 @@ int main(int argc, char* argv[])
bool list_targets{false};
bool no_relaunch{false}; // true means no re-launch after rebuild.
bool run_unittests{false};
-
- dg::Options opt;
- int key{128};
-
- opt.add("jobs", required_argument, 'j',
- "Number of parallel jobs. (default: cpucount * 2 - 1)",
- [&]() {
- try
- {
- settings.parallel_processes =
- static_cast<std::size_t>(std::stoi(optarg));
- }
- catch(...)
- {
- std::cerr << "Not a number\n";
- return 1;
- }
+ std::vector<std::string> arguments;
+ arg::Parser<int, std::string> opt(argc, argv);
+ opt.set_pos_cb(
+ [&](std::string_view arg)
+ {
+ arguments.emplace_back(std::string(arg));
+ return 0;
+ });
+
+ opt.add('j', "--jobs",
+ std::function([&](int jobs)
+ {
+ settings.parallel_processes = static_cast<std::size_t>(jobs);
return 0;
- });
+ }),
+ "Number of parallel jobs. (default: cpucount * 2 - 1)");
- opt.add("build-dir", required_argument, 'b',
- "Overload output directory for build files (default: '" +
- settings.builddir + "').",
- [&]() {
- settings.builddir = optarg;
+ opt.add('b', "--build-dir",
+ std::function([&](std::string builddir) {
+ settings.builddir = builddir;
return 0;
- });
+ }),
+ "Overload output directory for build files (default: '" +
+ settings.builddir + "').");
- opt.add("verbose", no_argument, 'v',
- "Be verbose. Add multiple times for more verbosity.",
- [&]() {
+ opt.add('v', "--verbose",
+ std::function([&]()
+ {
settings.verbose++;
return 0;
- });
+ }),
+ "Be verbose. Add multiple times for more verbosity.");
- opt.add("quiet", no_argument, 'q',
- "Be completely silent.",
- [&]() {
+ opt.add('q', "--quiet",
+ std::function([&]() {
settings.verbose = -1;
return 0;
- });
+ }),
+ "Be completely silent.");
- opt.add("add", required_argument, 'a',
- "Add specified file to the build configurations.",
- [&]() {
+ opt.add('a', "--add",
+ std::function([&](std::string filename) {
no_relaunch = true;
- add_files.emplace_back(optarg);
+ add_files.emplace_back(filename);
return 0;
- });
+ }),
+ "Add specified file to the build configurations.");
- opt.add("remove", required_argument, 'r',
- "Remove specified file from the build configurations.",
- [&]() {
+ opt.add('r', "--remove",
+ std::function([&](std::string filename)
+ {
no_relaunch = true;
- remove_files.emplace_back(optarg);
+ remove_files.emplace_back(filename);
return 0;
- });
+ }),
+ "Remove specified file from the build configurations.");
- opt.add("list-files", no_argument, 'L',
- "List files in the build configurations.",
- [&]() {
+ opt.add('L', "--list-files",
+ std::function([&]()
+ {
no_relaunch = true;
list_files = true;
return 0;
- });
+ }),
+ "List files in the build configurations.");
- opt.add("list-targets", no_argument, 'l',
- "List targets.",
- [&]() {
+ opt.add('l', "--list-targets",
+ std::function([&]()
+ {
no_relaunch = true;
list_targets = true;
return 0;
- });
+ }),
+ "List targets.");
- opt.add("dry-run", no_argument, 'n',
- "Print the commands that would be executed, but do not execute them.",
- [&]() {
+ opt.add('n', "--dry-run",
+ std::function([&]()
+ {
settings.dry_run = true;
return 0;
- });
+ }),
+ "Print the commands to be executed, but do not execute them.");
- opt.add("configure-cmd", no_argument, key++,
- "Print commandline for last configure.",
- [&]() {
+ opt.add({}, "--configure-cmd",
+ std::function([&]()
+ {
no_relaunch = true;
print_configure_cmd = true;
return 0;
- });
+ }),
+ "Print commandline for last configure.");
- opt.add("configure-db", no_argument, key++,
- "Print entire configure parameter database.",
- [&]() {
+ opt.add({}, "--configure-db",
+ std::function([&]()
+ {
no_relaunch = true;
print_configure_db = true;
return 0;
- });
+ }),
+ "Print entire configure parameter database.");
- opt.add("database", required_argument, 'd',
- "Write compilation database json file.",
- [&]() {
+ opt.add('d', "--database",
+ std::function([&](std::string database)
+ {
no_relaunch = true;
write_compilation_database = true;
- compilation_database = optarg;
+ compilation_database = database;
return 0;
- });
+ }),
+ "Write compilation database json file.");
- opt.add("help", no_argument, 'h',
- "Print this help text.",
- [&]() -> int {
+ opt.add('h', "--help",
+ std::function([&]() -> int
+ {
std::cout << "Usage: " << args[0] << " [options] [target] ...\n";
std::cout <<
R"_( where target can be either:
@@ -181,9 +186,40 @@ Options:
)_";
opt.help();
exit(0);
- });
+ }),
+ "Print this help text.");
- opt.process(argc, argv);
+ opt.set_err_cb(
+ [&](arg::error err, std::string_view arg)
+ {
+ switch(err)
+ {
+ case arg::error::invalid_arg:
+ std::cerr << opt.prog_name() <<
+ ": invalid argument for option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+
+ case arg::error::missing_arg:
+ std::cerr << opt.prog_name() << ": option requires and argument '" <<
+ arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+
+ case arg::error::invalid_opt:
+ std::cerr << opt.prog_name() << ": invalid option '" << arg << "'\n";
+ std::cerr << "Type '" << opt.prog_name() <<
+ " -h' for more information.\n";
+ break;
+ }
+ });
+ auto res = opt.parse();
+ if(res != 0)
+ {
+ return res;
+ }
auto verbose_env = std::getenv("V");
if(verbose_env)
@@ -295,7 +331,7 @@ Options:
}
bool build_all{!no_default_build};
- for(const auto& arg : opt.arguments())
+ for(const auto& arg : arguments)
{
if(arg == "configure")
{
diff --git a/test/argparser_test.cc b/test/argparser_test.cc
new file mode 100644
index 0000000..b649e8c
--- /dev/null
+++ b/test/argparser_test.cc
@@ -0,0 +1,1019 @@
+#include <argparser.h>
+
+#include <iostream>
+#include <string>
+
+std::ostream& operator<<(std::ostream& ostr, const arg::error& err)
+{
+ switch(err)
+ {
+ case arg::error::invalid_arg:
+ ostr << "arg::error::invalid_arg";
+ break;
+ case arg::error::invalid_opt:
+ ostr << "arg::error::invalid_opt";
+ break;
+ case arg::error::missing_arg:
+ ostr << "arg::error::missing_arg";
+ break;
+ }
+ return ostr;
+}
+
+#include <uunit.h>
+
+class ArgParserTest
+ : public uUnit
+{
+public:
+ ArgParserTest()
+ {
+ uTEST(ArgParserTest::test_zero);
+ uTEST(ArgParserTest::test_one);
+ uTEST(ArgParserTest::test_many);
+ uTEST(ArgParserTest::test_exceptional);
+ uTEST(ArgParserTest::test_err_callback);
+ uTEST(ArgParserTest::test_pos_callback);
+ uTEST(ArgParserTest::test_grouped);
+ uTEST(ArgParserTest::test_nullprogram);
+ }
+
+ void test_zero()
+ {
+ const char* const argv[] = { "app-name" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ }
+
+ void test_one()
+ {
+ const char* argv[] = { "app-name", "-x", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(42, x);
+ }
+
+ void test_many()
+ {
+ const char* argv[] = { "app-name", "-x", "42", "-y17", "-z",
+ "--long-X=12", "--long-Y", "18" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+ bool z{false};
+ int X{};
+ int Y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](){ z = true; return 0;}), "Help z");
+
+ args.add('X', "--long-X",
+ std::function([&](int i){ X = i; return 0;}), "Help X");
+
+ args.add('Y', "--long-Y",
+ std::function([&](int i){ Y = i; return 0;}), "Help Y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(42, x);
+ uASSERT_EQUAL(17, y);
+ uASSERT_EQUAL(true, z);
+ uASSERT_EQUAL(12, X);
+ uASSERT_EQUAL(18, Y);
+ }
+
+ void test_exceptional()
+ {
+
+ { // Missing arg at trailing opt
+ const char* argv[] = { "app-name", "-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ }
+
+ { // Missing arg before other opt
+ const char* argv[] = { "app-name", "-x", "-y" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ }
+
+ { // Unknown arg
+ const char* argv[] = { "app-name", "-T" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ int y{};
+
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](int i){ y = i; return 0;}), "Help y");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ }
+ }
+
+ void test_err_callback()
+ {
+ using namespace std::string_literals;
+
+ { // Missing arg at trailing opt
+ const char* argv[] = { "app-name", "-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ bool called{false};
+ args.set_err_cb(
+ [&](arg::error err, std::string_view opt)
+ {
+ called = true;
+ uASSERT_EQUAL(arg::error::missing_arg, err);
+ uASSERT_EQUAL("-x"s, opt);
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ uASSERT(called);
+ }
+
+ { // Invalid arg format
+ const char* argv[] = { "app-name", "-x", "abc" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ bool called{false};
+ args.set_err_cb(
+ [&](arg::error err, std::string_view opt)
+ {
+ called = true;
+ uASSERT_EQUAL(arg::error::invalid_arg, err);
+ uASSERT_EQUAL("abc"s, opt);
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ uASSERT(called);
+ }
+
+ { // Invalid opt
+ const char* argv[] = { "app-name", "-y" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int> args(argc, argv);
+
+ int x{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ bool called{false};
+ args.set_err_cb(
+ [&](arg::error err, std::string_view opt)
+ {
+ called = true;
+ uASSERT_EQUAL(arg::error::invalid_opt, err);
+ uASSERT_EQUAL("-y"s, opt);
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(1, res);
+ uASSERT(called);
+ }
+ }
+
+ void test_pos_callback()
+ {
+ using namespace std::string_literals;
+ const char* argv[] =
+ { "app-name", "foo", "-x", "42", "bar", "-X43", "-Y", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ int x{};
+ int X{};
+ int Y{};
+ args.add('x', "--long-x",
+ std::function([&](int i){ x = i; return 0;}), "Help x");
+
+ args.add('X', "--opt-x",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(i.has_value());
+ X = *i;
+ return 0;
+ }),
+ "Help X");
+
+ args.add('Y', "--opt-y",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(!i.has_value());
+ Y = 1;
+ return 0;
+ }),
+ "Help X");
+
+ std::vector<std::string> pos;
+ args.set_pos_cb(
+ [&](std::string_view sv)
+ {
+ pos.push_back(std::string(sv));
+ return 0;
+ });
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, pos.size());
+ uASSERT_EQUAL("foo"s, pos[0]);
+ uASSERT_EQUAL("bar"s, pos[1]);
+ uASSERT_EQUAL("42"s, pos[2]);
+ uASSERT_EQUAL(42, x);
+ uASSERT_EQUAL(43, X);
+ uASSERT_EQUAL(1, Y);
+ }
+
+ void test_grouped()
+ {
+ {
+ const char* argv[] = { "app-name", "-xyz", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](int i){ z = i; return 0;}), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(42, z);
+ }
+
+ {
+ const char* argv[] = { "app-name", "-xyz42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](int i){ z = i; return 0;}), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(42, z);
+ }
+
+
+ {
+ const char* argv[] = { "app-name", "-xyz42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(i.has_value());
+ z = *i; return 0;
+ }), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(42, z);
+ }
+
+ {
+ const char* argv[] = { "app-name", "-xyz" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(!i.has_value());
+ z = 1; return 0;
+ }), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(1, z);
+ }
+
+ {
+ const char* argv[] = { "app-name", "-xyz", "42" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<int, std::optional<int>> args(argc, argv);
+
+ bool x{false};
+ bool y{false};
+ int z{};
+ args.add('x', "--long-x",
+ std::function([&](){ x = true; return 0;}), "Help x");
+
+ args.add('y', "--long-y",
+ std::function([&](){ y = true; return 0;}), "Help y");
+
+ args.add('z', "--long-z",
+ std::function([&](std::optional<int> i)
+ {
+ uASSERT(!i.has_value());
+ z = 1; return 0;
+ }), "Help z");
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT(x);
+ uASSERT(y);
+ uASSERT_EQUAL(1, z);
+ }
+ }
+
+ void test_nullprogram()
+ {
+ using namespace std::string_literals;
+ // Inspired by https://nullprogram.com/blog/2020/08/01/
+
+ //
+ // Short options
+ //
+ {
+ const char* argv[] = { "program", "-a", "-b", "-c" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<char> r;
+ args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, r.size());
+ uASSERT_EQUAL('a', r[0]);
+ uASSERT_EQUAL('b', r[1]);
+ uASSERT_EQUAL('c', r[2]);
+ }
+
+ {
+ const char* argv[] = { "program", "-abc" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<char> r;
+ args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, r.size());
+ uASSERT_EQUAL('a', r[0]);
+ uASSERT_EQUAL('b', r[1]);
+ uASSERT_EQUAL('c', r[2]);
+ }
+
+ {
+ const char* argv[] = { "program", "-acb" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<char> r;
+ args.add('a', {}, std::function([&](){ r.push_back('a'); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back('b'); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back('c'); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(3u, r.size());
+ uASSERT_EQUAL('a', r[0]);
+ uASSERT_EQUAL('c', r[1]);
+ uASSERT_EQUAL('b', r[2]);
+ }
+
+ {
+ const char* argv[] = { "program", "-i", "input.txt", "-o", "output.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('i', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("input.txt"s, r[0]);
+ uASSERT_EQUAL("output.txt"s, r[1]);
+ }
+
+ {
+ const char* argv[] = { "program", "-iinput.txt", "-ooutput.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('i', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("input.txt"s, r[0]);
+ uASSERT_EQUAL("output.txt"s, r[1]);
+ }
+
+ {
+ const char* argv[] = { "program", "-abco", "output.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {});
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("c"s, r[2]);
+ uASSERT_EQUAL("output.txt"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-abcooutput.txt" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.add('c', {}, std::function([&](){ r.push_back("c"); return 0;}), {});
+ args.add('o', {},
+ std::function([&](std::string input)
+ {
+ r.push_back(input);
+ return 0;
+ }), {});
+
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("c"s, r[2]);
+ uASSERT_EQUAL("output.txt"s, r[3]);
+ }
+
+ {
+ // Optional omitted
+ const char* argv[] = { "program", "-c" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(!c.has_value());
+ r.push_back("c");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("c"s, r[0]);
+ }
+
+ {
+ // Optional provided
+ const char* argv[] = { "program", "-cblue" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(c.has_value());
+ r.push_back(*c);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("blue"s, r[0]);
+ }
+
+ {
+ // Optional omitted (blue is a new argument)
+ const char* argv[] = { "program", "-c", "blue" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(!c.has_value());
+ r.push_back("c");
+ return 0;
+ }), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("c"s, r[0]);
+ uASSERT_EQUAL("blue"s, r[1]);
+ }
+
+ {
+ // Two seperate flags
+ const char* argv[] = { "program", "-c", "-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {});
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(!c.has_value());
+ r.push_back("c");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("c"s, r[0]);
+ uASSERT_EQUAL("x"s, r[1]);
+ }
+
+ {
+ // -c with argument "-x"
+ const char* argv[] = { "program", "-c-x" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('x', {}, std::function([&](){ r.push_back("x"); return 0;}), {});
+ args.add('c', {},
+ std::function([&](std::optional<std::string> c)
+ {
+ uASSERT(c.has_value());
+ r.push_back(*c);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("-x"s, r[0]);
+ }
+
+ {
+ const char* argv[] = { "program", "-a", "-b", "foo", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("foo"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-b", "-a", "foo", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("b"s, r[0]);
+ uASSERT_EQUAL("a"s, r[1]);
+ uASSERT_EQUAL("foo"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-a", "foo", "-b", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("foo"s, r[1]);
+ uASSERT_EQUAL("b"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "foo", "-a", "-b", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("foo"s, r[0]);
+ uASSERT_EQUAL("a"s, r[1]);
+ uASSERT_EQUAL("b"s, r[2]);
+ uASSERT_EQUAL("bar"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "foo", "bar", "-a", "-b" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(4u, r.size());
+ uASSERT_EQUAL("foo"s, r[0]);
+ uASSERT_EQUAL("bar"s, r[1]);
+ uASSERT_EQUAL("a"s, r[2]);
+ uASSERT_EQUAL("b"s, r[3]);
+ }
+
+ {
+ const char* argv[] = { "program", "-a", "-b", "--", "-x", "foo", "bar" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add('a', {}, std::function([&](){ r.push_back("a"); return 0;}), {});
+ args.add('b', {}, std::function([&](){ r.push_back("b"); return 0;}), {});
+ args.set_pos_cb(std::function([&](std::string_view pos)
+ {
+ r.push_back(std::string(pos));
+ return 0;
+ }));
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(5u, r.size());
+ uASSERT_EQUAL("a"s, r[0]);
+ uASSERT_EQUAL("b"s, r[1]);
+ uASSERT_EQUAL("-x"s, r[2]);
+ uASSERT_EQUAL("foo"s, r[3]);
+ uASSERT_EQUAL("bar"s, r[4]);
+ }
+
+ //
+ // Long options
+ //
+ {
+ const char* argv[] = { "program", "--sort" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--sort",
+ std::function([&](){ r.push_back("sort"); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("sort"s, r[0]);
+ }
+
+ {
+ const char* argv[] = { "program", "--no-sort" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--no-sort",
+ std::function([&](){ r.push_back("no-sort"); return 0;}), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(1u, r.size());
+ uASSERT_EQUAL("no-sort"s, r[0]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--output", "output.txt", "--block-size", "1024" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string, int> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--output",
+ std::function([&](std::string output)
+ {
+ r.push_back(output);
+ return 0;
+ }), {});
+ args.add({}, "--block-size",
+ std::function([&](int block_size)
+ {
+ r.push_back("["s + std::to_string(block_size) + "]"s);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("output.txt"s, r[0]);
+ uASSERT_EQUAL("[1024]"s, r[1]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--output=output.txt", "--block-size=1024" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::string, int> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--output",
+ std::function([&](std::string output)
+ {
+ r.push_back(output);
+ return 0;
+ }), {});
+ args.add({}, "--block-size",
+ std::function([&](int block_size)
+ {
+ r.push_back("["s + std::to_string(block_size) + "]"s);
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("output.txt"s, r[0]);
+ uASSERT_EQUAL("[1024]"s, r[1]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--color", "--reverse" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--color",
+ std::function([&](std::optional<std::string> color)
+ {
+ uASSERT(!color.has_value());
+ r.push_back("color");
+ return 0;
+ }), {});
+ args.add({}, "--reverse",
+ std::function([&]()
+ {
+ r.push_back("reverse");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("color"s, r[0]);
+ uASSERT_EQUAL("reverse"s, r[1]);
+ }
+
+ {
+ const char* argv[] =
+ { "program", "--color=never", "--reverse" };
+ int argc = sizeof(argv)/sizeof(*argv);
+
+ arg::Parser<std::optional<std::string>> args(argc, argv);
+
+ std::vector<std::string> r;
+ args.add({}, "--color",
+ std::function([&](std::optional<std::string> color)
+ {
+ uASSERT(color.has_value());
+ r.push_back(*color);
+ return 0;
+ }), {});
+ args.add({}, "--reverse",
+ std::function([&]()
+ {
+ r.push_back("reverse");
+ return 0;
+ }), {});
+
+ auto res = args.parse();
+ uASSERT_EQUAL(0, res);
+ uASSERT_EQUAL(2u, r.size());
+ uASSERT_EQUAL("never"s, r[0]);
+ uASSERT_EQUAL("reverse"s, r[1]);
+ }
+
+ }
+};
+
+// Registers the fixture into the 'registry'
+static ArgParserTest test;
diff --git a/test/ctor.cc b/test/ctor.cc
index b7bcc6d..0d77a3e 100644
--- a/test/ctor.cc
+++ b/test/ctor.cc
@@ -12,6 +12,22 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
{
.type = ctor::target_type::unit_test,
.system = ctor::output_system::build,
+ .target = "argparser_test",
+ .sources = {
+ "argparser_test.cc",
+ "testmain.cc",
+ },
+ .flags = {
+ .cxxflags = {
+ "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"argparser\"",
+ },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
.target = "argsplit_test",
.sources = {
"argsplit_test.cc",
diff --git a/test/suite/test.sh b/test/suite/test.sh
index 97d2551..c54137a 100755
--- a/test/suite/test.sh
+++ b/test/suite/test.sh
@@ -70,8 +70,6 @@ ctor -v
# Object file should have been recompiled
MOD2=`stat $STAT_FORMAT ${BUILDDIR}/hello-hello_cc.o`
-echo $MOD1
-echo $MOD2
[[ $MOD1 == $MOD2 ]] && fail ${LINENO}
# Replacve -DFOO with -DBAR in foo external.cxxflags