#pragma once #include #include #include #include #include #include #include #include namespace lm { class pair { public: std::string key; std::string value; pair(std::string_view k, std::string_view v) : key(k) , value(v) {} }; class pair_iterator { using vec_type = std::vector; using base_iter = vec_type::const_iterator; base_iter _iter; base_iter _end; std::string _key; public: using iterator_category = std::forward_iterator_tag; using difference_type = vec_type::difference_type; using value_type = vec_type::value_type; using pointer = vec_type::pointer; using reference = vec_type::reference; inline pair_iterator(base_iter, base_iter, std::string_view k); pair_iterator& operator++() & noexcept { assert(_iter != _end); ++_iter; while (_iter != _end && _iter->key != _key) { ++_iter; } return *this; } const pair* operator->() const noexcept { assert(_iter != _end); return _iter.operator->(); } const pair& operator*() const noexcept { assert(_iter != _end); return *_iter; } inline bool operator!=(const pair_iterator& o) const noexcept { return _iter != o._iter; } inline bool operator==(const pair_iterator& o) const noexcept { return _iter == o._iter; } auto begin() const noexcept { return *this; } auto end() const noexcept { return pair_iterator(_end, _end, _key); } explicit operator bool() const noexcept { return *this != end(); } }; class pair_list { std::vector _kvs; public: explicit pair_list(std::vector kvs) : _kvs(kvs) {} auto& items() const noexcept { return _kvs; } const pair* find(const std::string_view& key) const noexcept { for (auto&& item : items()) { if (item.key == key) { return &item; } } return nullptr; } pair_iterator iter(std::string_view key) const noexcept { auto iter = items().begin(); const auto end = items().end(); while (iter != end && iter->key != key) { ++iter; } return pair_iterator{iter, end, key}; } std::vector all_of(std::string_view key) const noexcept { auto iter = this->iter(key); return std::vector(iter, iter.end()); } auto size() const noexcept { return _kvs.size(); } }; inline pair_iterator::pair_iterator(base_iter it, base_iter end, std::string_view k) : _iter{it} , _end{end} , _key{k} {} pair_list parse_string(std::string_view); pair_list parse_file(std::filesystem::path); void write_pairs(std::filesystem::path, const std::vector&); inline void write_pairs(const std::filesystem::path& fpath, const pair_list& pairs) { write_pairs(fpath, pairs.items()); } struct nested_kvlist { std::string primary; pair_list pairs; static nested_kvlist parse(std::string_view s); }; struct unchanged { template auto operator()(Item&& item) const { return item; } }; template class read_required { std::string_view _key; What& _ref; bool _did_read = false; public: read_required(std::string_view key, What& ref) : _key(key) , _ref(ref) {} int operator()(std::string_view context, std::string_view key, std::string_view value) { if (key != _key) { return 0; } if (_did_read) { throw std::runtime_error(std::string(context) + ": Duplicated key '" + std::string(key) + "' is not allowed"); } _did_read = true; _ref = What(value); return 1; } void validate(std::string_view context) const { if (!_did_read) { throw std::runtime_error(std::string(context) + ": Missing required key '" + std::string(_key) + "'"); } } }; template class read_opt { std::string_view _key; T& _ref; bool _did_read = false; Transform _tr; public: read_opt(std::string_view key, T& ref, Transform tr = unchanged()) : _key(key) , _ref(ref) , _tr(std::move(tr)) {} int operator()(std::string_view context, std::string_view key, std::string_view value) { if (key != _key) { return 0; } if (_did_read) { throw std::runtime_error(std::string(context) + ": Duplicated key '" + std::string(key) + "' is not allowed."); } _ref = T(_tr(value)); return 1; } }; template class read_bool { std::string_view _key; T& _ref; bool _did_read = false; public: read_bool(std::string_view key, T& ref) : _key(key) , _ref(ref) {} bool operator()(std::string_view context, std::string_view key, std::string_view value) { if (key != _key) { return false; } if (_did_read) { throw std::runtime_error(std::string(context) + ": Duplicate key '" + std::string(key) + "' is not allowed."); } if (value == "true" || value == "True") { _ref = true; } else if (value == "false" || value == "False") { _ref = false; } else { throw std::runtime_error(std::string(context) + ": Invalid value '" + std::string(value) + "' for key '" + std::string(key) + ".' Expected `true` or `false`."); } _did_read = true; return true; } }; struct read_empty_set_true { std::string_view _key; bool& _bool; bool _seen = false; bool operator()(std::string_view context, std::string_view key, std::string_view value) { if (key != _key) { return false; } if (value != "") { throw std::runtime_error(std::string(context) + ": Key '" + std::string(key) + "' does not expected a value (Got '" + std::string(value) + "')."); } if (_seen) { throw std::runtime_error(std::string(context) + ": Duplicated key '" + std::string(key) + "'"); } _bool = true; _seen = true; return true; } }; class read_check_eq { std::string_view _key; std::string_view _expect; public: read_check_eq(std::string_view key, std::string_view value) : _key(key) , _expect(value) {} int operator()(std::string_view context, std::string_view key, std::string_view value) const { if (key != _key) { return 0; } if (value != _expect) { throw std::runtime_error(std::string(context) + ": Expected key '" + std::string(key) + "' to have value '" + std::string(_expect) + "' (Got '" + std::string(value) + "')"); } return 1; } }; template class read_accumulate { std::string_view _key; Container& _items; Transform _tr; public: read_accumulate(std::string_view key, Container& c, Transform tr) : _key(key) , _items(c) , _tr(std::move(tr)) {} read_accumulate(std::string_view key, Container& c) : _key(key) , _items(c) , _tr(unchanged()) {} int operator()(std::string_view, std::string_view key, std::string_view value) const { if (key == _key) { _items.emplace_back(_tr(value)); return 1; } return 0; } }; class ignore_x_keys { public: bool operator()(std::string_view, std::string_view key, std::string_view) const { return key.find("X-") == 0; } }; class reject_unknown { public: int operator()(std::string_view context, std::string_view key, std::string_view) const { throw std::runtime_error(std::string(context) + ": Unknown key '" + std::string(key) + "'"); } }; template auto validate_reader(T&& t, std::string_view context, int) -> decltype(t.validate(context)) { t.validate(context); } template void validate_reader(T&&, std::string_view, ...) {} template auto read(std::string_view context [[maybe_unused]], const pair_list& pairs, Items... is) { std::vector bad_pairs; for (auto [key, value] : pairs.items()) { auto did_read = (is(context, key, value) || ...); if (did_read) { bad_pairs.emplace_back(key, value); } } (validate_reader(is, context, 0), ...); return bad_pairs; } } // namespace lm