ini-cpp
ini.h
1 
6 #ifndef __INI_H__
7 #define __INI_H__
8 
9 #include <ctype.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 
14 #include <algorithm>
15 #include <cctype>
16 #include <cstdlib>
17 #include <fstream>
18 #include <iostream>
19 #include <iterator>
20 #include <map>
21 #include <set>
22 #include <sstream>
23 #include <string>
24 #include <type_traits>
25 #include <unordered_map>
26 #include <vector>
27 
28 namespace inih {
29 
30 /* Typedef for prototype of handler function. */
31 typedef int (*ini_handler)(void* user, const char* section, const char* name,
32  const char* value);
33 
34 /* Typedef for prototype of fgets-style reader function. */
35 typedef char* (*ini_reader)(char* str, int num, void* stream);
36 
37 #define INI_STOP_ON_FIRST_ERROR 1
38 #define INI_MAX_LINE 2000
39 #define INI_INITIAL_ALLOC 200
40 #define MAX_SECTION 50
41 #define MAX_NAME 50
42 #define INI_START_COMMENT_PREFIXES ";#"
43 #define INI_INLINE_COMMENT_PREFIXES ";"
44 
45 /* Strip whitespace chars off end of given string, in place. Return s. */
46 inline static char* rstrip(char* s) {
47  char* p = s + strlen(s);
48  while (p > s && isspace((unsigned char)(*--p))) *p = '\0';
49  return s;
50 }
51 
52 /* Return pointer to first non-whitespace char in given string. */
53 inline static char* lskip(const char* s) {
54  while (*s && isspace((unsigned char)(*s))) s++;
55  return (char*)s;
56 }
57 
58 /* Return pointer to first char (of chars) or inline comment in given string,
59  or pointer to null at end of string if neither found. Inline comment must
60  be prefixed by a whitespace character to register as a comment. */
61 inline static char* find_chars_or_comment(const char* s, const char* chars) {
62  int was_space = 0;
63  while (*s && (!chars || !strchr(chars, *s)) &&
64  !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
65  was_space = isspace((unsigned char)(*s));
66  s++;
67  }
68  return (char*)s;
69 }
70 
71 /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
72 inline static char* strncpy0(char* dest, const char* src, size_t size) {
73  strncpy(dest, src, size - 1);
74  dest[size - 1] = '\0';
75  return dest;
76 }
77 
78 /* See documentation in header file. */
79 inline int ini_parse_stream(ini_reader reader, void* stream,
80  ini_handler handler, void* user) {
81  /* Uses a fair bit of stack (use heap instead if you need to) */
82  char* line;
83  size_t max_line = INI_INITIAL_ALLOC;
84  char* new_line;
85  size_t offset;
86  char section[MAX_SECTION] = "";
87  char prev_name[MAX_NAME] = "";
88 
89  char* start;
90  char* end;
91  char* name;
92  char* value;
93  int lineno = 0;
94  int error = 0;
95 
96  line = (char*)malloc(INI_INITIAL_ALLOC);
97  if (!line) {
98  return -2;
99  }
100 
101 #if INI_HANDLER_LINENO
102 #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
103 #else
104 #define HANDLER(u, s, n, v) handler(u, s, n, v)
105 #endif
106 
107  /* Scan through stream line by line */
108  while (reader(line, (int)max_line, stream) != NULL) {
109  offset = strlen(line);
110  while (offset == max_line - 1 && line[offset - 1] != '\n') {
111  max_line *= 2;
112  if (max_line > INI_MAX_LINE) max_line = INI_MAX_LINE;
113  new_line = (char*)realloc(line, max_line);
114  if (!new_line) {
115  free(line);
116  return -2;
117  }
118  line = new_line;
119  if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
120  break;
121  if (max_line >= INI_MAX_LINE) break;
122  offset += strlen(line + offset);
123  }
124 
125  lineno++;
126 
127  start = line;
128  if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
129  (unsigned char)start[1] == 0xBB &&
130  (unsigned char)start[2] == 0xBF) {
131  start += 3;
132  }
133  start = lskip(rstrip(start));
134 
135  if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
136  /* Start-of-line comment */
137  } else if (*start == '[') {
138  /* A "[section]" line */
139  end = find_chars_or_comment(start + 1, "]");
140  if (*end == ']') {
141  *end = '\0';
142  strncpy0(section, start + 1, sizeof(section));
143  *prev_name = '\0';
144  } else if (!error) {
145  /* No ']' found on section line */
146  error = lineno;
147  }
148  } else if (*start) {
149  /* Not a comment, must be a name[=:]value pair */
150  end = find_chars_or_comment(start, "=:");
151  if (*end == '=' || *end == ':') {
152  *end = '\0';
153  name = rstrip(start);
154  value = end + 1;
155  end = find_chars_or_comment(value, NULL);
156  if (*end) *end = '\0';
157  value = lskip(value);
158  rstrip(value);
159 
160  /* Valid name[=:]value pair found, call handler */
161  strncpy0(prev_name, name, sizeof(prev_name));
162  if (!HANDLER(user, section, name, value) && !error)
163  error = lineno;
164  } else if (!error) {
165  /* No '=' or ':' found on name[=:]value line */
166  error = lineno;
167  }
168  }
169 
170  if (error) break;
171  }
172 
173  free(line);
174 
175  return error;
176 }
177 
178 inline int ini_parse_file(FILE* file, ini_handler handler, void* user) {
179  return ini_parse_stream((ini_reader)fgets, file, handler, user);
180 }
181 
182 inline int ini_parse(const char* filename, ini_handler handler, void* user) {
183  FILE* file;
184  int error;
185 
186  file = fopen(filename, "r");
187  if (!file) return -1;
188  error = ini_parse_file(file, handler, user);
189  fclose(file);
190  return error;
191 }
192 
193 #endif /* __INI_H__ */
194 
195 #ifndef __INIREADER_H__
196 #define __INIREADER_H__
197 
198 // Read an INI file into easy-to-access name/value pairs. (Note that I've gone
199 // for simplicity here rather than speed, but it should be pretty decent.)
200 class INIReader {
201  public:
202  // Empty Constructor
203  INIReader(){};
204 
205  // Construct INIReader and parse given filename. See ini.h for more info
206  // about the parsing.
207  INIReader(std::string filename);
208 
209  // Construct INIReader and parse given file. See ini.h for more info
210  // about the parsing.
211  INIReader(FILE* file);
212 
213  // Return the result of ini_parse(), i.e., 0 on success, line number of
214  // first error on parse error, or -1 on file open error.
215  int ParseError() const;
216 
217  // Return the list of sections found in ini file
218  const std::set<std::string> Sections() const;
219 
220  // Return the list of keys in the given section
221  const std::set<std::string> Keys(std::string section) const;
222 
223  const std::unordered_map<std::string, std::string> Get(
224  std::string section) const;
225 
226  template <typename T = std::string>
227  T Get(const std::string& section, const std::string& name) const;
228 
229  template <typename T>
230  T Get(const std::string& section, const std::string& name,
231  T&& default_v) const;
232 
233  template <typename T = std::string>
234  std::vector<T> GetVector(const std::string& section,
235  const std::string& name) const;
236 
237  template <typename T>
238  std::vector<T> GetVector(const std::string& section,
239  const std::string& name,
240  const std::vector<T>& default_v) const;
241 
242  template <typename T = std::string>
243  void InsertEntry(const std::string& section, const std::string& name,
244  const T& v);
245 
246  template <typename T = std::string>
247  void InsertEntry(const std::string& section, const std::string& name,
248  const std::vector<T>& vs);
249 
250  template <typename T = std::string>
251  void UpdateEntry(const std::string& section, const std::string& name,
252  const T& v);
253 
254  template <typename T = std::string>
255  void UpdateEntry(const std::string& section, const std::string& name,
256  const std::vector<T>& vs);
257 
258  protected:
259  int _error;
260  std::unordered_map<std::string,
261  std::unordered_map<std::string, std::string>>
262  _values;
263  static int ValueHandler(void* user, const char* section, const char* name,
264  const char* value);
265 
266  template <typename T>
267  T Converter(const std::string& s) const;
268 
269  const bool BoolConverter(std::string s) const;
270 
271  template <typename T>
272  std::string V2String(const T& v) const;
273 
274  template <typename T>
275  std::string Vec2String(const std::vector<T>& v) const;
276 };
277 
278 #endif // __INIREADER_H__
279 
280 #ifndef __INIREADER__
281 #define __INIREADER__
282 
288 inline INIReader::INIReader(std::string filename) {
289  _error = ini_parse(filename.c_str(), ValueHandler, this);
290  ParseError();
291 }
292 
298 inline INIReader::INIReader(FILE* file) {
299  _error = ini_parse_file(file, ValueHandler, this);
300  ParseError();
301 }
302 
303 inline int INIReader::ParseError() const {
304  switch (_error) {
305  case 0:
306  break;
307  case -1:
308  throw std::runtime_error("ini file not found.");
309  case -2:
310  throw std::runtime_error("memory alloc error");
311  default:
312  throw std::runtime_error("parse error on line no: " +
313  std::to_string(_error));
314  }
315  return 0;
316 }
317 
322 inline const std::set<std::string> INIReader::Sections() const {
323  std::set<std::string> retval;
324  for (auto const& element : _values) {
325  retval.insert(element.first);
326  }
327  return retval;
328 }
329 
335 inline const std::set<std::string> INIReader::Keys(std::string section) const {
336  auto const _section = Get(section);
337  std::set<std::string> retval;
338  for (auto const& element : _section) {
339  retval.insert(element.first);
340  }
341  return retval;
342 }
343 
350 inline const std::unordered_map<std::string, std::string> INIReader::Get(
351  std::string section) const {
352  auto const _section = _values.find(section);
353  if (_section == _values.end()) {
354  throw std::runtime_error("section '" + section + "' not found.");
355  }
356  return _section->second;
357 }
358 
365 template <typename T>
366 inline T INIReader::Get(const std::string& section,
367  const std::string& name) const {
368  auto const _section = Get(section);
369  auto const _value = _section.find(name);
370 
371  if (_value == _section.end()) {
372  throw std::runtime_error("key '" + name + "' not found in section '" +
373  section + "'.");
374  }
375 
376  std::string value = _value->second;
377 
378  if constexpr (std::is_same<T, std::string>()) {
379  return value;
380  } else if constexpr (std::is_same<T, bool>()) {
381  return BoolConverter(value);
382  } else {
383  return Converter<T>(value);
384  };
385 }
386 
396 template <typename T>
397 inline T INIReader::Get(const std::string& section, const std::string& name,
398  T&& default_v) const {
399  try {
400  return Get<T>(section, name);
401  } catch (std::runtime_error& e) {
402  return default_v;
403  }
404 }
405 
422 template <typename T>
423 inline std::vector<T> INIReader::GetVector(const std::string& section,
424  const std::string& name) const {
425  std::string value = Get(section, name);
426 
427  std::istringstream out{value};
428  const std::vector<std::string> strs{std::istream_iterator<std::string>{out},
429  std::istream_iterator<std::string>()};
430  try {
431  std::vector<T> vs{};
432  for (const std::string& s : strs) {
433  vs.emplace_back(Converter<T>(s));
434  }
435  return vs;
436  } catch (std::exception& e) {
437  throw std::runtime_error("cannot parse value " + value +
438  " to vector<T>.");
439  }
440 }
441 
453 template <typename T>
454 inline std::vector<T> INIReader::GetVector(
455  const std::string& section, const std::string& name,
456  const std::vector<T>& default_v) const {
457  try {
458  return GetVector<T>(section, name);
459  } catch (std::runtime_error& e) {
460  return default_v;
461  };
462 }
463 
471 template <typename T>
472 inline void INIReader::InsertEntry(const std::string& section,
473  const std::string& name, const T& v) {
474  if (_values[section][name].size() > 0) {
475  throw std::runtime_error("duplicate key '" + std::string(name) +
476  "' in section '" + section + "'.");
477  }
478  _values[section][name] = V2String(v);
479 }
480 
488 template <typename T>
489 inline void INIReader::InsertEntry(const std::string& section,
490  const std::string& name,
491  const std::vector<T>& vs) {
492  if (_values[section][name].size() > 0) {
493  throw std::runtime_error("duplicate key '" + std::string(name) +
494  "' in section '" + section + "'.");
495  }
496  _values[section][name] = Vec2String(vs);
497 }
498 
506 template <typename T>
507 inline void INIReader::UpdateEntry(const std::string& section,
508  const std::string& name, const T& v) {
509  if (!_values[section][name].size()) {
510  throw std::runtime_error("key '" + std::string(name) +
511  "' not exist in section '" + section + "'.");
512  }
513  _values[section][name] = V2String(v);
514 }
515 
523 template <typename T>
524 inline void INIReader::UpdateEntry(const std::string& section,
525  const std::string& name,
526  const std::vector<T>& vs) {
527  if (!_values[section][name].size()) {
528  throw std::runtime_error("key '" + std::string(name) +
529  "' not exist in section '" + section + "'.");
530  }
531  _values[section][name] = Vec2String(vs);
532 }
533 
534 template <typename T>
535 inline std::string INIReader::V2String(const T& v) const {
536  std::stringstream ss;
537  ss << v;
538  return ss.str();
539 }
540 
541 template <typename T>
542 inline std::string INIReader::Vec2String(const std::vector<T>& v) const {
543  if (v.empty()) {
544  return "";
545  }
546  std::ostringstream oss;
547  std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(oss, " "));
548  oss << v.back();
549 
550  return oss.str();
551 }
552 
553 template <typename T>
554 inline T INIReader::Converter(const std::string& s) const {
555  try {
556  T v{};
557  std::istringstream _{s};
558  _.exceptions(std::ios::failbit);
559  _ >> v;
560  return v;
561  } catch (std::exception& e) {
562  throw std::runtime_error("cannot parse value '" + s + "' to type<T>.");
563  };
564 }
565 
566 inline const bool INIReader::BoolConverter(std::string s) const {
567  std::transform(s.begin(), s.end(), s.begin(), ::tolower);
568  static const std::unordered_map<std::string, bool> s2b{
569  {"1", true}, {"true", true}, {"yes", true}, {"on", true},
570  {"0", false}, {"false", false}, {"no", false}, {"off", false},
571  };
572  auto const value = s2b.find(s);
573  if (value == s2b.end()) {
574  throw std::runtime_error("'" + s + "' is not a valid boolean value.");
575  }
576  return value->second;
577 }
578 
579 inline int INIReader::ValueHandler(void* user, const char* section,
580  const char* name, const char* value) {
581  INIReader* reader = (INIReader*)user;
582  if (reader->_values[section][name].size() > 0) {
583  throw std::runtime_error("duplicate key '" + std::string(name) +
584  "' in section '" + section + "'.");
585  }
586  reader->_values[section][name] = value;
587  return 1;
588 }
589 #endif // __INIREADER__
590 
591 #ifndef __INIWRITER_H__
592 #define __INIWRITER_H__
593 
594 class INIWriter {
595  public:
596  INIWriter(){};
604  inline static void write(const std::string& filepath,
605  const INIReader& reader) {
606  if (struct stat buf; stat(filepath.c_str(), &buf) == 0) {
607  throw std::runtime_error("file: " + filepath + " already exist.");
608  }
609  std::ofstream out;
610  out.open(filepath);
611  if (!out.is_open()) {
612  throw std::runtime_error("cannot open output file: " + filepath);
613  }
614  for (const auto& section : reader.Sections()) {
615  out << "[" << section << "]\n";
616  for (const auto& key : reader.Keys(section)) {
617  out << key << "=" << reader.Get(section, key) << "\n";
618  }
619  }
620  out.close();
621  }
622 };
623 }
624 #endif /* __INIWRITER_H__ */
Definition: ini.h:200
const std::unordered_map< std::string, std::string > Get(std::string section) const
Get the map representing the values in a section of the INI file.
Definition: ini.h:350
const std::set< std::string > Sections() const
Return the list of sections found in ini file.
Definition: ini.h:322
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:472
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:507
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:423
const std::set< std::string > Keys(std::string section) const
Return the list of keys in the given section.
Definition: ini.h:335
Definition: ini.h:594
static void write(const std::string &filepath, const INIReader &reader)
Write the contents of an INI file to a new file.
Definition: ini.h:604
Yet another .ini parser for modern c++ (made for cpp17), inspired and extend from @benhoyt's inih.
Definition: ini.h:28