summaryrefslogtreecommitdiff
path: root/cpp/pom.hpp
blob: 4e207274fa510a4b6c6db193cf7d1e75a7de9f42 (plain)
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
/// \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>

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.
	///
	/// `lang` should be an IETF-like language tag.
	///
	/// The closest supported language will be used
	/// (e.g. `fr-CA` will currently redirect to `fr`).
	///
	/// Currently supported: `en`, `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`.
	virtual size_t read(char *buf, size_t count) = 0;
};

/// An item in a configuration
///
/// This is an abstract class so that items can be given
/// more members in the future.
class Item {
public:
	inline virtual ~Item() {};
	virtual std::string_view key() const noexcept = 0;
	virtual std::string_view value() const noexcept = 0;
	virtual std::string_view file() const noexcept = 0;
	virtual uint64_t line() const noexcept = 0;
};

class Location {
public:
	inline std::string_view file() const { return m_file; }
	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;
};
std::ostream &operator<<(std::ostream &, const Location &);

class Configuration {
public:
	Configuration();
	Configuration &operator=(const Configuration &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.
	Configuration(std::string_view filename, Reader &source, const Settings *settings = nullptr);
	Configuration(std::string_view filename, std::istream &stream, const Settings *settings = nullptr);
	Configuration(std::string_view path, const Settings *settings = nullptr);
	Configuration(std::string_view filename, std::string_view string, const Settings *settings = nullptr);
	~Configuration();
	std::optional<Location> location(std::string_view key) const;
	std::optional<std::string> get(std::string_view key) const;
	std::string get_or_default(std::string_view key, std::string_view dflt) const;
	std::optional<int64_t> get_int(std::string_view key) const;
	int64_t get_int_or_default(std::string_view key, int64_t dflt) const;
	std::optional<uint64_t> get_uint(std::string_view key) const;
	uint64_t get_uint_or_default(std::string_view key, uint64_t dflt) const;
	std::optional<double> get_float(std::string_view key) const;
	double get_float_or_default(std::string_view key, double dflt) const;
	std::optional<bool> get_bool(std::string_view key) const;
	bool get_bool_or_default(std::string_view key, bool dflt) const;
	std::optional<std::vector<std::string>> get_list(std::string_view key) const;
	std::vector<std::string> get_list_or_default(std::string_view key, const std::vector<std::string> &dflt) const;
	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;
	std::vector<std::string> keys() const;
	std::vector<std::shared_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;
};
std::ostream &operator<<(std::ostream &, const Configuration &);




} // namespace pom

#endif // POM_HPP_