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 } else if (*key_class == KeyClassification::MultiValued && !is_multi) {
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 '='")};
87 }
88 } else {
90 = is_multi ? KeyClassification::MultiValued : KeyClassification::SingleValued;
91 }
92
93 fn(std::move(key), std::move(val));
94 }
95 if (name) {
96 name->clear();
97 }
98 return is;
99}
100
103 public:
106
107 // Copy.
108 Config(const Config&);
110
111 // Move.
112 Config(Config&&) noexcept;
113 Config& operator=(Config&&) noexcept;
114
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
119 {
120 return get(key, static_cast<const char*>(nullptr));
121 }
123 template <typename ValueT>
124 ValueT get(const std::string& key) const
125 {
126 const auto it{get_last_value(key)};
127 if (it != map_.end()) {
128 return transform_value<ValueT>(it->second);
129 }
130 if (!parent_) {
131 throw std::runtime_error{std::string{"missing config key: "} + key};
132 }
133 return parent_->get<ValueT>(key);
134 }
135 template <typename ValueT>
136 ValueT get(const std::string& key, ValueT dfl) const
137 {
138 const auto it{get_last_value(key)};
139 if (it != map_.end()) {
140 return transform_value<ValueT>(it->second);
141 }
142 return parent_ ? parent_->get<ValueT>(key, dfl) : dfl;
143 }
144
145 auto get_multi(const std::string& key) const
146 {
147 auto [begin, end] = map_.equal_range(key);
148
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);
151
152 if (!rng.empty()) {
153 return rng;
154 }
155
156 return parent_ ? parent_->get_multi(key) : rng;
157 }
158
159 template <typename ValueT>
160 auto get_multi(const std::string& key) const
161 {
162 return get_multi(key) | std::views::transform(transform_value<ValueT>);
163 }
164
165 std::size_t size() const noexcept { return map_.size(); }
166 void clear() noexcept { map_.clear(); }
167 std::istream& read_section(std::istream& is) { return read_section(is, nullptr); }
168 std::istream& read_section(std::istream&& is) { return read_section(is); }
169 std::istream& read_section(std::istream& is, std::string& next)
170 {
171 return read_section(is, &next);
172 }
173 std::istream& read_section(std::istream&& is, std::string& next)
174 {
175 return read_section(is, next);
176 }
177 void insert(std::string key, std::string val) { map_.emplace(std::move(key), std::move(val)); }
178
179 // existing values for given key are erased, and new values are inserted
180 template <typename... ValueTs>
182 void set(const std::string& key, ValueTs... vals)
183 {
184 static_assert(sizeof...(ValueTs) > 0, "at least 1 value is required");
185
186 map_.erase(key);
187 (insert(key, std::string(std::move(vals))), ...);
188 }
189
190 void set_parent(Config& parent) noexcept { parent_ = &parent; }
191
192 private:
193 template <typename ValueT>
194 static ValueT transform_value(const std::string& v)
195 {
196 if constexpr (std::is_same_v<ValueT, std::string_view>) {
197 // Explicitly allow conversion to std::string_view in this case,
198 // because we know that the argument is not a temporary.
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)};
202 } else {
203 return from_string<ValueT>(v);
204 };
205 }
206
207 auto get_last_value(const std::string& key) const
208 -> std::multimap<std::string, std::string>::const_iterator
209 {
210 auto [first, second] = map_.equal_range(key);
211 return (first == second) ? map_.end() : std::prev(second);
212 }
213
214 std::istream& read_section(std::istream& is, std::string* next);
215 std::multimap<std::string, std::string> map_;
216 Config* parent_{nullptr};
217};
218
220 public:
223
224 // Copy.
225 MultiConfig(const MultiConfig&) = delete;
227
228 // Move.
230 MultiConfig& operator=(MultiConfig&&) noexcept;
231
232 void clear() noexcept;
233 void read(std::istream& is);
234 void read(std::istream&& is) { read(is); }
235
236 const Config& root() const noexcept { return root_; }
237 const Config& section(const std::string& name) const noexcept
238 {
239 auto it = map_.find(name);
240 return it != map_.end() ? it->second : root_;
241 }
242 const Config& section(std::string_view name) const noexcept
243 {
244 return section(std::string{name});
245 }
246
247 template <typename F>
248 void for_each_section(F f) const
249 noexcept(noexcept(f(std::declval<const MultiConfig::MapType::key_type&>(),
250 std::declval<const MultiConfig::MapType::mapped_type&>())))
251 {
252 std::for_each(map_.begin(), map_.end(), [&f](const auto& kv) { f(kv.first, kv.second); });
253 }
254
255 private:
256 Config root_;
257 // Map of named sections.
258 using MapType = std::map<std::string, Config>;
259 MapType map_;
260};
261
262} // namespace util
263} // namespace toolbox
264
265#endif // TOOLBOX_UTIL_CONFIG_HPP
#define TOOLBOX_API
Definition Config.h:39
Simple config reader with environment variable substitution.
Definition Config.hpp:102
void clear() noexcept
Definition Config.hpp:166
auto get_multi(const std::string &key) const
Definition Config.hpp:145
ValueT get(const std::string &key, ValueT dfl) const
Definition Config.hpp:136
Config & operator=(const Config &)
void insert(std::string key, std::string val)
Definition Config.hpp:177
std::size_t size() const noexcept
Definition Config.hpp:165
Config(const Config &)
Config(Config &&) noexcept
std::istream & read_section(std::istream &&is, std::string &next)
Definition Config.hpp:173
std::istream & read_section(std::istream &&is)
Definition Config.hpp:168
std::istream & read_section(std::istream &is)
Definition Config.hpp:167
void set_parent(Config &parent) noexcept
Definition Config.hpp:190
ValueT get(const std::string &key) const
Throws std::runtime_error if key does not exist.
Definition Config.hpp:124
std::istream & read_section(std::istream &is, std::string &next)
Definition Config.hpp:169
auto get_multi(const std::string &key) const
Definition Config.hpp:160
MultiConfig & operator=(const MultiConfig &)=delete
const Config & section(const std::string &name) const noexcept
Definition Config.hpp:237
const Config & root() const noexcept
Definition Config.hpp:236
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:248
const Config & section(std::string_view name) const noexcept
Definition Config.hpp:242
void read(std::istream &&is)
Definition Config.hpp:234
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:95
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