1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
|
/// \file
/// POM configuration parser for C++.
///
/// ## Thread-safety
///
/// Of course, you should not \ref pom::Configuration::merge into
/// a configuration while another thread is using it.
///
/// Otherwise, libpom is fully thread-safe
/// provided that C11 atomics are available
/// (`__STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)`).
/// But beware of race conditions when using \ref pom::Configuration::unread_keys
/// — this will not lead to UB but you may get unexpected results —
/// ensure there is synchronization so that all threads
/// have certainly read their keys before calling it.
///
/// If C11 atomics are not available, you can almost certainly still get away
/// with sharing configurations across threads, as long as you use proper
/// synchronization for \ref pom::Configuration::unread_keys.
/// (Essentially, libpom may end up writing the same value to the same address
/// from separate threads, which is *technically* undefined behaviour, but will
/// likely never be an issue on any real machine.)
/// Even if you are extremely paranoid, you can still use
/// distinct configurations in different threads without worry.
///
/// ## Notes
///
/// Every libpom++ function may change the value of `errno` arbitrarily
/// (its value after any libpom++ call should be ignored).
/// \mainpage libpom++ doxygen documentation
///
/// See \ref pom.hpp for all types/functions.
#ifndef POM_HPP_
#define POM_HPP_
#include <exception>
#include <cstdint>
#include <string_view>
#include <string>
#include <vector>
#include <memory>
#include <optional>
/// libpom++ namespace
namespace pom {
/// A libpom++ error.
class Error: public std::exception {
public:
~Error();
Error(Error &other) = delete;
/// Get file where error occured.
///
/// Returned string view lives for as long as `this`.
std::string_view file() const noexcept;
/// Get line number where error occurred.
uint64_t line() const noexcept;
/// Get error message. You probably want \ref to_string instead.
///
/// This only gets a single error message from this entry in an error list,
/// and doesn't include the file name or line number.
///
/// Returned string view lives for as long as `this`.
std::string_view message() const noexcept;
/// Get next error
///
/// Returned pointer lives for as long as `this`.
inline const Error *next() const noexcept { return m_next.get(); }
/// Get description of error.
///
/// Returned string view lives for as long as `this`.
std::string_view to_string() noexcept;
/// Get description of error (equivalent to \ref to_string — use that instead if you can).
///
/// You should only call this on the first error in an error list
/// (i.e. don't call it on an error gotten from \ref next);
/// otherwise you will get a valid but mostly useless string.
/// (Unlike \ref to_string, this can't be enforced with
/// constness because this method needs to override `std::exception::what`.)
///
/// Returned pointer lives for as long as `this`.
virtual const char *what() const noexcept override;
private:
friend class Configuration;
friend class Parser;
Error() = delete;
Error(void *C_error);
Error(const void *C_error);
void *C;
bool m_is_original;
std::unique_ptr<const Error> m_next;
};
/// Print error.
std::ostream &operator<<(std::ostream &, Error &);
/// Settings for configuration parsing.
class Settings {
public:
/// Default settings.
inline Settings() {}
/// Set language for error messages.
///
/// Currently supported languages: `en`, `fr`.
///
/// \param lang IETF-like language tag.
/// The closest supported language will be used
/// (e.g. `fr-CA` will currently redirect to `fr`).
void set_error_language(std::string_view lang);
private:
void check_version() const;
void to_C(void *C) const;
friend class Configuration;
char m_error_lang[16] = {};
// to allow for future extensions without breaking backwards compatibility
const uint32_t version = 1;
};
/// Abstract base class for a file reader.
///
/// This can be passed to \ref Configuration::Configuration(std::string_view, Reader &, const Settings *)
class Reader {
public:
/// Read up to `count` bytes of data into `buf`.
///
/// This method will not be called excessively/with lots of tiny reads—it's
/// okay to do unbuffered reads in it.
///
/// \param buf Place to put data.
/// \param count (Non-zero) number of bytes to read
///
/// \returns 0 to indicate the end of the file,
/// or a positive value ≤ `count`, to indicate the number of bytes read.
virtual size_t read(char *buf, size_t count) = 0;
};
/// An item in a configuration (returned by \ref Configuration::items).
class Item {
public:
/// Name of the key.
///
/// The returned string view is valid for the lifetime of `this`.
std::string_view key() const noexcept { return m_key; }
/// Value of the key.
///
/// The returned string view is valid for the lifetime of `this`.
std::string_view value() const noexcept { return m_value; }
/// File name where key was defined.
///
/// The returned string view is valid for the lifetime of `this`.
std::string_view file() const noexcept { return m_file; }
/// Line number where key was defined.
uint64_t line() const noexcept { return m_line; }
private:
Item(std::string_view key, std::string_view value, std::string_view file, uint64_t line):
m_key(key), m_value(value), m_file(file), m_line(line) {}
friend class Configuration;
std::string m_key, m_value, m_file;
uint64_t m_line;
};
/// A source location.
class Location {
public:
/// File name
///
/// The returned string view is valid for the lifetime of `this`.
inline std::string_view file() const { return m_file; }
/// Line number
inline uint64_t line() const { return m_line; }
private:
inline Location(std::string file, uint64_t line):
m_file(file), m_line(line) {}
friend class Configuration;
std::string m_file;
uint64_t m_line;
// to allow for future extensions without breaking backwards compatibility
const uint32_t version = 1;
};
/// Print location as `file``:``line` (exact format may change in future).
std::ostream &operator<<(std::ostream &, const Location &);
/// A POM configuration.
class Configuration {
public:
/// Create empty configuration.
Configuration();
/// Set `this` to `other`, deleting the current configuration in `this`.
Configuration &operator=(const Configuration &other);
/// Copy configuration from `other`.
inline Configuration(const Configuration &other) { *this = other; };
/// Load configuration from abstract \ref Reader.
///
/// Most of the time, you will be able to use another constructor to load a configuration.
/// But if you have special functions for performing reads, you may need this.
///
/// \param filename File name for error messages.
/// \param source Abstract reader to get file data from.
/// \param settings Settings for parsing the file, or `nullptr` to use defaults.
Configuration(std::string_view filename, Reader &source, const Settings *settings = nullptr);
/// Load configuration from a `std::istream`.
///
/// \param filename File name for error messages.
/// \param stream Stream to read file from.
/// \param settings Settings for parsing the file, or `nullptr` to use defaults.
Configuration(std::string_view filename, std::istream &stream, const Settings *settings = nullptr);
/// Load configuration from a file path.
///
/// \param path File name for error messages and to load configuration from.
/// \param settings Settings for parsing the file, or `nullptr` to use defaults.
Configuration(std::string_view path, const Settings *settings = nullptr);
/// Load configuration from a string.
///
/// \param filename File name for error messages.
/// \param string String containing the configuration.
/// \param settings Settings for parsing the file, or `nullptr` to use defaults.
Configuration(std::string_view filename, std::string_view string, const Settings *settings = nullptr);
~Configuration();
/// Get value of `key` in configuration.
std::optional<std::string> get(std::string_view key) const;
/// Get value of `key` in configuration, or `dflt` if `key` isn't defined.
std::string get_or_default(std::string_view key, std::string_view dflt) const;
/// Get signed integer value of `key` in configuration.
///
/// Throws an \ref Error if `key` exists but isn't a valid signed integer.
std::optional<int64_t> get_int(std::string_view key) const;
/// Get signed integer value of `key` in configuration, or `dflt` if `key` isn't defined.
///
/// Throws an \ref Error if `key` exists but isn't a valid signed integer.
int64_t get_int_or_default(std::string_view key, int64_t dflt) const;
/// Get unsigned integer value of `key` in configuration.
///
/// Throws an \ref Error if `key` exists but isn't a valid unsigned integer.
std::optional<uint64_t> get_uint(std::string_view key) const;
/// Get unsigned integer value of `key` in configuration, or `dflt` if `key` isn't defined.
///
/// Throws an \ref Error if `key` exists but isn't a valid unsigned integer.
uint64_t get_uint_or_default(std::string_view key, uint64_t dflt) const;
/// Get floating-point value of `key` in configuration.
///
/// Throws an \ref Error if `key` exists but isn't a valid floating-point number.
std::optional<double> get_float(std::string_view key) const;
/// Get floating-point value of `key` in configuration, or `dflt` if `key` isn't defined.
///
/// Throws an \ref Error if `key` exists but isn't a valid floating-point number.
double get_float_or_default(std::string_view key, double dflt) const;
/// Get boolean value of `key` in configuration.
///
/// Throws an \ref Error if `key` exists but isn't a valid boolean (`on`/`off`/`yes`/`no`/`true`/`false`).
std::optional<bool> get_bool(std::string_view key) const;
/// Get boolean value of `key` in configuration, or `dflt` if `key` isn't defined.
///
/// Throws an \ref Error if `key` exists but isn't a valid boolean (`on`/`off`/`yes`/`no`/`true`/`false`).
bool get_bool_or_default(std::string_view key, bool dflt) const;
/// Get list value of `key` in configuration.
std::optional<std::vector<std::string>> get_list(std::string_view key) const;
/// Get list value of `key` in configuration, or `dflt` if `key` isn't defined.
std::vector<std::string> get_list_or_default(std::string_view key, const std::vector<std::string> &dflt) const;
/// Returns whether `key` is in this configuration.
bool has(std::string_view key) const;
/// Returns location of `key` in this configuration.
std::optional<Location> location(std::string_view key) const;
/// Extract section of configuration consisting of all keys starting with `name.` and their values.
Configuration section(std::string_view name) const;
/// Get list of keys which haven't been the target of a `get_*` method.
std::vector<std::string> unread_keys() const;
/// Get list of all "direct" keys (unique first components of keys) in this configuration.
std::vector<std::string> keys() const;
/// Get all key-value pairs in this configuration.
///
/// This returns a vector of pointers, so that more fields can be added to items in the future
/// without breaking binary backwards compatibility.
std::vector<std::unique_ptr<Item>> items() const;
/// Merge `other` configuration into `this`.
///
/// Puts all the key-value pairs of `other` into this configuration.
/// If `this` and `other` both have a value for a key, the one
/// in `other` is preferred.
void merge(const Configuration &other);
private:
void load(std::string_view filename, Reader &source, const Settings *settings);
explicit Configuration(void *c): C(c) {}
void *C;
};
/// Print configuration.
std::ostream &operator<<(std::ostream &, const Configuration &);
} // namespace pom
#endif // POM_HPP_
|