ini-cpp
Loading...
Searching...
No Matches
ini.h
1
6#ifndef INI_CPP_INI_H_
7#define INI_CPP_INI_H_
8
9#include <cstddef>
10#include <cstdio>
11#include <fstream>
12#include <set>
13#include <sstream>
14#include <stdexcept>
15#include <string>
16#include <string_view>
17#include <type_traits>
18#include <unordered_map>
19#include <utility>
20#include <vector>
21
22#if defined(__has_include)
23#if __has_include(<charconv>)
24#include <charconv>
25#define INI_CPP_HAS_CHARCONV 1
26#if defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L
27#define INI_CPP_HAS_FLOAT_CHARCONV 1
28#endif
29#endif
30#endif
31
32namespace inih {
33
34namespace detail {
35
36/* Locale-independent whitespace check (same set as C isspace). */
37inline constexpr bool is_space(char c) noexcept {
38 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' ||
39 c == '\v';
40}
41
42inline std::string_view ltrim(std::string_view s) noexcept {
43 while (!s.empty() && is_space(s.front())) s.remove_prefix(1);
44 return s;
45}
46
47inline std::string_view rtrim(std::string_view s) noexcept {
48 while (!s.empty() && is_space(s.back())) s.remove_suffix(1);
49 return s;
50}
51
52inline std::string_view trim(std::string_view s) noexcept {
53 return ltrim(rtrim(s));
54}
55
56/* Return index of the first char of `chars` or of an inline comment (a ';'
57 preceded by whitespace) in `s`, or npos if neither is found. */
58inline std::size_t find_char_or_comment(std::string_view s,
59 std::string_view chars) noexcept {
60 bool was_space = false;
61 for (std::size_t i = 0; i < s.size(); ++i) {
62 const char c = s[i];
63 if (chars.find(c) != std::string_view::npos ||
64 (was_space && c == ';')) {
65 return i;
66 }
67 was_space = is_space(c);
68 }
69 return std::string_view::npos;
70}
71
72/* True for types that can be parsed with std::from_chars. Character types
73 are excluded so they keep stream semantics ("7" -> '7', not int 7). */
74template <typename T>
75inline constexpr bool use_charconv =
76#if defined(INI_CPP_HAS_FLOAT_CHARCONV)
77 std::is_floating_point_v<T> ||
78#endif
79 (std::is_integral_v<T> && !std::is_same_v<T, bool> &&
80 !std::is_same_v<T, char> && !std::is_same_v<T, signed char> &&
81 !std::is_same_v<T, unsigned char> && !std::is_same_v<T, wchar_t> &&
82 !std::is_same_v<T, char16_t> && !std::is_same_v<T, char32_t>);
83
84/* Parse `s` into `v`, return false on failure. Uses std::from_chars for
85 numbers when available, stream extraction otherwise. */
86template <typename T>
87inline bool parse_value(const std::string& s, T& v) {
88#if defined(INI_CPP_HAS_CHARCONV)
89 if constexpr (use_charconv<T>) {
90 const char* first = s.data();
91 const char* const last = s.data() + s.size();
92 while (first != last && is_space(*first)) ++first;
93 // istream compatibility: allow an explicit leading '+'
94 if (last - first > 1 && *first == '+' &&
95 ((first[1] >= '0' && first[1] <= '9') || first[1] == '.')) {
96 ++first;
97 }
98 return std::from_chars(first, last, v).ec == std::errc{};
99 } else
100#endif
101 {
102 std::istringstream in{s};
103 in >> v;
104 return !in.fail();
105 }
106}
107
108} // namespace detail
109
114 public:
115 // Empty Constructor
116 INIReader() = default;
117
123 INIReader(const std::string& filename) {
124 std::ifstream in{filename, std::ios::in | std::ios::binary};
125 if (!in) {
126 _error = -1;
127 ParseError();
128 return;
129 }
130 std::string content;
131 in.seekg(0, std::ios::end);
132 const auto size = in.tellg();
133 if (size > 0) {
134 content.resize(static_cast<std::size_t>(size));
135 in.seekg(0, std::ios::beg);
136 in.read(&content[0], size);
137 }
138 Parse(content);
139 ParseError();
140 }
141
147 INIReader(std::FILE* file) {
148 std::string content;
149 char buf[1 << 15];
150 std::size_t n = 0;
151 while ((n = std::fread(buf, 1, sizeof(buf), file)) > 0) {
152 content.append(buf, n);
153 }
154 Parse(content);
155 ParseError();
156 }
157
162 int ParseError() const {
163 switch (_error) {
164 case 0:
165 break;
166 case -1:
167 throw std::runtime_error("ini file not found.");
168 case -2:
169 throw std::runtime_error("memory alloc error");
170 default:
171 throw std::runtime_error("parse error on line no: " +
172 std::to_string(_error));
173 }
174 return 0;
175 }
176
181 std::set<std::string> Sections() const {
182 std::set<std::string> retval;
183 for (const auto& element : _values) {
184 retval.insert(element.first);
185 }
186 return retval;
187 }
188
194 std::set<std::string> Keys(const std::string& section) const {
195 const auto& sec = GetSection(section);
196 std::set<std::string> retval;
197 for (const auto& element : sec) {
198 retval.insert(element.first);
199 }
200 return retval;
201 }
202
209 std::unordered_map<std::string, std::string> Get(
210 const std::string& section) const {
211 return GetSection(section);
212 }
213
222 template <typename T = std::string>
223 T Get(const std::string& section, const std::string& name) const {
224 const auto& sec = GetSection(section);
225 const auto value = sec.find(name);
226 if (value == sec.end()) {
227 throw std::runtime_error(
228 "key '" + name + "' not found in section '" + section + "'.");
229 }
230
231 if constexpr (std::is_same_v<T, std::string>) {
232 return value->second;
233 } else if constexpr (std::is_same_v<T, bool>) {
234 return BoolConverter(value->second);
235 } else {
236 return Converter<T>(value->second);
237 }
238 }
239
249 template <typename T>
250 T Get(const std::string& section, const std::string& name,
251 T&& default_v) const {
252 try {
253 return Get<T>(section, name);
254 } catch (std::runtime_error&) {
255 return std::forward<T>(default_v);
256 }
257 }
258
275 template <typename T = std::string>
276 std::vector<T> GetVector(const std::string& section,
277 const std::string& name) const {
278 const std::string value = Get(section, name);
279 try {
280 std::vector<T> vs;
281 std::size_t i = 0;
282 while (i < value.size()) {
283 while (i < value.size() && detail::is_space(value[i])) ++i;
284 std::size_t j = i;
285 while (j < value.size() && !detail::is_space(value[j])) ++j;
286 if (j > i)
287 vs.emplace_back(Converter<T>(value.substr(i, j - i)));
288 i = j;
289 }
290 return vs;
291 } catch (std::exception&) {
292 throw std::runtime_error("cannot parse value " + value +
293 " to vector<T>.");
294 }
295 }
296
308 template <typename T>
309 std::vector<T> GetVector(const std::string& section,
310 const std::string& name,
311 const std::vector<T>& default_v) const {
312 try {
313 return GetVector<T>(section, name);
314 } catch (std::runtime_error&) {
315 return default_v;
316 }
317 }
318
326 template <typename T = std::string>
327 void InsertEntry(const std::string& section, const std::string& name,
328 const T& v) {
329 if (!_values[section].emplace(name, V2String(v)).second) {
330 throw std::runtime_error("duplicate key '" + name +
331 "' in section '" + section + "'.");
332 }
333 }
334
342 template <typename T = std::string>
343 void InsertEntry(const std::string& section, const std::string& name,
344 const std::vector<T>& vs) {
345 if (!_values[section].emplace(name, Vec2String(vs)).second) {
346 throw std::runtime_error("duplicate key '" + name +
347 "' in section '" + section + "'.");
348 }
349 }
350
358 template <typename T = std::string>
359 void UpdateEntry(const std::string& section, const std::string& name,
360 const T& v) {
361 FindEntry(section, name) = V2String(v);
362 }
363
371 template <typename T = std::string>
372 void UpdateEntry(const std::string& section, const std::string& name,
373 const std::vector<T>& vs) {
374 FindEntry(section, name) = Vec2String(vs);
375 }
376
377 protected:
380 int _error = 0;
382 std::unordered_map<std::string,
383 std::unordered_map<std::string, std::string>>
385
387 template <typename T>
388 T Converter(const std::string& s) const {
389 if constexpr (std::is_same_v<T, std::string>) {
390 return s;
391 } else {
392 T v{};
393 if (!detail::parse_value(s, v)) {
394 throw std::runtime_error("cannot parse value '" + s +
395 "' to type<T>.");
396 }
397 return v;
398 }
399 }
400
403 bool BoolConverter(std::string s) const {
404 for (char& c : s) {
405 if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
406 }
407 static const std::unordered_map<std::string, bool> s2b{
408 {"1", true}, {"true", true}, {"yes", true}, {"on", true},
409 {"0", false}, {"false", false}, {"no", false}, {"off", false},
410 };
411 const auto value = s2b.find(s);
412 if (value == s2b.end()) {
413 throw std::runtime_error("'" + s +
414 "' is not a valid boolean value.");
415 }
416 return value->second;
417 }
418
420 template <typename T>
421 std::string V2String(const T& v) const {
422 std::ostringstream ss;
423 ss << v;
424 return ss.str();
425 }
426
428 template <typename T>
429 std::string Vec2String(const std::vector<T>& v) const {
430 std::ostringstream oss;
431 for (std::size_t i = 0; i < v.size(); ++i) {
432 if (i > 0) oss << ' ';
433 oss << v[i];
434 }
435 return oss.str();
436 }
437
438 private:
439 const std::unordered_map<std::string, std::string>& GetSection(
440 const std::string& section) const {
441 const auto sec = _values.find(section);
442 if (sec == _values.end()) {
443 throw std::runtime_error("section '" + section + "' not found.");
444 }
445 return sec->second;
446 }
447
448 std::string& FindEntry(const std::string& section,
449 const std::string& name) {
450 const auto sec = _values.find(section);
451 if (sec != _values.end()) {
452 const auto value = sec->second.find(name);
453 if (value != sec->second.end()) {
454 return value->second;
455 }
456 }
457 throw std::runtime_error("key '" + name + "' not exist in section '" +
458 section + "'.");
459 }
460
461 /* Parse the whole ini content. Grammar:
462 - `[section]` lines open a section; text after ']' is ignored
463 - `name = value` or `name : value` pairs, whitespace-trimmed
464 - lines starting with ';' or '#' are comments
465 - a ';' preceded by whitespace starts an inline comment
466 Records the first faulty line in _error and stops there. Throws on
467 duplicate keys. */
468 void Parse(std::string_view content) {
469 constexpr std::string_view bom{"\xEF\xBB\xBF", 3};
470 if (content.substr(0, bom.size()) == bom) {
471 content.remove_prefix(bom.size());
472 }
473
474 std::string section;
475 std::unordered_map<std::string, std::string>* values = nullptr;
476 int lineno = 0;
477 _error = 0;
478
479 while (!content.empty()) {
480 ++lineno;
481 const auto eol = content.find('\n');
482 const auto line = detail::trim(content.substr(0, eol));
483 content.remove_prefix(eol == std::string_view::npos ? content.size()
484 : eol + 1);
485
486 if (line.empty() || line.front() == ';' || line.front() == '#') {
487 /* Blank line or comment */
488 } else if (line.front() == '[') {
489 /* A "[section]" line */
490 const auto end =
491 detail::find_char_or_comment(line.substr(1), "]");
492 if (end != std::string_view::npos && line[end + 1] == ']') {
493 section.assign(line.data() + 1, end);
494 values = nullptr;
495 } else {
496 /* No ']' found on section line */
497 _error = lineno;
498 break;
499 }
500 } else {
501 /* Not a comment, must be a name[=:]value pair */
502 const auto sep = detail::find_char_or_comment(line, "=:");
503 if (sep == std::string_view::npos || line[sep] == ';') {
504 /* No '=' or ':' found on name[=:]value line */
505 _error = lineno;
506 break;
507 }
508 const auto name = detail::rtrim(line.substr(0, sep));
509 auto value = line.substr(sep + 1);
510 const auto comment = detail::find_char_or_comment(value, {});
511 if (comment != std::string_view::npos) {
512 value = value.substr(0, comment);
513 }
514 value = detail::trim(value);
515
516 if (values == nullptr) {
517 values = &_values[section];
518 }
519 if (!values->emplace(std::string(name), std::string(value))
520 .second) {
521 throw std::runtime_error("duplicate key '" +
522 std::string(name) +
523 "' in section '" + section + "'.");
524 }
525 }
526 }
527 }
528};
529
534 public:
535 INIWriter() = default;
544 inline static void write(const std::string& filepath,
545 const INIReader& reader,
546 const bool overwrite = false) {
547 if (!overwrite && std::ifstream{filepath}) {
548 throw std::runtime_error("file: " + filepath + " already exists.");
549 }
550 std::ofstream out{filepath};
551 if (!out.is_open()) {
552 throw std::runtime_error("cannot open output file: " + filepath);
553 }
554 for (const auto& section : reader.Sections()) {
555 out << "[" << section << "]\n";
556 for (const auto& key : reader.Keys(section)) {
557 out << key << "=" << reader.Get(section, key) << "\n";
558 }
559 }
560 }
561};
562
563} // namespace inih
564
565#endif // INI_CPP_INI_H_
Read an INI file into easy-to-access name/value pairs.
Definition ini.h:113
T Get(const std::string &section, const std::string &name, T &&default_v) const
Return the value of the given key in the given section, return default if not found.
Definition ini.h:250
T Converter(const std::string &s) const
Parse s as a T; throws std::runtime_error on failure.
Definition ini.h:388
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > _values
Parsed content, as _values[section][name] = value.
Definition ini.h:384
int _error
Parse result: 0 on success, -1 on file open error, otherwise the number of the first faulty line.
Definition ini.h:380
void InsertEntry(const std::string &section, const std::string &name, const std::vector< T > &vs)
Insert a vector of values into the INI file.
Definition ini.h:343
std::string V2String(const T &v) const
Serialize a value with operator<<.
Definition ini.h:421
int ParseError() const
Return the result of the parse, i.e., 0 on success.
Definition ini.h:162
std::unordered_map< std::string, std::string > Get(const std::string &section) const
Get the map representing the values in a section of the INI file.
Definition ini.h:209
void InsertEntry(const std::string &section, const std::string &name, const T &v)
Insert a key-value pair into the INI file.
Definition ini.h:327
void UpdateEntry(const std::string &section, const std::string &name, const T &v)
Update a key-value pair in the INI file.
Definition ini.h:359
std::string Vec2String(const std::vector< T > &v) const
Serialize a vector as space-separated values.
Definition ini.h:429
INIReader(std::FILE *file)
Construct an INIReader object from a file pointer.
Definition ini.h:147
std::set< std::string > Sections() const
Return the list of sections found in ini file.
Definition ini.h:181
std::vector< T > GetVector(const std::string &section, const std::string &name) const
Return the value array of the given key in the given section.
Definition ini.h:276
INIReader(const std::string &filename)
Construct an INIReader object from a file name.
Definition ini.h:123
T Get(const std::string &section, const std::string &name) const
Return the value of the given key in the given section.
Definition ini.h:223
void UpdateEntry(const std::string &section, const std::string &name, const std::vector< T > &vs)
Update a vector of values in the INI file.
Definition ini.h:372
std::set< std::string > Keys(const std::string &section) const
Return the list of keys in the given section.
Definition ini.h:194
bool BoolConverter(std::string s) const
Parse a boolean token: 1/0/true/false/yes/no/on/off, case-insensitive; throws std::runtime_error on a...
Definition ini.h:403
std::vector< T > GetVector(const std::string &section, const std::string &name, const std::vector< T > &default_v) const
Return the value array of the given key in the given section, return default if not found.
Definition ini.h:309
Write the contents of an INIReader to an ini file.
Definition ini.h:533
static void write(const std::string &filepath, const INIReader &reader, const bool overwrite=false)
Write the contents of an INI file to a new file.
Definition ini.h:544
Yet another .ini parser for modern c++ (made for cpp17), Project page: https://github....
Definition ini.h:32