From c029d90d1975e124d237605f1edb2be16bd05b5d Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 28 Feb 2017 21:50:44 +0100 Subject: Initial commit --- src/args.cc | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/args.cc (limited to 'src/args.cc') diff --git a/src/args.cc b/src/args.cc new file mode 100644 index 0000000..cde77b0 --- /dev/null +++ b/src/args.cc @@ -0,0 +1,281 @@ +// -*- mode: c++; c-basic-offset: 2; -*- + +#include "common.hh" + +#include +#include +#include +#include + +#include "args.hh" +#include "character.hh" +#include "terminal.hh" + +namespace { + +class ArgsImpl : public Args { +public: + ArgsImpl() + : good_(true) { + } + void add(char short_opt, std::string const& long_opt, + std::string const& argument, std::string const& help) override { + assert(short_opt == '\0' || short_opts_.count(short_opt) == 0); + assert(long_opt.empty() || long_opts_.count(long_opt) == 0); + assert(long_opt.find('=') == std::string::npos); + auto const index = opts_.size(); + opts_.push_back(Option(short_opt, long_opt, argument, help)); + if (short_opt != '\0') { + short_opts_.insert(std::make_pair(short_opt, index)); + } + if (!long_opt.empty()) { + long_opts_.insert(std::make_pair(long_opt, index)); + } + } + + bool run(int argc, char** argv, std::ostream& out) override { + if (argc == 0) { + assert(false); + good_ = false; + return false; + } + auto start = strrchr(argv[0], '/'); + if (!start) { + start = argv[0]; + } else { + start++; + } + return run(start, argc, argv, out); + } + + bool run(std::string const& prg, int argc, char** argv, std::ostream& out) + override { + reset(); + + std::string opt; + for (int a = 1; a < argc; ++a) { + if (argv[a][0] == '-') { + if (argv[a][1] == '-') { + if (argv[a][2] == '\0') { + for (++a; a < argc; ++a) { + args_.push_back(argv[a]); + } + return good_; + } + size_t len = 2; + while (argv[a][len] && argv[a][len] != '=') ++len; + opt.assign(argv[a] + 2, len - 2); + auto i = long_opts_.find(opt); + if (i == long_opts_.end()) { + out << prg << ": unrecognized option '--" << opt << "'\n"; + good_ = false; + continue; + } + if (argv[a][len] == '=') { + if (opts_[i->second].argument.empty()) { + out << prg << ": option '--" << opt << "'" + << " doesn't allow an argument\n"; + good_ = false; + continue; + } else { + opts_[i->second].value = argv[a] + len + 1; + opts_[i->second].is_set = true; + } + } else { + if (opts_[i->second].argument.empty()) { + opts_[i->second].is_set = true; + } else if (a + 1 == argc) { + out << prg << ": option '--" << opt << "'" + << " requires an argument\n"; + good_ = false; + continue; + } else { + opts_[i->second].value = argv[++a]; + opts_[i->second].is_set = true; + } + } + } else { + for (auto opt = argv[a] + 1; *opt; ++opt) { + auto i = short_opts_.find(*opt); + if (i == short_opts_.end()) { + out << prg << ": invalid option -- '" << *opt << "'\n"; + good_ = false; + continue; + } + if (opts_[i->second].argument.empty()) { + opts_[i->second].is_set = true; + } else if (a + 1 == argc) { + out << prg << ": option requires an argument " + << " -- '" << *opt << "'\n"; + good_ = false; + continue; + } else { + opts_[i->second].value = argv[++a]; + opts_[i->second].is_set = true; + } + } + } + } else { + args_.push_back(argv[a]); + } + } + + return good_; + } + bool good() const override { + return good_; + } + + bool is_set(char short_opt) const override { + auto i = short_opts_.find(short_opt); + if (i == short_opts_.end()) return false; + return opts_[i->second].is_set; + } + bool is_set(std::string const& long_opt) const override { + auto i = long_opts_.find(long_opt); + if (i == long_opts_.end()) return false; + return opts_[i->second].is_set; + } + char const* arg(char short_opt, char const* fallback) const override { + auto i = short_opts_.find(short_opt); + if (i == short_opts_.end()) return fallback; + if (!opts_[i->second].is_set) return fallback; + if (opts_[i->second].argument.empty()) return fallback; + return opts_[i->second].value.c_str(); + } + char const* arg(std::string const& long_opt, + char const* fallback) const override { + auto i = long_opts_.find(long_opt); + if (i == long_opts_.end()) return fallback; + if (!opts_[i->second].is_set) return fallback; + if (opts_[i->second].argument.empty()) return fallback; + return opts_[i->second].value.c_str(); + } + + std::vector const& arguments() const override { + return args_; + } + + void print_help(std::ostream& out) const override { + print_help(out, Terminal::size().width); + } + + void print_help(std::ostream& out, size_t width) const override { + size_t left = 0; + for (auto const& opt : opts_) { + size_t l = 0; + if (!opt.long_opt.empty()) { + l += 6 + opt.long_opt.size(); + } else if (opt.short_opt != '\0') { + l += 2; + } else { + continue; + } + if (!opt.argument.empty()) { + l += 1 + opt.argument.size(); + } + if (l > left) left = l; + } + + size_t const need = 2 + 2 + left; + if (need + 10 > width) { + width = need + 10; + } + size_t const right = width - need; + + for (auto const& opt : opts_) { + size_t i = 0; + if (!opt.long_opt.empty()) { + if (opt.short_opt != '\0') { + out << " -" << opt.short_opt << ", "; + } else { + out << " "; + } + out << "--" << opt.long_opt; + i += 8 + opt.long_opt.size(); + } else if (opt.short_opt != '\0') { + out << " -" << opt.short_opt; + i += 4; + } else { + continue; + } + if (!opt.argument.empty()) { + out << '=' << opt.argument; + i += 1 + opt.argument.size(); + } + pad(out, need - i); + if (opt.help.size() < right) { + out << opt.help << '\n'; + } else { + i = right; + while (i > 0 && !Character::isseparator(opt.help, i)) --i; + if (i == 0) i = right; + out << opt.help.substr(0, i) << '\n'; + while (true) { + while (i < opt.help.size() && Character::isspace(opt.help, i)) ++i; + if (i == opt.help.size()) break; + size_t j = right - 2; + pad(out, width - j); + if (i + j >= opt.help.size()) { + out << opt.help.substr(i) << '\n'; + break; + } + while (j > 0 && !Character::isseparator(opt.help, i + j)) --j; + if (j == 0) j = right - 2; + out << opt.help.substr(i, j) << '\n'; + i += j; + } + } + } + } + +private: + struct Option { + char const short_opt; + std::string const long_opt; + std::string const argument; + std::string const help; + + bool is_set; + std::string value; + + Option(char short_opt, std::string const& long_opt, + std::string const& argument, std::string const& help) + : short_opt(short_opt), long_opt(long_opt), argument(argument), + help(help), is_set(false) { + } + }; + bool good_; + std::unordered_map short_opts_; + std::unordered_map long_opts_; + std::vector