summaryrefslogtreecommitdiff
path: root/src/args.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2017-02-28 21:50:44 +0100
committerJoel Klinghed <the_jk@yahoo.com>2017-02-28 21:50:44 +0100
commitc029d90d1975e124d237605f1edb2be16bd05b5d (patch)
tree9df87ffb365354bdb74a969440b32c8304bdbcb7 /src/args.cc
Initial commit
Diffstat (limited to 'src/args.cc')
-rw-r--r--src/args.cc281
1 files changed, 281 insertions, 0 deletions
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 <cstring>
+#include <string>
+#include <vector>
+#include <unordered_map>
+
+#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<std::string> 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<char, size_t> short_opts_;
+ std::unordered_map<std::string, size_t> long_opts_;
+ std::vector<Option> opts_;
+ std::vector<std::string> args_;
+
+ void reset() {
+ good_ = true;
+ args_.clear();
+ for (auto& opt : opts_) {
+ opt.is_set = false;
+ opt.value.clear();
+ }
+ }
+
+ static void pad(std::ostream& out, size_t count) {
+ while (count > 4) {
+ out << " ";
+ count -= 4;
+ }
+ while (count) {
+ out << ' ';
+ --count;
+ }
+ }
+};
+
+} // namespace
+
+// static
+Args* Args::create() {
+ return new ArgsImpl();
+}
+