diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/argparser.h | 477 | ||||
-rw-r--r-- | src/configure.cc | 213 | ||||
m--------- | src/getoptpp | 0 | ||||
-rw-r--r-- | src/libctor.cc | 184 |
4 files changed, 720 insertions, 154 deletions
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") { |