summaryrefslogtreecommitdiff
path: root/src/args.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2017-09-26 20:09:31 +0200
committerJoel Klinghed <the_jk@yahoo.com>2017-09-26 20:09:31 +0200
commitc85b624d28564a6f785b25000e2b7825592a919d (patch)
tree647b756c824b470b35f1371eb869e9534ed6c1bb /src/args.cc
Initial commit
Diffstat (limited to 'src/args.cc')
-rw-r--r--src/args.cc323
1 files changed, 323 insertions, 0 deletions
diff --git a/src/args.cc b/src/args.cc
new file mode 100644
index 0000000..9b3e409
--- /dev/null
+++ b/src/args.cc
@@ -0,0 +1,323 @@
+#include "common.hh"
+
+#include <string.h>
+#include <string>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
+
+#include "args.hh"
+
+namespace {
+
+bool is_space(std::string const& str, size_t offset) {
+ return str[offset] == ' ' || str[offset] == '\t' || str[offset] == '\n';
+}
+
+bool is_separator(std::string const& str, size_t offset) {
+ return !((str[offset] >= '0' && str[offset] <= '9')
+ || (str[offset] >= 'a' && str[offset] <= 'z')
+ || (str[offset] >= 'A' && str[offset] <='Z'));
+}
+
+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].values.push_back(argv[a] + len + 1);
+ }
+ } else {
+ if (opts_[i->second].argument.empty()) {
+ opts_[i->second].values.push_back("");
+ } else if (a + 1 == argc) {
+ out << prg << ": option '--" << opt << "'"
+ << " requires an argument\n";
+ good_ = false;
+ continue;
+ } else {
+ opts_[i->second].values.push_back(argv[++a]);
+ }
+ }
+ } 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].values.push_back("");
+ } else if (a + 1 == argc) {
+ out << prg << ": option requires an argument "
+ << " -- '" << *opt << "'\n";
+ good_ = false;
+ continue;
+ } else {
+ opts_[i->second].values.push_back(argv[++a]);
+ }
+ }
+ }
+ } else {
+ args_.push_back(argv[a]);
+ }
+ }
+
+ return good_;
+ }
+ bool good() const override {
+ return good_;
+ }
+
+ bool is_set(char short_opt) const override {
+ return count(short_opt) > 0;
+ }
+ size_t count(char short_opt) const override {
+ auto i = short_opts_.find(short_opt);
+ if (i == short_opts_.end()) return 0;
+ return opts_[i->second].values.size();
+ }
+
+ bool is_set(std::string const& long_opt) const override {
+ return count(long_opt) > 0;
+ }
+ size_t count(std::string const& long_opt) const override {
+ auto i = long_opts_.find(long_opt);
+ if (i == long_opts_.end()) return 0;
+ return opts_[i->second].values.size();
+ }
+
+ 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].values.empty()) return fallback;
+ if (opts_[i->second].argument.empty()) return fallback;
+ return opts_[i->second].values.back().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].values.empty()) return fallback;
+ if (opts_[i->second].argument.empty()) return fallback;
+ return opts_[i->second].values.back().c_str();
+ }
+
+ bool args(char short_opt, std::vector<std::string>* out) const override {
+ auto i = short_opts_.find(short_opt);
+ if (i == short_opts_.end()) return false;
+ if (opts_[i->second].values.empty()) return false;
+ if (opts_[i->second].argument.empty()) return false;
+ if (out) {
+ *out = opts_[i->second].values;
+ }
+ return true;
+ }
+
+ bool args(std::string const& long_opt,
+ std::vector<std::string>* out) const override {
+ auto i = long_opts_.find(long_opt);
+ if (i == long_opts_.end()) return false;
+ if (opts_[i->second].values.empty()) return false;
+ if (opts_[i->second].argument.empty()) return false;
+ if (out) {
+ *out = opts_[i->second].values;
+ }
+ return true;
+ }
+
+ std::vector<std::string> const& arguments() const override {
+ return args_;
+ }
+
+ void print_help(std::ostream& out) const override {
+ struct winsize size;
+ memset(&size, 0, sizeof(size));
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
+ if (size.ws_col == 0) {
+ print_help(out, 80);
+ } else {
+ print_help(out, size.ws_col);
+ }
+ }
+
+ 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 && !is_separator(opt.help, i)) --i;
+ if (i == 0) i = right;
+ out << opt.help.substr(0, i) << '\n';
+ while (true) {
+ while (i < opt.help.size() && is_space(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 && !is_separator(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;
+
+ std::vector<std::string> values;
+
+ 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) {
+ }
+ };
+ 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.values.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();
+}
+