17#ifndef TOOLBOX_UTIL_CONFIG_HPP
18#define TOOLBOX_UTIL_CONFIG_HPP
28#include <unordered_map>
32inline namespace util {
34template <
typename FnT>
37 using namespace std::literals::string_literals;
40 std::unordered_map<std::string, KeyClassification, string_hash, std::equal_to<>>
key_class_map;
43 while (std::getline(
is,
line)) {
49 if (
line.empty() ||
line[0] ==
'#') {
53 if (
line.front() ==
'[' &&
line.back() ==
']') {
55 name->assign(
line, 1,
line.size() - 2);
73 std::optional<KeyClassification>
key_class;
80 throw std::runtime_error{
81 std::string{
key}.append(
" is already set as a single-valued key (with '=') "
82 "and cannot be reassigned with '+='")};
84 throw std::runtime_error{
85 std::string{
key}.append(
" is already set as a multi-valued key (with '+=') "
86 "and cannot be reassigned with '='")};
90 =
is_multi ? KeyClassification::MultiValued : KeyClassification::SingleValued;
93 fn(std::move(
key), std::move(
val));
116 const
std::
string& get(const
std::
string& key) const;
117 const
char* get(const
std::
string& key, const
char* dfl) const;
118 const
char* get(const
std::
string& key,
std::nullptr_t)
const
120 return get(key,
static_cast<const char*
>(
nullptr));
123 template <
typename ValueT>
124 ValueT
get(
const std::string& key)
const
126 const auto it{get_last_value(key)};
127 if (it != map_.end()) {
128 return transform_value<ValueT>(it->second);
131 throw std::runtime_error{std::string{
"missing config key: "} + key};
133 return parent_->get<ValueT>(key);
135 template <
typename ValueT>
136 ValueT
get(
const std::string& key, ValueT dfl)
const
138 const auto it{get_last_value(key)};
139 if (it != map_.end()) {
140 return transform_value<ValueT>(it->second);
142 return parent_ ? parent_->get<ValueT>(key, dfl) : dfl;
147 auto [begin, end] = map_.equal_range(key);
149 auto fn = [](
const auto& kvp) ->
const std::string& {
return kvp.second; };
150 auto rng = std::ranges::subrange(begin, end, map_.count(key)) | std::views::transform(fn);
156 return parent_ ? parent_->get_multi(key) : rng;
159 template <
typename ValueT>
162 return get_multi(key) | std::views::transform(transform_value<ValueT>);
165 std::size_t
size() const noexcept {
return map_.size(); }
166 void clear() noexcept { map_.clear(); }
171 return read_section(is, &next);
175 return read_section(is, next);
177 void insert(std::string key, std::string val) { map_.emplace(std::move(key), std::move(val)); }
180 template <
typename... ValueTs>
182 void set(const
std::
string& key, ValueTs... vals)
184 static_assert(
sizeof...(ValueTs) > 0,
"at least 1 value is required");
187 (insert(key, std::string(std::move(vals))), ...);
193 template <
typename ValueT>
194 static ValueT transform_value(
const std::string& v)
196 if constexpr (std::is_same_v<ValueT, std::string_view>) {
199 return std::string_view{v};
200 }
else if constexpr (std::is_enum_v<ValueT>) {
201 return ValueT{from_string<std::underlying_type_t<ValueT>>(v)};
203 return from_string<ValueT>(v);
207 auto get_last_value(
const std::string& key)
const
208 -> std::multimap<std::string, std::string>::const_iterator
210 auto [first, second] = map_.equal_range(key);
211 return (first == second) ? map_.end() : std::prev(second);
214 std::istream& read_section(std::istream& is, std::string* next);
215 std::multimap<std::string, std::string> map_;
216 Config* parent_{
nullptr};
232 void clear() noexcept;
233 void read(
std::istream& is);
239 auto it = map_.find(name);
240 return it != map_.end() ? it->second : root_;
244 return section(std::string{name});
247 template <
typename F>
249 noexcept(
noexcept(f(std::declval<const MultiConfig::MapType::key_type&>(),
250 std::declval<const MultiConfig::MapType::mapped_type&>())))
252 std::for_each(map_.begin(), map_.end(), [&f](
const auto& kv) { f(kv.first, kv.second); });
258 using MapType = std::map<std::string, Config>;