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 <ranges>
25#include <stdexcept>
26#include <unordered_map>
27#include <map>
28
29namespace toolbox {
30inline namespace util {
31
32template <typename FnT>
33std::istream& parse_section(std::istream& is, FnT fn, std::string* name = nullptr)
34{
35 using namespace std::literals::string_literals;
36
38 std::unordered_map<std::string, KeyClassification, string_hash, std::equal_to<>> key_class_map;
39
40 std::string line;
41 while (std::getline(is, line)) {
42
43 trim(line);
44
45 // Ignore empty lines and comments.
46
47 if (line.empty() || line[0] == '#') {
48 continue;
49 }
50
51 if (line.front() == '[' && line.back() == ']') {
52 if (name) {
53 name->assign(line, 1, line.size() - 2);
54 trim(*name);
55 }
56 return is;
57 }
58
59 bool is_multi = [&line]() {
60 auto multi_pos = line.find("+=");
61 auto single_pos = line.find('=');
62 return multi_pos < single_pos;
63 }();
64
65 auto delim = std::string_view{is_multi ? "+=" : "="};
66
67 auto [key, val] = split_pair(line, delim);
68 rtrim(key);
69 ltrim(val);
70
71 std::optional<KeyClassification> key_class;
72 if (auto it = key_class_map.find(key); it != key_class_map.end()) {
73 key_class = (*it).second;
74 }
75
76 if (key_class) [[unlikely]] {
77 if (*key_class == KeyClassification::SingleValued && is_multi) {
78 throw std::runtime_error{
79 std::string{key}.append(" is already set as a single-valued key (with '=') "
80 "and cannot be reassigned with '+='")};
81 }
82 else if (*key_class == KeyClassification::MultiValued && !is_multi) {
83 throw std::runtime_error{
84 std::string{key}.append(" is already set as a multi-valued key (with '+=') "
85 "and cannot be reassigned with '='")};
86 }
87 }
88 else {
89 key_class_map[key] = is_multi ? KeyClassification::MultiValued
90 : 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 return get_multi(key) | std::views::transform(transform_value<ValueT>);
162 }
163
164 std::size_t size() const noexcept { return map_.size(); }
165 void clear() noexcept { map_.clear(); }
166 std::istream& read_section(std::istream& is) { return read_section(is, nullptr); }
167 std::istream& read_section(std::istream&& is) { return read_section(is); }
168 std::istream& read_section(std::istream& is, std::string& next)
169 {
170 return read_section(is, &next);
171 }
172 std::istream& read_section(std::istream&& is, std::string& next)
173 {
174 return read_section(is, next);
175 }
176 void insert(std::string key, std::string val)
177 {
178 map_.emplace(std::move(key), std::move(val));
179 }
180
181 // existing values for given key are erased, and new values are inserted
182 template<typename... ValueTs>
184 void set(const std::string& key, ValueTs ... vals)
185 {
186 static_assert(sizeof...(ValueTs) > 0, "at least 1 value is required");
187
188 map_.erase(key);
189 (insert(key, std::string(std::move(vals))), ...);
190 }
191
192 void set_parent(Config& parent) noexcept { parent_ = &parent; }
193
194 private:
195 template <typename ValueT>
196 static ValueT transform_value(const std::string& v)
197 {
198 if constexpr (std::is_same_v<ValueT, std::string_view>) {
199 // Explicitly allow conversion to std::string_view in this case,
200 // because we know that the argument is not a temporary.
201 return std::string_view{v};
202 } else if constexpr (std::is_enum_v<ValueT>) {
203 return ValueT{from_string<std::underlying_type_t<ValueT>>(v)};
204 } else {
205 return from_string<ValueT>(v);
206 };
207 }
208
209 auto get_last_value(const std::string& key) const
210 -> std::multimap<std::string, std::string>::const_iterator
211 {
212 auto [first, second] = map_.equal_range(key);
213 return (first == second) ? map_.end() : std::prev(second);
214 }
215
216 std::istream& read_section(std::istream& is, std::string* next);
217 std::multimap<std::string, std::string> map_;
218 Config* parent_{nullptr};
219};
220
222 public:
225
226 // Copy.
227 MultiConfig(const MultiConfig&) = delete;
229
230 // Move.
232 MultiConfig& operator=(MultiConfig&&) noexcept;
233
234 void clear() noexcept;
235 void read(std::istream& is);
236 void read(std::istream&& is) { read(is); }
237
238 const Config& root() const noexcept { return root_; }
239 const Config& section(const std::string& name) const noexcept
240 {
241 auto it = map_.find(name);
242 return it != map_.end() ? it->second : root_;
243 }
244 const Config& section(std::string_view name) const noexcept
245 {
246 return section(std::string{name});
247 }
248
249 private:
250 Config root_;
251 // Map of named sections.
252 std::map<std::string, Config> map_;
253};
254
255} // namespace util
256} // namespace toolbox
257
258#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:165
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:176
std::size_t size() const noexcept
Definition Config.hpp:164
Config(const Config &)
Config(Config &&) noexcept
std::istream & read_section(std::istream &&is, std::string &next)
Definition Config.hpp:172
std::istream & read_section(std::istream &&is)
Definition Config.hpp:167
std::istream & read_section(std::istream &is)
Definition Config.hpp:166
void set_parent(Config &parent) noexcept
Definition Config.hpp:192
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:168
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:239
const Config & root() const noexcept
Definition Config.hpp:238
MultiConfig(const MultiConfig &)=delete
MultiConfig(MultiConfig &&) noexcept
const Config & section(std::string_view name) const noexcept
Definition Config.hpp:244
void read(std::istream &&is)
Definition Config.hpp:236
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:33
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:92