I've written a modern C++17 INI file reader to get access parameters values from an INI configuration file anywhere in a source file by simply included #include "Ini.hpp"
in the headers.
The file parsing is done once at first parameter request with a default file path. The parsing is done with REGEX I've written on this purpose.
I may went too far when checking the different number ranges depending on the C++ number type (short, int, long, long long, ...
).
Let me know what you think.
Ini.hpp
#include <fstream>
#include <unordered_map>
#include <string>
#include <regex>
#include <cfloat>
#include <climits>
// PARAMETERS_INI_FILE_PATH is a constant defined in the CMake configuration project file
struct Ini
{
public:
typedef std::unordered_map<std::string, std::unordered_map<std::string, std::string>> IniStructure;
Ini() = delete;
static void parse(const std::string &filePath = PARAMETERS_INI_FILE_PATH);
template<typename T> static T get(const std::string §ion, const std::string &key);
private:
static bool keyExists(const std::string §ion, const std::string &key);
static long long extractIntegerNumber(const std::string §ion, const std::string &key);
static long double extractFloatNumber(const std::string §ion, const std::string &key);
// [section][parameter name][parameter value]
static inline IniStructure values;
static inline std::string iniPath;
};
Ini.cpp
#include "Ini.hpp"
// ------------------------------------------------- Static functions ------------------------------------------------ //
/**
* Parse the INI file located to the given file path and store the values in Ini::values
*
* @param filePath - The path of the INI file to parse
*/
void Ini::parse(const std::string &filePath)
{
std::ifstream fileReader(filePath, std::ifstream::in);
std::string fileContent((std::istreambuf_iterator<char>(fileReader)), std::istreambuf_iterator<char>());
std::regex sections(R"(\[([^\]\r\n]+)]((?:\r?\n(?:[^[\r\n].*)?)*))");
std::regex parameters(R"((\w+) ?= ?(\"([^\"]+)\"|([^\r\n\t\f\v;#]+)))");
std::smatch sectionMatch;
iniPath = filePath;
if (fileReader.fail()) {
throw std::runtime_error("The file " + Ini::iniPath + " could not be opened");
}
while (regex_search(fileContent, sectionMatch, sections)) {
std::unordered_map<std::string, std::string> sectionParameters;
std::string sectionString = sectionMatch[2].str();
for (std::sregex_iterator parameter(sectionString.begin(), sectionString.end(), parameters); parameter != std::sregex_iterator(); ++parameter) {
std::smatch parameterMatch = *parameter;
sectionParameters[parameterMatch[1].str()] = parameterMatch[3].matched ? parameterMatch[3].str() : parameterMatch[4].str();
// parameterMatch[1] is the key, parameterMatch[3] is a trim quoted string (string without double quotes)
// and parameterMatch[4] is a number
}
values[sectionMatch[1].str()] = sectionParameters;
fileContent = sectionMatch.suffix();
}
}
/**
* Tells if the parameter exists in the given section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return True if the parameter exists in the given section at the given key, false otherwise
*/
bool Ini::keyExists(const std::string §ion, const std::string &key)
{
return values.find(section) != values.end() && values[section].find(key) != values[section].end();
}
/**
* Extract the integer number of the parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested integer number value
*/
long long Ini::extractIntegerNumber(const std::string §ion, const std::string &key)
{
std::smatch intNumberMatch;
std::regex intNumber(R"(^\s*(\-?\d+)\s*$)");
if (!keyExists(section, key)) {
throw std::runtime_error("The key " + key + " does not exist in section " + section + " in " + Ini::iniPath);
}
if (std::regex_match(values[section][key], intNumberMatch, intNumber)) {
return std::strtoll(intNumberMatch[1].str().c_str(), nullptr, 10);
} else {
throw std::runtime_error("The given parameter is not an integer number");
}
}
/**
* Extract the floating point number of the parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested floating point number value
*/
long double Ini::extractFloatNumber(const std::string §ion, const std::string &key)
{
std::smatch floatNumberMatch;
std::regex floatNumber(R"(^\s*(\-?(?:(?:\d+(?:\.\d*)?)|(?:\d+(?:\.\d+)?e(?:\+|\-)\d+))f?)\s*$)");
if (!keyExists(section, key)) {
throw std::runtime_error("The key " + key + " does not exist in section " + section + " in " + Ini::iniPath);
}
if (std::regex_match(values[section][key], floatNumberMatch, floatNumber)) {
return std::strtold(floatNumberMatch[1].str().c_str(), nullptr);
} else {
throw std::runtime_error("The given parameter is not a floating point number");
}
}
// ---------------------------------------------- Template specialisation -------------------------------------------- //
/**
* Throw an exception if the requested type is not defined
*/
template<typename T>
T Ini::get(const std::string §ion, const std::string &key)
{
throw std::runtime_error("The type of the given parameter is not defined");
}
/**
* Get the boolean parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested bool value
*/
template<>
bool Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
return values[section][key] == "true" || values[section][key] == "1";
}
/**
* Get the string parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested string value
*/
template<>
std::string Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
return values[section][key];
}
/**
* Get the short number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested short number value
*/
template<>
short Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
auto number = extractIntegerNumber(section, key);
if (number >= -SHRT_MAX && number <= SHRT_MAX) {
return static_cast<short>(number);
} else {
throw std::runtime_error("The number is out of range of a short integer");
}
}
/**
* Get the int number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested int number value
*/
template<>
int Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
auto number = extractIntegerNumber(section, key);
if (number >= -INT_MAX && number <= INT_MAX) {
return static_cast<int>(number);
} else {
throw std::runtime_error("The number is out of range of a an integer");
}
}
/**
* Get the unsigned int number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested unsigned int number value
*/
template<>
unsigned int Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
auto number = extractIntegerNumber(section, key);
if (number < 0) {
throw std::runtime_error("The number is negative so it cannot be unsigned");
} else if (number <= UINT_MAX) {
return static_cast<unsigned int>(number);
} else {
throw std::runtime_error("The number is out of range of a an unsigned integer");
}
}
/**
* Get the long int number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested long int number value
*/
template<>
long Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
auto number = extractIntegerNumber(section, key);
if (number >= -LONG_MAX && number <= LONG_MAX) {
return number;
} else {
throw std::runtime_error("The number is out of range of a a long integer");
}
}
/**
* Get the long long int number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested long long int number value
*/
template<>
long long Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
return extractIntegerNumber(section, key);
}
/**
* Get the float number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested float number value
*/
template<>
float Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
auto number = extractFloatNumber(section, key);
if (number >= -FLT_MAX && number <= FLT_MAX) {
return static_cast<float>(number);
} else {
throw std::runtime_error("The number is out of range of a float");
}
}
/**
* Get the double number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested double number value
*/
template<>
double Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
auto number = extractFloatNumber(section, key);
if (number >= -DBL_MAX && number <= DBL_MAX) {
return static_cast<double>(number);
} else {
throw std::runtime_error("The number is out of range of a double");
}
}
/**
* Get the long double number parameter in the given INI section at the given key
*
* @param section - The INI section to get the parameter from
* @param key - The INI parameter key
*
* @return The requested long double number value
*/
template<>
long double Ini::get(const std::string §ion, const std::string &key)
{
if (iniPath.empty()) { parse(); }
return extractFloatNumber(section, key);
}
main.cpp
#include <iostream>
#include "Ini.hpp"
int main(int argc, char **argv)
{
try {
auto myNumber = Ini::get<int>("Section", "parameter1");
auto myString = Ini::get<std::string>("Section", "parameter2");
std::cout << "Get the string " << myString << " and the number " << myNumber << " from the INI config file." << std::endl;
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}
example.ini
[Section]
parameter1 = 10
; comment
parameter2 = My string parameter ; comment