File Ini.h¶
File List > src > utils > Ini.h
Go to the documentation of this file
#ifndef __INI_H__
#define __INI_H__
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <iterator>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>
namespace inih {
/* Typedef for prototype of handler function. */
typedef int (*ini_handler)(void* user, const char* section, const char* name,
const char* value);
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
#define INI_STOP_ON_FIRST_ERROR 1
#define INI_MAX_LINE 2000
#define INI_INITIAL_ALLOC 200
#define MAX_SECTION 50
#define MAX_NAME 50
#define INI_START_COMMENT_PREFIXES ";#"
#define INI_INLINE_COMMENT_PREFIXES ";"
/* Strip whitespace chars off end of given string, in place. Return s. */
inline static char* rstrip(char* s) {
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p))) *p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
inline static char* lskip(const char* s) {
while (*s && isspace((unsigned char)(*s))) s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
inline static char* find_chars_or_comment(const char* s, const char* chars) {
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
inline static char* strncpy0(char* dest, const char* src, size_t size) {
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
inline int ini_parse_stream(ini_reader reader, void* stream,
ini_handler handler, void* user) {
/* Uses a fair bit of stack (use heap instead if you need to) */
char* line;
size_t max_line = INI_INITIAL_ALLOC;
char* new_line;
size_t offset;
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
line = (char*)malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, (int)max_line, stream) != NULL) {
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE) max_line = INI_MAX_LINE;
new_line = (char*)realloc(line, max_line);
if (!new_line) {
free(line);
return -2;
}
line = new_line;
if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
break;
if (max_line >= INI_MAX_LINE) break;
offset += strlen(line + offset);
}
lineno++;
start = line;
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
} else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
} else if (!error) {
/* No ']' found on section line */
error = lineno;
}
} else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
end = find_chars_or_comment(value, NULL);
if (*end) *end = '\0';
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
} else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
if (error) break;
}
free(line);
return error;
}
inline int ini_parse_file(FILE* file, ini_handler handler, void* user) {
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
inline int ini_parse(const char* filename, ini_handler handler, void* user) {
FILE* file;
int error;
file = fopen(filename, "a+");
if (!file) {
return -1;
}
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
#endif /* __INI_H__ */
#ifndef __INIREADER_H__
#define __INIREADER_H__
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
// for simplicity here rather than speed, but it should be pretty decent.)
class INIReader {
public:
// Empty Constructor
INIReader(){};
// Construct INIReader and parse given filename. See ini.h for more info
// about the parsing.
INIReader(std::string filename);
// Construct INIReader and parse given file. See ini.h for more info
// about the parsing.
INIReader(FILE* file);
// Return the result of ini_parse(), i.e., 0 on success, line number of
// first error on parse error, or -1 on file open error.
int ParseError() const;
// Return the list of sections found in ini file
const std::set<std::string> Sections() const;
// Return the list of keys in the given section
const std::set<std::string> Keys(std::string section) const;
const std::unordered_map<std::string, std::string> Get(
std::string section) const;
template <typename T = std::string>
T Get(const std::string& section, const std::string& name) ;
template <typename T>
T Get(const std::string& section, const std::string& name,
T& default_v) ;
template <typename T = std::string>
std::vector<T> GetVector(const std::string& section,
const std::string& name);
template <typename T>
std::vector<T> GetVector(const std::string& section,
const std::string& name,
const std::vector<T>& default_v);
template <typename T = std::string>
void InsertEntry(const std::string& section, const std::string& name,
const T& v);
template <typename T = std::string>
void InsertEntry(const std::string& section, const std::string& name,
const std::vector<T>& vs);
template <typename T = std::string>
void UpdateEntry(const std::string& section, const std::string& name,
const T& v);
template <typename T = std::string>
void UpdateEntry(const std::string& section, const std::string& name,
const std::vector<T>& vs);
protected:
int _error;
std::unordered_map<std::string,
std::unordered_map<std::string, std::string>>
_values;
static int ValueHandler(void* user, const char* section, const char* name,
const char* value);
template <typename T>
T Converter(const std::string& s) const;
const bool BoolConverter(std::string s) const;
template <typename T>
std::string V2String(const T& v) const;
template <typename T>
std::string Vec2String(const std::vector<T>& v) const;
};
#endif // __INIREADER_H__
#ifndef __INIREADER__
#define __INIREADER__
inline INIReader::INIReader(std::string filename) {
_error = ini_parse(filename.c_str(), ValueHandler, this);
ParseError();
}
inline INIReader::INIReader(FILE* file) {
_error = ini_parse_file(file, ValueHandler, this);
ParseError();
}
inline int INIReader::ParseError() const {
switch (_error) {
case 0:
break;
case -1:
throw std::runtime_error("ini file not found.");
case -2:
throw std::runtime_error("memory alloc error");
default:
throw std::runtime_error("parse error on line no: " +
std::to_string(_error));
}
return 0;
}
inline const std::set<std::string> INIReader::Sections() const {
std::set<std::string> retval;
for (auto const& element : _values) {
retval.insert(element.first);
}
return retval;
}
inline const std::set<std::string> INIReader::Keys(std::string section) const {
auto const _section = Get(section);
std::set<std::string> retval;
for (auto const& element : _section) {
retval.insert(element.first);
}
return retval;
}
inline const std::unordered_map<std::string, std::string> INIReader::Get(
std::string section) const {
auto const _section = _values.find(section);
if (_section == _values.end()) {
throw std::runtime_error("section '" + section + "' not found.");
}
return _section->second;
}
template <typename T>
inline T INIReader::Get(const std::string& section,
const std::string& name) {
auto const _section = Get(section);
auto const _value = _section.find(name);
if (_value == _section.end()) {
throw std::runtime_error("key '" + name + "' not found in section '" +
section + "'.");
}
std::string value = _value->second;
if constexpr (std::is_same<T, std::string>()) {
return value;
} else if constexpr (std::is_same<T, bool>()) {
return BoolConverter(value);
} else {
return Converter<T>(value);
};
}
template <typename T>
inline T INIReader::Get(const std::string& section, const std::string& name,
T& default_v) {
try {
return Get<T>(section, name);
} catch (std::runtime_error& e) {
INIReader::InsertEntry(section, name, default_v);
return default_v;
}
}
template <typename T>
inline std::vector<T> INIReader::GetVector(const std::string& section,
const std::string& name) {
std::string value = Get(section, name);
std::istringstream out{value};
const std::vector<std::string> strs{std::istream_iterator<std::string>{out},
std::istream_iterator<std::string>()};
try {
std::vector<T> vs{};
for (const std::string& s : strs) {
vs.emplace_back(Converter<T>(s));
}
return vs;
} catch (std::exception& e) {
throw std::runtime_error("cannot parse value " + value +
" to vector<T>.");
}
}
template <typename T>
inline std::vector<T> INIReader::GetVector(
const std::string& section, const std::string& name,
const std::vector<T>& default_v) {
try {
return GetVector<T>(section, name);
} catch (std::runtime_error& e) {
INIReader::InsertEntry(section, name, default_v);
return default_v;
};
}
template <typename T>
inline void INIReader::InsertEntry(const std::string& section,
const std::string& name, const T& v) {
// if (_values[section][name].size() > 0) {
// throw std::runtime_error("duplicate key '" + std::string(name) +
// "' in section '" + section + "'.");
// }
_values[section][name] = V2String(v);
}
template <typename T>
inline void INIReader::InsertEntry(const std::string& section,
const std::string& name,
const std::vector<T>& vs) {
// if (_values[section][name].size() > 0) {
// throw std::runtime_error("duplicate key '" + std::string(name) +
// "' in section '" + section + "'.");
// }
_values[section][name] = Vec2String(vs);
}
template <typename T>
inline void INIReader::UpdateEntry(const std::string& section,
const std::string& name, const T& v) {
// if (!_values[section][name].size()) {
// throw std::runtime_error("key '" + std::string(name) +
// "' not exist in section '" + section + "'.");
// }
_values[section][name] = V2String(v);
}
template <typename T>
inline void INIReader::UpdateEntry(const std::string& section,
const std::string& name,
const std::vector<T>& vs) {
// if (!_values[section][name].size()) {
// throw std::runtime_error("key '" + std::string(name) +
// "' not exist in section '" + section + "'.");
// }
_values[section][name] = Vec2String(vs);
}
template <typename T>
inline std::string INIReader::V2String(const T& v) const {
std::stringstream ss;
ss << v;
return ss.str();
}
template <typename T>
inline std::string INIReader::Vec2String(const std::vector<T>& v) const {
if (v.empty()) {
return "";
}
std::ostringstream oss;
std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(oss, " "));
oss << v.back();
return oss.str();
}
template <typename T>
inline T INIReader::Converter(const std::string& s) const {
try {
T v{};
std::istringstream _{s};
_.exceptions(std::ios::failbit);
_ >> v;
return v;
} catch (std::exception& e) {
throw std::runtime_error("cannot parse value '" + s + "' to type<T>.");
};
}
inline const bool INIReader::BoolConverter(std::string s) const {
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
static const std::unordered_map<std::string, bool> s2b{
{"1", true}, {"true", true}, {"yes", true}, {"on", true},
{"0", false}, {"false", false}, {"no", false}, {"off", false},
};
auto const value = s2b.find(s);
if (value == s2b.end()) {
throw std::runtime_error("'" + s + "' is not a valid boolean value.");
}
return value->second;
}
inline int INIReader::ValueHandler(void* user, const char* section,
const char* name, const char* value) {
INIReader* reader = (INIReader*)user;
if (reader->_values[section][name].size() > 0) {
throw std::runtime_error("duplicate key '" + std::string(name) +
"' in section '" + section + "'.");
}
reader->_values[section][name] = value;
return 1;
}
#endif // __INIREADER__
#ifndef __INIWRITER_H__
#define __INIWRITER_H__
class INIWriter {
public:
INIWriter(){};
inline static void write(const std::string& filepath,
INIReader& reader) {
// if (struct stat buf; stat(filepath.c_str(), &buf) == 0) {
// throw std::runtime_error("file: " + filepath + " already exist.");
// }
std::ofstream out;
out.open(filepath);
if (!out.is_open()) {
throw std::runtime_error("cannot open output file: " + filepath);
}
for (auto& section : reader.Sections()) {
out << "[" << section << "]\n";
for (auto& key : reader.Keys(section)) {
out << key << " = " << reader.Get(section, key) << "\n";
}
out << "\n";
}
out.close();
}
};
}
#endif /* __INIWRITER_H__ */