// -*- 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