| /* |
| * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #ifndef _CLI_H |
| #define _CLI_H |
| |
| #include <algorithm> |
| #include <exception> |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| #include <cassert> |
| #include <iostream> |
| #include <numeric> |
| #include <sstream> |
| #include <limits> |
| |
| /** |
| * Note this is a hastily hacked together command line parser. |
| * |
| * I like the syntax of clipp; but it seemed really buggy, and writing something less functional |
| * with similar syntax seemed like the quickest way to go. |
| * |
| * Ironically this is probably just as buggy off the happy path as clipp appeared to be, but |
| * in this case the happy path is ours! |
| */ |
| namespace cli { |
| |
| typedef std::string string; |
| template<typename T> using vector = std::vector<T>; |
| template<typename A, typename B> using map = std::map<A, B>; |
| template<typename A, typename B> using pair = std::pair<A, B>; |
| template<typename T> using shared_ptr = std::shared_ptr<T>; |
| |
| auto join = [](const vector<string> &range, const string &separator) { |
| if (range.empty()) return string(); |
| |
| return accumulate( |
| next(begin(range)), // there is at least 1 element, so OK. |
| end(range), |
| |
| range[0], // the initial value |
| |
| [&separator](auto result, const auto &value) { |
| return result + separator + value; |
| }); |
| }; |
| |
| struct parse_error : public std::exception { |
| explicit parse_error(string what) : _what(std::move(what)) {} |
| |
| const char *what() const noexcept override { |
| return _what.c_str(); |
| } |
| |
| private: |
| string _what; |
| }; |
| |
| struct group; |
| template<typename T> |
| struct matchable_derived; |
| |
| template <typename K, typename V> struct map_and_order { |
| map<K,V> _map; |
| vector<K> _order; |
| |
| V& operator[](const K& key) { |
| auto i = _map.find(key); |
| if (i == _map.end()) { |
| _order.push_back(key); |
| } |
| return _map[key]; |
| } |
| |
| vector<K> ordered_keys() { |
| return _order; |
| } |
| }; |
| |
| struct option_map { |
| typedef map_and_order<string, map_and_order<string, vector<pair<string, string>>>> container; |
| |
| void add(const string& major_group, const string& minor_group, const string& option, const string& description) { |
| auto &v = contents[major_group][minor_group]; |
| // we don't want to repeat the same option |
| if (std::find_if(v.begin(), v.end(), [&](const auto &x) { return x.first == option; }) == v.end()) { |
| v.emplace_back(option, description); |
| } |
| } |
| |
| container contents; |
| }; |
| |
| struct matchable; |
| |
| enum struct match_type { |
| not_yet, |
| match, |
| error, |
| no_match, |
| }; |
| |
| struct opaque_settings { |
| virtual shared_ptr<opaque_settings> copy() = 0; |
| virtual void save_into() = 0; |
| virtual void apply_from() = 0; |
| }; |
| |
| struct settings_holder { |
| explicit settings_holder(shared_ptr<opaque_settings> settings) : settings(settings) {} |
| settings_holder(const settings_holder &other) { |
| settings = other.settings->copy(); |
| } |
| settings_holder& operator=(const settings_holder&) = default; |
| void save_into() { |
| settings->save_into(); |
| } |
| void apply_from() { |
| settings->apply_from(); |
| } |
| shared_ptr<opaque_settings> settings; |
| }; |
| |
| struct match_state { |
| vector<string> remaining_args; |
| string error_message; |
| int match_count = 0; |
| int error_count = 0; |
| // if we are an error for something mising; we should rather report on an up next |
| // unsupported option than our error message |
| bool prefer_unknown_option_message = false; |
| std::map<const matchable *, int> matchable_counts; |
| settings_holder settings; |
| |
| match_state(const settings_holder& settings) : settings(settings) {} |
| |
| void apply_settings_from() { |
| settings.apply_from(); |
| } |
| void save_settings_into() { |
| settings.save_into(); |
| } |
| |
| match_type match_value(const matchable *matchable, std::function<bool(const string&)> filter); |
| |
| match_type check_min_max(const matchable *matchable); |
| |
| int get_match_count(const std::shared_ptr<matchable>& element) { |
| return matchable_counts[element.get()]; |
| } |
| |
| match_type update_stats(match_type type, const matchable *matchable) { |
| assert(type != match_type::not_yet); |
| if (type == match_type::match) { |
| match_count++; |
| matchable_counts[matchable]++; |
| } else if (type == match_type::error) { |
| error_count++; |
| matchable_counts[matchable]++; |
| } |
| return type; |
| } |
| |
| match_type match_if_equal(const matchable *matchable, const string& s); |
| }; |
| |
| struct matcher { |
| }; |
| |
| struct matchable { |
| matchable() = default; |
| virtual ~matchable() = default; |
| |
| explicit matchable(string name) : _name(std::move(name)) {} |
| |
| std::function<string(string)> action = [](const string&) { return ""; }; |
| |
| std::function<string()> missing; |
| |
| virtual match_type match(match_state& m) const { return match_type::no_match; } |
| |
| string name() const { |
| return _name; |
| } |
| |
| virtual std::vector<string> synopsys() const { |
| return {_name}; |
| } |
| |
| virtual bool is_optional() const { |
| return !_min; |
| } |
| |
| bool doc_non_optional() const { |
| return _doc_non_optional; |
| } |
| |
| bool force_expand_help() const { |
| return _force_expand_help; |
| } |
| |
| string collapse_synopsys() const { |
| return _collapse_synopsys; |
| } |
| |
| string doc() const { |
| return _doc; |
| } |
| |
| virtual bool get_option_help(string major_group, string minor_group, option_map &options) const { |
| return false; |
| } |
| |
| int min() const { |
| return _min; |
| } |
| |
| int max() const { |
| return _max; |
| } |
| |
| protected: |
| string _name; |
| string _doc; |
| int _min = 1; |
| int _max = 1; |
| bool _doc_non_optional = false; |
| bool _force_expand_help = false; |
| string _collapse_synopsys = ""; |
| }; |
| |
| template<typename D> |
| struct matchable_derived : public matchable { |
| matchable_derived() = default; |
| explicit matchable_derived(string name) : matchable(std::move(name)) {} |
| |
| D &on_action(std::function<string(const string&)> action) { |
| this->action = action; |
| return *static_cast<D *>(this); |
| } |
| |
| D &if_missing(std::function<string()> missing) { |
| this->missing = missing; |
| return *static_cast<D *>(this); |
| } |
| |
| D &operator%(const string& doc) { |
| _doc = doc; |
| return *static_cast<D *>(this); |
| } |
| |
| D &required() { |
| _min = 1; |
| _max = std::max(_min, _max); |
| return *static_cast<D *>(this); |
| } |
| |
| D &repeatable() { |
| _max = std::numeric_limits<int>::max(); |
| return *static_cast<D *>(this); |
| } |
| |
| D &min(int v) { |
| _min = v; |
| return *static_cast<D *>(this); |
| } |
| |
| D &doc_non_optional(bool v) { |
| _doc_non_optional = v; |
| return *static_cast<D *>(this); |
| } |
| |
| D &force_expand_help(bool v) { |
| _force_expand_help = v; |
| return *static_cast<D *>(this); |
| } |
| |
| D &collapse_synopsys(string v) { |
| _collapse_synopsys = v; |
| return *static_cast<D *>(this); |
| } |
| |
| D &max(int v) { |
| _max = v; |
| return *static_cast<D *>(this); |
| } |
| std::shared_ptr<matchable> to_ptr() const { |
| return std::shared_ptr<matchable>(new D(*static_cast<const D *>(this))); |
| } |
| |
| template<typename T> |
| group operator&(const matchable_derived<T> &m); |
| template<typename T> |
| group operator|(const matchable_derived<T> &m); |
| template<typename T> |
| group operator+(const matchable_derived<T> &m); |
| }; |
| |
| template<typename D> |
| struct value_base : public matchable_derived<D> { |
| std::function<bool(const string&)> exclusion_filter = [](const string &x){return false;}; |
| |
| explicit value_base(string name) : matchable_derived<D>(std::move(name)) { |
| this->_min = 1; |
| this->_max = 1; |
| } |
| |
| vector<string> synopsys() const override { |
| string s = string("<") + this->_name + ">"; |
| if (this->_max > 1) s += ".."; |
| return {s}; |
| } |
| |
| bool get_option_help(string major_group, string minor_group, option_map &options) const override { |
| if (this->doc().empty()) { |
| return false; |
| } |
| options.add(major_group, minor_group, string("<") + this->_name + ">", this->doc()); |
| return true; |
| } |
| |
| match_type match(match_state& ms) const override { |
| match_type rc = ms.check_min_max(this); |
| if (rc == match_type::not_yet) { |
| rc = ms.match_value(this, exclusion_filter); |
| } |
| return rc; |
| } |
| |
| D &with_exclusion_filter(std::function<bool(const string&)> exclusion_filter) { |
| this->exclusion_filter = exclusion_filter; |
| return *static_cast<D *>(this); |
| } |
| }; |
| |
| struct option : public matchable_derived<option> { |
| explicit option(char short_opt) : option(short_opt, "") {} |
| |
| explicit option(string _long_opt) : option(0, std::move(_long_opt)) {} |
| |
| option(char _short_opt, string _long_opt) { |
| _min = 0; |
| short_opt = _short_opt ? "-" + string(1, _short_opt) : ""; |
| long_opt = std::move(_long_opt); |
| _name = short_opt.empty() ? long_opt : short_opt; |
| } |
| |
| bool get_option_help(string major_group, string minor_group, option_map &options) const override { |
| if (doc().empty()) return false; |
| string label = short_opt.empty() ? "" : _name; |
| if (!long_opt.empty()) { |
| if (!label.empty()) label += ", "; |
| label += long_opt; |
| } |
| options.add(major_group, minor_group, label, doc()); |
| return true; |
| } |
| |
| template<typename T> |
| option &set(T &t) { |
| // note we cannot capture "this" |
| on_action([&t](const string& value) { |
| t = true; |
| return ""; |
| }); |
| return *this; |
| } |
| |
| template<typename T> |
| option &clear(T &t) { |
| // note we cannot capture "this" |
| on_action([&t](const string& value) { |
| t = false; |
| return ""; |
| }); |
| return *this; |
| } |
| |
| match_type match(match_state &ms) const override { |
| match_type rc = ms.match_if_equal(this, short_opt); |
| if (rc == match_type::no_match) { |
| rc = ms.match_if_equal(this, long_opt); |
| } |
| return rc; |
| } |
| |
| private: |
| string short_opt; |
| string long_opt; |
| }; |
| |
| struct value : public value_base<value> { |
| explicit value(string name) : value_base(std::move(name)) {} |
| |
| template<typename T> |
| value &set(T &t) { |
| on_action([&](const string& value) { |
| t = value; |
| return ""; |
| }); |
| return *this; |
| } |
| template<typename T> value &add_to(T &t) { |
| // note we cannot capture "this" |
| on_action([&t](const string& value) { |
| t.push_back(value); |
| return ""; |
| }); |
| return *this; |
| } |
| }; |
| |
| struct integer : public value_base<integer> { |
| explicit integer(string name) : value_base(std::move(name)) {} |
| |
| template<typename T> |
| static std::string parse_string(std::string value, T& out) { |
| size_t pos = 0; |
| long lvalue = std::numeric_limits<long>::max(); |
| int64_t base = 10; |
| if (value.find("0x") == 0) { |
| value = value.substr(2); |
| base = 16; |
| } else if (value.find("0b") == 0) { |
| value = value.substr(2); |
| base = 2; |
| } |
| try { |
| lvalue = std::stoll(value, &pos, base); |
| if (pos != value.length()) { |
| return "Garbage after integer value: " + value.substr(pos); |
| } |
| } catch (std::invalid_argument&) { |
| return value + " is not a valid integer"; |
| } catch (std::out_of_range&) { |
| } |
| if (lvalue != (int64_t)lvalue) { |
| return value + " is too big"; |
| } |
| out = (int64_t)lvalue; |
| return ""; |
| } |
| |
| template<typename T> |
| integer &set(T &t) { |
| int64_t min = _min_value; |
| int64_t max = _max_value; |
| int64_t invalid_bits = _invalid_bits; |
| std::string invalid_bits_error = _invalid_bits_error; |
| string nm = "<" + name() + ">"; |
| // note we cannot capture "this" |
| on_action([&t, min, max, nm, invalid_bits, invalid_bits_error](const string& value) { |
| int64_t tmp = 0; |
| std::string err = parse_string(value, tmp); |
| t = tmp; |
| if (!err.empty()) return err; |
| if (t < min) { |
| return nm + " must be >= " + std::to_string(min); |
| } |
| if (t > max) { |
| return nm + " must be <= " + std::to_string(max); |
| } |
| if (t & invalid_bits) { |
| return nm + " " + invalid_bits_error; |
| } |
| return string(""); |
| }); |
| return *this; |
| } |
| |
| template<typename T> |
| integer &add_to(T &t) { |
| int64_t min = _min_value; |
| int64_t max = _max_value; |
| int64_t invalid_bits = _invalid_bits; |
| std::string invalid_bits_error = _invalid_bits_error; |
| string nm = "<" + name() + ">"; |
| // note we cannot capture "this" |
| on_action([&t, min, max, nm, invalid_bits, invalid_bits_error](const string& value) { |
| int64_t tmp = 0; |
| std::string err = parse_string(value, tmp); |
| if (!err.empty()) return err; |
| if (tmp < min) { |
| return nm + " must be >= " + std::to_string(min); |
| } |
| if (tmp > max) { |
| return nm + " must be <= " + std::to_string(max); |
| } |
| if (tmp & invalid_bits) { |
| return nm + " " + invalid_bits_error; |
| } |
| t.push_back(tmp); |
| return string(""); |
| }); |
| return *this; |
| } |
| |
| integer& min_value(int64_t v) { |
| _min_value = v; |
| return *this; |
| } |
| |
| integer& max_value(int64_t v) { |
| _max_value = v; |
| return *this; |
| } |
| |
| integer& invalid_bits(int64_t bits, std::string error) { |
| _invalid_bits = bits; |
| _invalid_bits_error = error; |
| return *this; |
| } |
| |
| int64_t _min_value = 0; |
| int64_t _max_value = std::numeric_limits<int64_t>::max(); |
| std::string _invalid_bits_error; |
| int64_t _invalid_bits = 0; |
| }; |
| |
| struct hex : public value_base<hex> { |
| explicit hex(string name) : value_base(std::move(name)) {} |
| |
| template<typename T> |
| hex &set(T &t) { |
| unsigned int min = _min_value; |
| unsigned int max = _max_value; |
| string nm = "<" + name() + ">"; |
| // note we cannot capture "this" |
| on_action([&t, min, max, nm](string value) { |
| auto ovalue = value; |
| if (value.find("0x") == 0) value = value.substr(2); |
| size_t pos = 0; |
| long lvalue = std::numeric_limits<long>::max(); |
| try { |
| lvalue = std::stoul(value, &pos, 16); |
| if (pos != value.length()) { |
| return "Garbage after hex value: " + value.substr(pos); |
| } |
| } catch (std::invalid_argument&) { |
| return ovalue + " is not a valid hex value"; |
| } catch (std::out_of_range&) { |
| } |
| if (lvalue != (unsigned int)lvalue) { |
| return value + " is not a valid 32 bit value"; |
| } |
| t = (unsigned int)lvalue; |
| if (t < min) { |
| std::stringstream ss; |
| ss << nm << " must be >= 0x" << std::hex << std::to_string(min); |
| return ss.str(); |
| } |
| if (t > max) { |
| std::stringstream ss; |
| ss << nm << " must be M= 0x" << std::hex << std::to_string(min); |
| return ss.str(); |
| } |
| return string(""); |
| }); |
| return *this; |
| } |
| |
| template<typename T> |
| hex &add_to(T &t) { |
| unsigned int min = _min_value; |
| unsigned int max = _max_value; |
| string nm = "<" + name() + ">"; |
| // note we cannot capture "this" |
| on_action([&t, min, max, nm](string value) { |
| auto ovalue = value; |
| if (value.find("0x") == 0) value = value.substr(2); |
| size_t pos = 0; |
| long lvalue = std::numeric_limits<long>::max(); |
| try { |
| lvalue = std::stoul(value, &pos, 16); |
| if (pos != value.length()) { |
| return "Garbage after hex value: " + value.substr(pos); |
| } |
| } catch (std::invalid_argument&) { |
| return ovalue + " is not a valid hex value"; |
| } catch (std::out_of_range&) { |
| } |
| if (lvalue != (unsigned int)lvalue) { |
| return value + " is not a valid 32 bit value"; |
| } |
| unsigned int tmp = (unsigned int)lvalue; |
| if (tmp < min) { |
| std::stringstream ss; |
| ss << nm << " must be >= 0x" << std::hex << std::to_string(min); |
| return ss.str(); |
| } |
| if (tmp > max) { |
| std::stringstream ss; |
| ss << nm << " must be <= 0x" << std::hex << std::to_string(max); |
| return ss.str(); |
| } |
| t.push_back(tmp); |
| return string(""); |
| }); |
| return *this; |
| } |
| |
| hex& min_value(unsigned int v) { |
| _min_value = v; |
| return *this; |
| } |
| |
| hex& max_value(unsigned int v) { |
| _max_value = v; |
| return *this; |
| } |
| |
| unsigned int _min_value = 0; |
| unsigned int _max_value = std::numeric_limits<unsigned int>::max(); |
| }; |
| |
| struct group : public matchable_derived<group> { |
| enum group_type { |
| sequence, |
| set, |
| exclusive, |
| collapse, |
| }; |
| |
| public: |
| group() : type(set) {} |
| |
| template<typename T> |
| explicit group(const T &t) : type(set), elements{t.to_ptr()} {} |
| |
| template<class Matchable, class... Matchables> |
| group(Matchable m, Matchable ms...) : type(set), elements{m, ms} {} |
| |
| group &set_type(group_type t) { |
| type = t; |
| return *this; |
| } |
| |
| group &major_group(string g) { |
| _major_group = std::move(g); |
| return *this; |
| } |
| |
| static string decorate(const matchable &e, string s) { |
| if (e.is_optional() && !e.doc_non_optional()) { |
| return string("[") + s + "]"; |
| } else { |
| return s; |
| } |
| } |
| |
| vector<string> synopsys() const override { |
| vector<string> rc; |
| switch (type) { |
| case set: |
| case sequence: { |
| std::vector<std::vector<string>> tmp{{}}; |
| if (_collapse_synopsys.empty()) { |
| for (auto &x : elements) { |
| auto xs = x->synopsys(); |
| if (xs.size() == 1) { |
| for (auto &s : tmp) { |
| s.push_back(decorate(*x, xs[0])); |
| } |
| } else { |
| auto save = tmp; |
| tmp.clear(); |
| for (auto &v : save) { |
| for (auto &s : xs) { |
| auto nv = v; |
| nv.push_back(decorate(*x, s)); |
| tmp.push_back(nv); |
| } |
| } |
| } |
| } |
| } else { |
| std::vector<std::string> xs = {"[" + _collapse_synopsys + "]"}; |
| for (auto &s : tmp) { |
| s.push_back(xs[0]); |
| } |
| } |
| for (const auto &v : tmp) { |
| rc.push_back(join(v, " ")); |
| } |
| break; |
| } |
| case exclusive: |
| for (auto &x : elements) { |
| auto xs = x->synopsys(); |
| std::transform(xs.begin(), xs.end(), std::back_inserter(rc), [&](const auto &s) { |
| return decorate(*x, s); |
| }); |
| } |
| break; |
| default: |
| assert(false); |
| break; |
| } |
| return rc; |
| } |
| |
| // group operator|(const group &g) { |
| // return matchable_derived::operator|(g); |
| // } |
| // |
| // group operator&(const group &g) { |
| // return matchable_derived::operator&(g); |
| // } |
| // |
| // group operator+(const group &g) { |
| // return matchable_derived::operator+(g); |
| // } |
| |
| bool no_match_beats_error() const { |
| return _no_match_beats_error; |
| } |
| |
| group &no_match_beats_error(bool v) { |
| _no_match_beats_error = v; |
| return *this; |
| } |
| |
| template<typename T> |
| group operator&(const matchable_derived<T> &m) { |
| if (type == sequence) { |
| elements.push_back(m.to_ptr()); |
| return *this; |
| } |
| return matchable_derived::operator&(m); |
| } |
| |
| template<typename T> |
| group operator|(const matchable_derived<T> &m) { |
| if (type == exclusive) { |
| elements.push_back(m.to_ptr()); |
| return *this; |
| } |
| return matchable_derived::operator|(m); |
| } |
| |
| template<typename T> |
| group operator+(const matchable_derived<T> &m) { |
| if (type == set) { |
| elements.push_back(m.to_ptr()); |
| return *this; |
| } |
| return matchable_derived::operator+(m); |
| } |
| |
| bool get_option_help(string major_group, string minor_group, option_map &options) const override { |
| // todo beware.. this check is necessary as is, but I'm not sure what removing it breaks in terms of formatting :-( |
| if (is_optional() && !this->_doc_non_optional && !this->_force_expand_help) { |
| options.add(major_group, minor_group, synopsys()[0], doc()); |
| return true; |
| } |
| if (!doc().empty()) { |
| minor_group = doc(); |
| } |
| if (!_major_group.empty()) { |
| major_group = _major_group; |
| } |
| for (const auto &e : elements) { |
| e->get_option_help(major_group, minor_group, options); |
| } |
| return true; |
| } |
| |
| match_type match(match_state& ms) const override { |
| match_type rc = ms.check_min_max(this); |
| if (rc == match_type::no_match) return rc; |
| assert(rc == match_type::not_yet); |
| switch(type) { |
| case sequence: |
| rc = match_sequence(ms); |
| break; |
| case set: |
| rc = match_set(ms); |
| break; |
| default: |
| rc = match_exclusive(ms); |
| break; |
| } |
| return ms.update_stats(rc, this); |
| } |
| |
| match_type match_sequence(match_state& ms) const { |
| match_type rc = match_type::no_match; |
| for(const auto& e : elements) { |
| rc = e->match(ms); |
| assert(rc != match_type::not_yet); |
| if (rc != match_type::match) { |
| break; |
| } |
| } |
| return rc; |
| } |
| |
| match_type match_set(match_state& ms) const { |
| // because of repeatability, we keep matching until there is nothing left to match |
| // vector<match_type> types(elements.size(), match_type::not_yet); |
| bool had_any_matches = false; |
| bool final_pass = false; |
| do { |
| bool matches_this_time = false; |
| bool errors_this_time = false; |
| bool not_min_this_time = false; |
| for (size_t i=0;i<elements.size();i++) { |
| // if (types[i] == match_type::not_yet) { |
| auto ms_prime = ms; |
| ms_prime.apply_settings_from(); |
| match_type t = elements[i]->match(ms_prime); |
| assert(t != match_type::not_yet); |
| if (t == match_type::match) { |
| // we got a match, so record in ms and try again |
| // (if the matchable isn't repeatable it will no match next time) |
| // types[i] = match_type::not_yet; |
| ms_prime.save_settings_into(); |
| ms = ms_prime; |
| had_any_matches = true; |
| matches_this_time = true; |
| } else if (t == match_type::error) { |
| if (final_pass) { |
| ms_prime.save_settings_into(); |
| ms = ms_prime; |
| return t; |
| } |
| errors_this_time = true; |
| } else { |
| if (ms.get_match_count(elements[i]) < elements[i]->min()) { |
| if (final_pass) { |
| ms.error_message = elements[i]->missing ? elements[i]->missing() : "missing required argument"; |
| return match_type::error; |
| } |
| not_min_this_time = true; |
| } |
| } |
| // } |
| } |
| if (final_pass) break; |
| if (!matches_this_time) { |
| if (errors_this_time || not_min_this_time) { |
| final_pass = true; |
| } else { |
| break; |
| } |
| } |
| } while (true); |
| return had_any_matches ? match_type::match : match_type::no_match; |
| } |
| |
| match_type match_exclusive(match_state& ms) const { |
| vector<match_state> matches(elements.size(), ms); |
| vector<match_type> types(elements.size(), match_type::no_match); |
| int elements_with_errors = 0; |
| int elements_with_no_match = 0; |
| int error_at = -1; |
| int error_match_count = -1; |
| for (size_t i=0;i<elements.size();i++) { |
| match_type t; |
| matches[i].apply_settings_from(); |
| do { |
| t = elements[i]->match(matches[i]); |
| assert(t != match_type::not_yet); |
| if (t != match_type::no_match) { |
| types[i] = t; |
| } |
| } while (t == match_type::match); |
| matches[i].save_settings_into(); |
| if (types[i] == match_type::match) { |
| ms = matches[i]; |
| return match_type::match; |
| } else if (types[i] == match_type::error) { |
| if (matches[i].match_count > error_match_count) { |
| error_match_count = matches[i].match_count; |
| error_at = i; |
| } |
| elements_with_errors++; |
| } else if (types[i] == match_type::no_match) { |
| elements_with_no_match++; |
| } |
| } |
| if (elements_with_no_match && (!elements_with_errors || no_match_beats_error())) { |
| return match_type::no_match; |
| } |
| if (elements_with_errors) { |
| ms = matches[error_at]; |
| ms.apply_settings_from(); // todo perhaps want to apply the previous settings instead? |
| return match_type::error; |
| } else { |
| // back out any modified settings |
| ms.apply_settings_from(); |
| return match_type::no_match; |
| } |
| } |
| |
| private: |
| string _major_group; |
| group_type type; |
| vector<std::shared_ptr<matchable>> elements; |
| bool _no_match_beats_error = true; |
| }; |
| |
| template<typename D> |
| template<typename T> |
| group matchable_derived<D>::operator|(const matchable_derived<T> &m) { |
| return group{this->to_ptr(), m.to_ptr()}.set_type(group::exclusive); |
| } |
| |
| template<typename D> |
| template<typename T> |
| group matchable_derived<D>::operator&(const matchable_derived<T> &m) { |
| int _min = matchable::min(); |
| int _max = matchable::max(); |
| min(1); |
| max(1); |
| return group{this->to_ptr(), m.to_ptr()}.set_type(group::sequence).min(_min).max(_max); |
| } |
| |
| template<typename D> |
| template<typename T> |
| group matchable_derived<D>::operator+(const matchable_derived<T> &m) { |
| return group{this->to_ptr(), m.to_ptr()}; |
| } |
| |
| vector<string> make_args(int argc, char **argv) { |
| vector<string> args; |
| for (int i = 1; i < argc; i++) { |
| string arg(argv[i]); |
| if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') { |
| // expand collapsed args (unconditionally for now) |
| for (auto c = arg.begin() + 1; c != arg.end(); c++) { |
| args.push_back("-" + string(1, *c)); |
| } |
| } else { |
| args.push_back(arg); |
| } |
| } |
| return args; |
| } |
| |
| match_type match_state::check_min_max(const matchable *matchable) { |
| if (matchable_counts[matchable] < matchable->min()) { |
| return match_type::not_yet; |
| } |
| if (matchable_counts[matchable] >= matchable->max()) { |
| return match_type::no_match; |
| } |
| return match_type::not_yet; |
| } |
| |
| match_type match_state::match_if_equal(const matchable *matchable, const string& s) { |
| if (remaining_args.empty()) return match_type::no_match; |
| if (remaining_args[0] == s) { |
| auto message = matchable->action(s); |
| assert(message.empty()); |
| remaining_args.erase(remaining_args.begin()); |
| return update_stats(match_type::match, matchable); |
| } |
| return match_type::no_match; |
| } |
| |
| match_type match_state::match_value(const matchable *matchable, std::function<bool(const string&)> exclusion_filter) { |
| // treat an excluded value as missing |
| bool empty = remaining_args.empty() || exclusion_filter(remaining_args[0]); |
| if (empty) { |
| if (matchable_counts[matchable] < matchable->min()) { |
| prefer_unknown_option_message = !remaining_args.empty(); |
| error_message = matchable->missing ? matchable->missing() : "missing <" + matchable->name() +">"; |
| return update_stats(match_type::error, matchable); |
| } |
| return match_type::no_match; |
| } |
| auto message = matchable->action(remaining_args[0]); |
| if (!message.empty()) { |
| error_message = message; |
| return update_stats(match_type::error, matchable); |
| } |
| remaining_args.erase(remaining_args.begin()); |
| return update_stats(match_type::match, matchable); |
| } |
| |
| template<typename S> struct typed_settings final : public opaque_settings { |
| explicit typed_settings(S& settings) : root_settings(settings), settings(settings) { |
| } |
| |
| shared_ptr<cli::opaque_settings> copy() override { |
| auto c = std::make_shared<typed_settings<S>>(*this); |
| c->settings = settings; |
| return c; |
| } |
| |
| void save_into() override { |
| settings = root_settings; |
| } |
| |
| void apply_from() override { |
| root_settings = settings; |
| } |
| |
| S& root_settings; |
| S settings; |
| }; |
| |
| template<typename S> void match(S& settings, const group& g, std::vector<string> args) { |
| auto holder = settings_holder(std::make_shared<typed_settings<S>>(settings)); |
| match_state ms(holder); |
| ms.remaining_args = std::move(args); |
| auto t = g.match(ms); |
| if (!ms.prefer_unknown_option_message) { |
| if (t == match_type::error) { |
| throw parse_error(ms.error_message); |
| } |
| } |
| if (!ms.remaining_args.empty()) { |
| if (ms.remaining_args[0].find('-')==0) { |
| throw parse_error("unexpected option: "+ms.remaining_args[0]); |
| } else { |
| throw parse_error("unexpected argument: "+ms.remaining_args[0]); |
| } |
| } |
| if (ms.prefer_unknown_option_message) { |
| if (t == match_type::error) { |
| throw parse_error(ms.error_message); |
| } |
| } |
| } |
| } |
| |
| #endif |