Toolbox snapshot
The Reactive C++ Toolbox
Loading...
Searching...
No Matches
Config.hpp
Go to the documentation of this file.
1// The Reactive C++ Toolbox.
2// Copyright (C) 2013-2019 Swirly Cloud Limited
3// Copyright (C) 2022 Reactive Markets Limited
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17#ifndef TOOLBOX_UTIL_CONFIG_HPP
18#define TOOLBOX_UTIL_CONFIG_HPP
19
23
24#include <algorithm>
25#include <map>
26#include <ranges>
27#include <stdexcept>
28#include <unordered_map>
29#include <utility>
30
31namespace toolbox {
32inline namespace util {
33
34template <typename FnT>
35std::istream& parse_section(std::istream& is, FnT fn, std::string* name = nullptr)
36{
37 using namespace std::literals::string_literals;
38
40 std::unordered_map<std::string, KeyClassification, string_hash, std::equal_to<>> key_class_map;
41
42 std::string line;
43 while (std::getline(is, line)) {
44
45 trim(line);
46
47 // Ignore empty lines and comments.
48
49 if (line.empty() || line[0] == '#') {
50 continue;
51 }
52
53 if (line.front() == '[' && line.back() == ']') {
54 if (name) {
55 name->assign(line, 1, line.size() - 2);
56 trim(*name);
57 }
58 return is;
59 }
60
61 bool is_multi = [&line]() {
62 auto multi_pos = line.find("+=");
63 auto single_pos = line.find('=');
64 return multi_pos < single_pos;
65 }();
66
67 auto delim = std::string_view{is_multi ? "+=" : "="};
68
69 auto [key, val] = split_pair(line, delim);
70 rtrim(key);
71 ltrim(val);
72
73 std::optional<KeyClassification> key_class;
74 if (auto it = key_class_map.find(key); it != key_class_map.end()) {
75 key_class = (*it).second;
76 }
77
78 if (key_class) [[unlikely]] {
79 if (*key_class == KeyClassification::SingleValued && is_multi) {
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 '+='")};
83 }
84 else if (*key_class == KeyClassification::MultiValued && !is_multi) {
85 throw std::runtime_error{
86 std::string{key}.append(" is already set as a multi-valued key (with '+=') "
87 "and cannot be reassigned with '='")};
88 }
89 }
90 else {
91 key_class_map[key] = is_multi ? KeyClassification::MultiValued
92 : KeyClassification::SingleValued;
93 }
94
95 fn(std::move(key), std::move(val));
96 }
97 if (name) {
98 name->clear();
99 }
100 return is;
101}
102
105 public:
108
109 // Copy.
110 Config(const Config&);
112
113 // Move.
114 Config(Config&&) noexcept;
115 Config& operator=(Config&&) noexcept;
116
118 const std::string& get(const std::string& key) const;
119 const char* get(const std::string& key, const char* dfl) const;
120 const char* get(const std::string& key, std::nullptr_t) const
121 {
122 return get(key, static_cast<const char*>(nullptr));
123 }
125 template <typename ValueT>
126 ValueT get(const std::string& key) const
127 {
128 const auto it{get_last_value(key)};
129 if (it != map_.end()) {
130 return transform_value<ValueT>(it->second);
131 }
132 if (!parent_) {
133 throw std::runtime_error{std::string{"missing config key: "} + key};
134 }
135 return parent_->get<ValueT>(key);
136 }
137 template <typename ValueT>
138 ValueT get(const std::string& key, ValueT dfl) const
139 {
140 const auto it{get_last_value(key)};
141 if (it != map_.end()) {
142 return transform_value<ValueT>(it->second);
143 }
144 return parent_ ? parent_->get<ValueT>(key, dfl) : dfl;
145 }
146
147 auto get_multi(const std::string& key) const
148 {
149 auto [begin, end] = map_.equal_range(key);
150
151 auto fn = [](const auto& kvp) -> const std::string& { return kvp.second; };
152 auto rng = std::ranges::subrange(begin, end, map_.count(key)) | std::views::transform(fn);
153
154 if (!rng.empty()) {
155 return rng;
156 }
157
158 return parent_ ? parent_->get_multi(key) : rng;
159 }
160
161 template <typename ValueT>
162 auto get_multi(const std::string& key) const {
163 return get_multi(key) | std::views::transform(transform_value<ValueT>);
164 }
165
166 std::size_t size() const noexcept { return map_.size(); }
167 void clear() noexcept { map_.clear(); }
168 std::istream& read_section(std::istream& is) { return read_section(is, nullptr); }
169 std::istream& read_section(std::istream&& is) { return read_section(is); }
170 std::istream& read_section(std::istream& is, std::string& next)
171 {
172 return read_section(is, &next);
173 }
174 std::istream& read_section(std::istream&& is, std::string& next)
175 {
176 return read_section(is, next);
177 }
178 void insert(std::string key, std::string val)
179 {
180 map_.emplace(std::move(key), std::move(val));
181 }
182
183 // existing values for given key are erased, and new values are inserted
184 template<typename... ValueTs>
186 void set(const std::string& key, ValueTs ... vals)
187 {
188 static_assert(sizeof...(ValueTs) > 0, "at least 1 value is required");
189
190 map_.erase(key);
191 (insert(key, std::string(std::move(vals))), ...);
192 }
193
194 void set_parent(Config& parent) noexcept { parent_ = &parent; }
195
196 private:
197 template <typename ValueT>
198 static ValueT transform_value(const std::string& v)
199 {
200 if constexpr (std::is_same_v<ValueT, std::string_view>) {
201 // Explicitly allow conversion to std::string_view in this case,
202 // because we know that the argument is not a temporary.
203 return std::string_view{v};
204 } else if constexpr (std::is_enum_v<ValueT>) {
205 return ValueT{from_string<std::underlying_type_t<ValueT>>(v)};
206 } else {
207 return from_string<ValueT>(v);
208 };
209 }
210
211 auto get_last_value(const std::string& key) const
212 -> std::multimap<std::string, std::string>::const_iterator
213 {
214 auto [first, second] = map_.equal_range(key);
215 return (first == second) ? map_.end() : std::prev(second);
216 }
217
218 std::istream& read_section(std::istream& is, std::string* next);
219 std::multimap<std::string, std::string> map_;
220 Config* parent_{nullptr};
221};
222
224 public:
227
228 // Copy.
229 MultiConfig(const MultiConfig&) = delete;
231
232 // Move.
234 MultiConfig& operator=(MultiConfig&&) noexcept;
235
236 void clear() noexcept;
237 void read(std::istream& is);
238 void read(std::istream&& is) { read(is); }
239
240 const Config& root() const noexcept { return root_; }
241 const Config& section(const std::string& name) const noexcept
242 {
243 auto it = map_.find(name);
244 return it != map_.end() ? it->second : root_;
245 }
246 const Config& section(std::string_view name) const noexcept
247 {
248 return section(std::string{name});
249 }
250
251 template <typename F>
252 void for_each_section(F f) const
253 noexcept(noexcept(
254 f(std::declval<const MultiConfig::MapType::key_type&>(),
255 std::declval<const MultiConfig::MapType::mapped_type&>())
256 ))
257 {
258 std::for_each(map_.begin(), map_.end(), [&f](const auto& kv) {
259 f(kv.first, kv.second);
260 });
261 }
262
263 private:
264 Config root_;
265 // Map of named sections.
266 using MapType = std::map<std::string, Config>;
267 MapType map_;
268};
269
270} // namespace util
271} // namespace toolbox
272
273#endif // TOOLBOX_UTIL_CONFIG_HPP
#define TOOLBOX_API
Definition Config.h:39
Simple config reader with environment variable substitution.
Definition Config.hpp:104
void clear() noexcept
Definition Config.hpp:167
auto get_multi(const std::string &key) const
Definition Config.hpp:147
ValueT get(const std::string &key, ValueT dfl) const
Definition Config.hpp:138
Config & operator=(const Config &)
void insert(std::string key, std::string val)
Definition Config.hpp:178
std::size_t size() const noexcept
Definition Config.hpp:166
Config(const Config &)
Config(Config &&) noexcept
std::istream & read_section(std::istream &&is, std::string &next)
Definition Config.hpp:174
std::istream & read_section(std::istream &&is)
Definition Config.hpp:169
std::istream & read_section(std::istream &is)
Definition Config.hpp:168
void set_parent(Config &parent) noexcept
Definition Config.hpp:194
ValueT get(const std::string &key) const
Throws std::runtime_error if key does not exist.
Definition Config.hpp:126
std::istream & read_section(std::istream &is, std::string &next)
Definition Config.hpp:170
auto get_multi(const std::string &key) const
Definition Config.hpp:162
MultiConfig & operator=(const MultiConfig &)=delete
const Config & section(const std::string &name) const noexcept
Definition Config.hpp:241
const Config & root() const noexcept
Definition Config.hpp:240
MultiConfig(const MultiConfig &)=delete
MultiConfig(MultiConfig &&) noexcept
void for_each_section(F f) const noexcept(noexcept(f(std::declval< const MultiConfig::MapType::key_type & >(), std::declval< const MultiConfig::MapType::mapped_type & >())))
Definition Config.hpp:252
const Config & section(std::string_view name) const noexcept
Definition Config.hpp:246
void read(std::istream &&is)
Definition Config.hpp:238
STL namespace.
pair< string_view, string_view > split_pair(string_view s, string_view delim) noexcept
Definition String.cpp:52
void rtrim(string_view &s) noexcept
Definition String.cpp:40
void trim(std::string_view &s) noexcept
Definition String.hpp:84
void ltrim(string_view &s) noexcept
Definition String.cpp:28
std::istream & parse_section(std::istream &is, FnT fn, std::string *name=nullptr)
Definition Config.hpp:35
constexpr const auto & get(const detail::Struct< detail::Member< TagsT, ValuesT >... > &s, TagT tag={})
Definition Struct.hpp:110
constexpr auto bind() noexcept
Definition Slot.hpp:97