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
164 static INIReader FromString(std::string_view content) {
165 INIReader r;
166 r.Parse(content);
167 r.ParseError();
168 return r;
169 }
170
175 int ParseError() const {
176 switch (_error) {
177 case 0:
178 break;
179 case -1:
180 throw std::runtime_error("ini file not found.");
181 case -2:
182 throw std::runtime_error("memory alloc error");
183 default:
184 throw std::runtime_error("parse error on line no: " +
185 std::to_string(_error));
186 }
187 return 0;
188 }
189
194 std::set<std::string> Sections() const {
195 std::set<std::string> retval;
196 for (const auto& element : _values) {
197 retval.insert(element.first);
198 }
199 return retval;
200 }
201
207 std::set<std::string> Keys(const std::string& section) const {
208 const auto& sec = GetSection(section);
209 std::set<std::string> retval;
210 for (const auto& element : sec) {
211 retval.insert(element.first);
212 }
213 return retval;
214 }
215
222 std::unordered_map<std::string, std::string> Get(
223 const std::string& section) const {
224 return GetSection(section);
225 }
226
235 template <typename T = std::string>
236 T Get(const std::string& section, const std::string& name) const {
237 const auto& sec = GetSection(section);
238 const auto value = sec.find(name);
239 if (value == sec.end()) {
240 throw std::runtime_error(
241 "key '" + name + "' not found in section '" + section + "'.");
242 }
243
244 if constexpr (std::is_same_v<T, std::string>) {
245 return value->second;
246 } else if constexpr (std::is_same_v<T, bool>) {
247 return BoolConverter(value->second);
248 } else {
249 return Converter<T>(value->second);
250 }
251 }
252
262 template <typename T>
263 T Get(const std::string& section, const std::string& name,
264 T&& default_v) const {
265 try {
266 return Get<T>(section, name);
267 } catch (std::runtime_error&) {
268 return std::forward<T>(default_v);
269 }
270 }
271
288 template <typename T = std::string>
289 std::vector<T> GetVector(const std::string& section,
290 const std::string& name) const {
291 const std::string value = Get(section, name);
292 try {
293 std::vector<T> vs;
294 std::size_t i = 0;
295 while (i < value.size()) {
296 while (i < value.size() && detail::is_space(value[i])) ++i;
297 std::size_t j = i;
298 while (j < value.size() && !detail::is_space(value[j])) ++j;
299 if (j > i)
300 vs.emplace_back(Converter<T>(value.substr(i, j - i)));
301 i = j;
302 }
303 return vs;
304 } catch (std::exception&) {
305 throw std::runtime_error("cannot parse value " + value +
306 " to vector<T>.");
307 }
308 }
309
321 template <typename T>
322 std::vector<T> GetVector(const std::string& section,
323 const std::string& name,
324 const std::vector<T>& default_v) const {
325 try {
326 return GetVector<T>(section, name);
327 } catch (std::runtime_error&) {
328 return default_v;
329 }
330 }
331
339 template <typename T = std::string>
340 void InsertEntry(const std::string& section, const std::string& name,
341 const T& v) {
342 if (!_values[section].emplace(name, V2String(v)).second) {
343 throw std::runtime_error("duplicate key '" + name +
344 "' in section '" + section + "'.");
345 }
346 }
347
355 template <typename T = std::string>
356 void InsertEntry(const std::string& section, const std::string& name,
357 const std::vector<T>& vs) {
358 if (!_values[section].emplace(name, Vec2String(vs)).second) {
359 throw std::runtime_error("duplicate key '" + name +
360 "' in section '" + section + "'.");
361 }
362 }
363
371 template <typename T = std::string>
372 void UpdateEntry(const std::string& section, const std::string& name,
373 const T& v) {
374 FindEntry(section, name) = V2String(v);
375 }
376
384 template <typename T = std::string>
385 void UpdateEntry(const std::string& section, const std::string& name,
386 const std::vector<T>& vs) {
387 FindEntry(section, name) = Vec2String(vs);
388 }
389
390 protected:
393 int _error = 0;
395 std::unordered_map<std::string,
396 std::unordered_map<std::string, std::string>>
398
400 template <typename T>
401 T Converter(const std::string& s) const {
402 if constexpr (std::is_same_v<T, std::string>) {
403 return s;
404 } else {
405 T v{};
406 if (!detail::parse_value(s, v)) {
407 throw std::runtime_error("cannot parse value '" + s +
408 "' to type<T>.");
409 }
410 return v;
411 }
412 }
413
416 bool BoolConverter(std::string s) const {
417 for (char& c : s) {
418 if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
419 }
420 static const std::unordered_map<std::string, bool> s2b{
421 {"1", true}, {"true", true}, {"yes", true}, {"on", true},
422 {"0", false}, {"false", false}, {"no", false}, {"off", false},
423 };
424 const auto value = s2b.find(s);
425 if (value == s2b.end()) {
426 throw std::runtime_error("'" + s +
427 "' is not a valid boolean value.");
428 }
429 return value->second;
430 }
431
433 template <typename T>
434 std::string V2String(const T& v) const {
435 std::ostringstream ss;
436 ss << v;
437 return ss.str();
438 }
439
441 template <typename T>
442 std::string Vec2String(const std::vector<T>& v) const {
443 std::ostringstream oss;
444 for (std::size_t i = 0; i < v.size(); ++i) {
445 if (i > 0) oss << ' ';
446 oss << v[i];
447 }
448 return oss.str();
449 }
450
451 private:
452 const std::unordered_map<std::string, std::string>& GetSection(
453 const std::string& section) const {
454 const auto sec = _values.find(section);
455 if (sec == _values.end()) {
456 throw std::runtime_error("section '" + section + "' not found.");
457 }
458 return sec->second;
459 }
460
461 std::string& FindEntry(const std::string& section,
462 const std::string& name) {
463 const auto sec = _values.find(section);
464 if (sec != _values.end()) {
465 const auto value = sec->second.find(name);
466 if (value != sec->second.end()) {
467 return value->second;
468 }
469 }
470 throw std::runtime_error("key '" + name + "' not exist in section '" +
471 section + "'.");
472 }
473
474 /* Parse the whole ini content. Grammar:
475 - `[section]` lines open a section; text after ']' is ignored
476 - `name = value` or `name : value` pairs, whitespace-trimmed
477 - lines starting with ';' or '#' are comments
478 - a ';' preceded by whitespace starts an inline comment
479 Records the first faulty line in _error and stops there. Throws on
480 duplicate keys. */
481 void Parse(std::string_view content) {
482 constexpr std::string_view bom{"\xEF\xBB\xBF", 3};
483 if (content.substr(0, bom.size()) == bom) {
484 content.remove_prefix(bom.size());
485 }
486
487 std::string section;
488 std::unordered_map<std::string, std::string>* values = nullptr;
489 int lineno = 0;
490 _error = 0;
491
492 while (!content.empty()) {
493 ++lineno;
494 const auto eol = content.find('\n');
495 const auto line = detail::trim(content.substr(0, eol));
496 content.remove_prefix(eol == std::string_view::npos ? content.size()
497 : eol + 1);
498
499 if (line.empty() || line.front() == ';' || line.front() == '#') {
500 /* Blank line or comment */
501 } else if (line.front() == '[') {
502 /* A "[section]" line */
503 const auto end =
504 detail::find_char_or_comment(line.substr(1), "]");
505 if (end != std::string_view::npos && line[end + 1] == ']') {
506 section.assign(line.data() + 1, end);
507 values = nullptr;
508 } else {
509 /* No ']' found on section line */
510 _error = lineno;
511 break;
512 }
513 } else {
514 /* Not a comment, must be a name[=:]value pair */
515 const auto sep = detail::find_char_or_comment(line, "=:");
516 if (sep == std::string_view::npos || line[sep] == ';') {
517 /* No '=' or ':' found on name[=:]value line */
518 _error = lineno;
519 break;
520 }
521 const auto name = detail::rtrim(line.substr(0, sep));
522 auto value = line.substr(sep + 1);
523 const auto comment = detail::find_char_or_comment(value, {});
524 if (comment != std::string_view::npos) {
525 value = value.substr(0, comment);
526 }
527 value = detail::trim(value);
528
529 if (values == nullptr) {
530 values = &_values[section];
531 }
532 if (!values->emplace(std::string(name), std::string(value))
533 .second) {
534 throw std::runtime_error("duplicate key '" +
535 std::string(name) +
536 "' in section '" + section + "'.");
537 }
538 }
539 }
540 }
541};
542
547 public:
548 INIWriter() = default;
557 inline static void write(const std::string& filepath,
558 const INIReader& reader,
559 const bool overwrite = false) {
560 if (!overwrite && std::ifstream{filepath}) {
561 throw std::runtime_error("file: " + filepath + " already exists.");
562 }
563 std::ofstream out{filepath};
564 if (!out.is_open()) {
565 throw std::runtime_error("cannot open output file: " + filepath);
566 }
567 for (const auto& section : reader.Sections()) {
568 out << "[" << section << "]\n";
569 for (const auto& key : reader.Keys(section)) {
570 out << key << "=" << reader.Get(section, key) << "\n";
571 }
572 }
573 }
574};
575
576} // namespace inih
577
578#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:263
T Converter(const std::string &s) const
Parse s as a T; throws std::runtime_error on failure.
Definition ini.h:401
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > _values
Parsed content, as _values[section][name] = value.
Definition ini.h:397
int _error
Parse result: 0 on success, -1 on file open error, otherwise the number of the first faulty line.
Definition ini.h:393
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:356
std::string V2String(const T &v) const
Serialize a value with operator<<.
Definition ini.h:434
int ParseError() const
Return the result of the parse, i.e., 0 on success.
Definition ini.h:175
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:222
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:340
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:372
std::string Vec2String(const std::vector< T > &v) const
Serialize a vector as space-separated values.
Definition ini.h:442
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:194
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:289
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:236
static INIReader FromString(std::string_view content)
Construct an INIReader object from in-memory ini content.
Definition ini.h:164
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:385
std::set< std::string > Keys(const std::string &section) const
Return the list of keys in the given section.
Definition ini.h:207
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:416
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:322
Write the contents of an INIReader to an ini file.
Definition ini.h:546
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:557
Yet another .ini parser for modern c++ (made for cpp17), Project page: https://github....
Definition ini.h:32