summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwithmorten <morten.with@gmail.com>2021-07-10 19:41:12 +0200
committerwithmorten <morten.with@gmail.com>2021-07-12 01:19:22 +0200
commitc0cbf8045f14d95a02c065910ede0d4a38ab99c7 (patch)
treed51e93cdb0c170bf8131ff6ccd5d03b48020349e
parentMerge pull request #1149 from withmorten/saves (diff)
downloadre3-c0cbf8045f14d95a02c065910ede0d4a38ab99c7.tar
re3-c0cbf8045f14d95a02c065910ede0d4a38ab99c7.tar.gz
re3-c0cbf8045f14d95a02c065910ede0d4a38ab99c7.tar.bz2
re3-c0cbf8045f14d95a02c065910ede0d4a38ab99c7.tar.lz
re3-c0cbf8045f14d95a02c065910ede0d4a38ab99c7.tar.xz
re3-c0cbf8045f14d95a02c065910ede0d4a38ab99c7.tar.zst
re3-c0cbf8045f14d95a02c065910ede0d4a38ab99c7.zip
-rw-r--r--src/core/re3.cpp109
-rw-r--r--src/extras/ini.h761
-rw-r--r--src/extras/ini_parser.hpp333
3 files changed, 821 insertions, 382 deletions
diff --git a/src/core/re3.cpp b/src/core/re3.cpp
index ef045eed..b7d89363 100644
--- a/src/core/re3.cpp
+++ b/src/core/re3.cpp
@@ -182,16 +182,29 @@ CustomFrontendOptionsPopulate(void)
#endif
#ifdef LOAD_INI_SETTINGS
-#include "ini_parser.hpp"
+#define MINI_CASE_SENSITIVE
+#include "ini.h"
+
+mINI::INIFile ini("re3.ini");
+mINI::INIStructure cfg;
-linb::ini cfg;
bool ReadIniIfExists(const char *cat, const char *key, uint32 *out)
{
- std::string strval = cfg.get(cat, key, "\xBA");
- const char *value = strval.c_str();
- char *endPtr;
- if (value && value[0] != '\xBA') {
- *out = strtoul(value, &endPtr, 0);
+ mINI::INIMap<std::string> section = cfg.get(cat);
+ if (section.has(key)) {
+ char *endPtr;
+ *out = strtoul(section.get(key).c_str(), &endPtr, 0);
+ return true;
+ }
+ return false;
+}
+
+bool ReadIniIfExists(const char *cat, const char *key, uint8 *out)
+{
+ mINI::INIMap<std::string> section = cfg.get(cat);
+ if (section.has(key)) {
+ char *endPtr;
+ *out = strtoul(section.get(key).c_str(), &endPtr, 0);
return true;
}
return false;
@@ -199,11 +212,10 @@ bool ReadIniIfExists(const char *cat, const char *key, uint32 *out)
bool ReadIniIfExists(const char *cat, const char *key, bool *out)
{
- std::string strval = cfg.get(cat, key, "\xBA");
- const char *value = strval.c_str();
- char *endPtr;
- if (value && value[0] != '\xBA') {
- *out = strtoul(value, &endPtr, 0);
+ mINI::INIMap<std::string> section = cfg.get(cat);
+ if (section.has(key)) {
+ char *endPtr;
+ *out = strtoul(section.get(key).c_str(), &endPtr, 0);
return true;
}
return false;
@@ -211,11 +223,10 @@ bool ReadIniIfExists(const char *cat, const char *key, bool *out)
bool ReadIniIfExists(const char *cat, const char *key, int32 *out)
{
- std::string strval = cfg.get(cat, key, "\xBA");
- const char *value = strval.c_str();
- char *endPtr;
- if (value && value[0] != '\xBA') {
- *out = strtol(value, &endPtr, 0);
+ mINI::INIMap<std::string> section = cfg.get(cat);
+ if (section.has(key)) {
+ char *endPtr;
+ *out = strtol(section.get(key).c_str(), &endPtr, 0);
return true;
}
return false;
@@ -223,11 +234,10 @@ bool ReadIniIfExists(const char *cat, const char *key, int32 *out)
bool ReadIniIfExists(const char *cat, const char *key, int8 *out)
{
- std::string strval = cfg.get(cat, key, "\xBA");
- const char *value = strval.c_str();
- char *endPtr;
- if (value && value[0] != '\xBA') {
- *out = strtol(value, &endPtr, 0);
+ mINI::INIMap<std::string> section = cfg.get(cat);
+ if (section.has(key)) {
+ char *endPtr;
+ *out = strtol(section.get(key).c_str(), &endPtr, 0);
return true;
}
return false;
@@ -235,10 +245,10 @@ bool ReadIniIfExists(const char *cat, const char *key, int8 *out)
bool ReadIniIfExists(const char *cat, const char *key, float *out)
{
- std::string strval = cfg.get(cat, key, "\xBA");
- const char *value = strval.c_str();
- if (value && value[0] != '\xBA') {
- *out = atof(value);
+ mINI::INIMap<std::string> section = cfg.get(cat);
+ if (section.has(key)) {
+ char *endPtr;
+ *out = strtof(section.get(key).c_str(), &endPtr);
return true;
}
return false;
@@ -246,10 +256,10 @@ bool ReadIniIfExists(const char *cat, const char *key, float *out)
bool ReadIniIfExists(const char *cat, const char *key, char *out, int size)
{
- std::string strval = cfg.get(cat, key, "\xBA");
- const char *value = strval.c_str();
- if (value && value[0] != '\xBA') {
- strncpy(out, value, size);
+ mINI::INIMap<std::string> section = cfg.get(cat);
+ if (section.has(key)) {
+ strncpy(out, section.get(key).c_str(), size - 1);
+ out[size - 1] = '\0';
return true;
}
return false;
@@ -257,42 +267,42 @@ bool ReadIniIfExists(const char *cat, const char *key, char *out, int size)
void StoreIni(const char *cat, const char *key, uint32 val)
{
- char temp[10];
+ char temp[11];
sprintf(temp, "%u", val);
- cfg.set(cat, key, temp);
+ cfg[cat][key] = temp;
}
void StoreIni(const char *cat, const char *key, uint8 val)
{
- char temp[10];
- sprintf(temp, "%u", (uint32)val);
- cfg.set(cat, key, temp);
+ char temp[11];
+ sprintf(temp, "%u", val);
+ cfg[cat][key] = temp;
}
void StoreIni(const char *cat, const char *key, int32 val)
{
- char temp[10];
+ char temp[11];
sprintf(temp, "%d", val);
- cfg.set(cat, key, temp);
+ cfg[cat][key] = temp;
}
void StoreIni(const char *cat, const char *key, int8 val)
{
- char temp[10];
- sprintf(temp, "%d", (int32)val);
- cfg.set(cat, key, temp);
+ char temp[11];
+ sprintf(temp, "%d", val);
+ cfg[cat][key] = temp;
}
void StoreIni(const char *cat, const char *key, float val)
{
- char temp[10];
+ char temp[50];
sprintf(temp, "%f", val);
- cfg.set(cat, key, temp);
+ cfg[cat][key] = temp;
}
void StoreIni(const char *cat, const char *key, char *val, int size)
{
- cfg.set(cat, key, val);
+ cfg[cat][key] = val;
}
const char *iniControllerActions[] = { "PED_FIREWEAPON", "PED_CYCLE_WEAPON_RIGHT", "PED_CYCLE_WEAPON_LEFT", "GO_FORWARD", "GO_BACK", "GO_LEFT", "GO_RIGHT", "PED_SNIPER_ZOOM_IN",
@@ -354,7 +364,7 @@ void LoadINIControllerSettings()
#endif
// force to default GTA behaviour (never overwrite bindings on joy change/initialization) if user init'ed/set bindings before we introduced that
if (!ReadIniIfExists("Controller", "PadButtonsInited", &ControlsManager.ms_padButtonsInited)) {
- ControlsManager.ms_padButtonsInited = cfg.category_size("Bindings") != 0 ? 16 : 0;
+ ControlsManager.ms_padButtonsInited = cfg.get("Bindings").size() != 0 ? 16 : 0;
}
for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) {
@@ -456,12 +466,13 @@ void SaveINIControllerSettings()
#endif
#endif
StoreIni("Controller", "PadButtonsInited", ControlsManager.ms_padButtonsInited);
- cfg.write_file("re3.ini");
+
+ ini.write(cfg);
}
bool LoadINISettings()
{
- if (!cfg.load_file("re3.ini"))
+ if (!ini.read(cfg))
return false;
#ifdef IMPROVED_VIDEOMODE
@@ -527,7 +538,7 @@ bool LoadINISettings()
#endif
#ifdef CUSTOM_FRONTEND_OPTIONS
- bool migrate = cfg.category_size("FrontendOptions") != 0;
+ bool migrate = cfg.get("FrontendOptions").size() != 0;
for (int i = 0; i < MENUPAGES; i++) {
for (int j = 0; j < NUM_MENUROWS; j++) {
CMenuScreenCustom::CMenuEntry &option = aScreens[i].m_aEntries[j];
@@ -539,7 +550,7 @@ bool LoadINISettings()
// Migrate from old .ini to new .ini
// Old values can only be int8, new ones can contain float if it is slider
if (migrate && ReadIniIfExists("FrontendOptions", option.m_CFO->save, (int8*)option.m_CFO->value))
- cfg.remove("FrontendOptions", option.m_CFO->save);
+ cfg["FrontendOptions"].remove(option.m_CFO->save);
else if (option.m_Action == MENUACTION_CFO_SLIDER)
ReadIniIfExists(option.m_CFO->saveCat, option.m_CFO->save, (float*)option.m_CFO->value);
else
@@ -642,7 +653,7 @@ void SaveINISettings()
}
#endif
- cfg.write_file("re3.ini");
+ ini.write(cfg);
}
#endif
diff --git a/src/extras/ini.h b/src/extras/ini.h
new file mode 100644
index 00000000..44dd3d57
--- /dev/null
+++ b/src/extras/ini.h
@@ -0,0 +1,761 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2018 Danijel Durakovic
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// /mINI/ v0.9.10
+// An INI file reader and writer for the modern age.
+//
+///////////////////////////////////////////////////////////////////////////////
+//
+// A tiny utility library for manipulating INI files with a straightforward
+// API and a minimal footprint. It conforms to the (somewhat) standard INI
+// format - sections and keys are case insensitive and all leading and
+// trailing whitespace is ignored. Comments are lines that begin with a
+// semicolon. Trailing comments are allowed on section lines.
+//
+// Files are read on demand, upon which data is kept in memory and the file
+// is closed. This utility supports lazy writing, which only writes changes
+// and updates to a file and preserves custom formatting and comments. A lazy
+// write invoked by a write() call will read the output file, find what
+// changes have been made and update the file accordingly. If you only need to
+// generate files, use generate() instead. Section and key order is preserved
+// on read, write and insert.
+//
+///////////////////////////////////////////////////////////////////////////////
+//
+// /* BASIC USAGE EXAMPLE: */
+//
+// /* read from file */
+// mINI::INIFile file("myfile.ini");
+// mINI::INIStructure ini;
+// file.read(ini);
+//
+// /* read value; gets a reference to actual value in the structure.
+// if key or section don't exist, a new empty value will be created */
+// std::string& value = ini["section"]["key"];
+//
+// /* read value safely; gets a copy of value in the structure.
+// does not alter the structure */
+// std::string value = ini.get("section").get("key");
+//
+// /* set or update values */
+// ini["section"]["key"] = "value";
+//
+// /* set multiple values */
+// ini["section2"].set({
+// {"key1", "value1"},
+// {"key2", "value2"}
+// });
+//
+// /* write updates back to file, preserving comments and formatting */
+// file.write(ini);
+//
+// /* or generate a file (overwrites the original) */
+// file.generate(ini);
+//
+///////////////////////////////////////////////////////////////////////////////
+//
+// Long live the INI file!!!
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef MINI_INI_H_
+#define MINI_INI_H_
+
+#include <string>
+#include <sstream>
+#include <algorithm>
+#include <utility>
+#include <unordered_map>
+#include <vector>
+#include <memory>
+#include <fstream>
+#include <sys/stat.h>
+#include <cctype>
+
+namespace mINI
+{
+ namespace INIStringUtil
+ {
+ const char* const whitespaceDelimiters = " \t\n\r\f\v";
+ inline void trim(std::string& str)
+ {
+ str.erase(str.find_last_not_of(whitespaceDelimiters) + 1);
+ str.erase(0, str.find_first_not_of(whitespaceDelimiters));
+ }
+#ifndef MINI_CASE_SENSITIVE
+ inline void toLower(std::string& str)
+ {
+ std::transform(str.begin(), str.end(), str.begin(), [](const char c) {
+ return static_cast<const char>(std::tolower(c));
+ });
+ }
+#endif
+ inline void replace(std::string& str, std::string const& a, std::string const& b)
+ {
+ if (!a.empty())
+ {
+ std::size_t pos = 0;
+ while ((pos = str.find(a, pos)) != std::string::npos)
+ {
+ str.replace(pos, a.size(), b);
+ pos += b.size();
+ }
+ }
+ }
+#ifdef _WIN32
+ const char* const endl = "\r\n";
+#else
+ const char* const endl = "\n";
+#endif
+ };
+
+ template<typename T>
+ class INIMap
+ {
+ private:
+ using T_DataIndexMap = std::unordered_map<std::string, std::size_t>;
+ using T_DataItem = std::pair<std::string, T>;
+ using T_DataContainer = std::vector<T_DataItem>;
+ using T_MultiArgs = typename std::vector<std::pair<std::string, T>>;
+
+ T_DataIndexMap dataIndexMap;
+ T_DataContainer data;
+
+ inline std::size_t setEmpty(std::string& key)
+ {
+ std::size_t index = data.size();
+ dataIndexMap[key] = index;
+ data.emplace_back(key, T());
+ return index;
+ }
+
+ public:
+ using const_iterator = typename T_DataContainer::const_iterator;
+
+ INIMap() { }
+
+ INIMap(INIMap const& other)
+ {
+ std::size_t data_size = other.data.size();
+ for (std::size_t i = 0; i < data_size; ++i)
+ {
+ auto const& key = other.data[i].first;
+ auto const& obj = other.data[i].second;
+ data.emplace_back(key, obj);
+ }
+ dataIndexMap = T_DataIndexMap(other.dataIndexMap);
+ }
+
+ T& operator[](std::string key)
+ {
+ INIStringUtil::trim(key);
+#ifndef MINI_CASE_SENSITIVE
+ INIStringUtil::toLower(key);
+#endif
+ auto it = dataIndexMap.find(key);
+ bool hasIt = (it != dataIndexMap.end());
+ std::size_t index = (hasIt) ? it->second : setEmpty(key);
+ return data[index].second;
+ }
+ T get(std::string key) const
+ {
+ INIStringUtil::trim(key);
+#ifndef MINI_CASE_SENSITIVE
+ INIStringUtil::toLower(key);
+#endif
+ auto it = dataIndexMap.find(key);
+ if (it == dataIndexMap.end())
+ {
+ return T();
+ }
+ return T(data[it->second].second);
+ }
+ bool has(std::string key) const
+ {
+ INIStringUtil::trim(key);
+#ifndef MINI_CASE_SENSITIVE
+ INIStringUtil::toLower(key);
+#endif
+ return (dataIndexMap.count(key) == 1);
+ }
+ void set(std::string key, T obj)
+ {
+ INIStringUtil::trim(key);
+#ifndef MINI_CASE_SENSITIVE
+ INIStringUtil::toLower(key);
+#endif
+ auto it = dataIndexMap.find(key);
+ if (it != dataIndexMap.end())
+ {
+ data[it->second].second = obj;
+ }
+ else
+ {
+ dataIndexMap[key] = data.size();
+ data.emplace_back(key, obj);
+ }
+ }
+ void set(T_MultiArgs const& multiArgs)
+ {
+ for (auto const& it : multiArgs)
+ {
+ auto const& key = it.first;
+ auto const& obj = it.second;
+ set(key, obj);
+ }
+ }
+ bool remove(std::string key)
+ {
+ INIStringUtil::trim(key);
+#ifndef MINI_CASE_SENSITIVE
+ INIStringUtil::toLower(key);
+#endif
+ auto it = dataIndexMap.find(key);
+ if (it != dataIndexMap.end())
+ {
+ std::size_t index = it->second;
+ data.erase(data.begin() + index);
+ dataIndexMap.erase(it);
+ for (auto& it2 : dataIndexMap)
+ {
+ auto& vi = it2.second;
+ if (vi > index)
+ {
+ vi--;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ void clear()
+ {
+ data.clear();
+ dataIndexMap.clear();
+ }
+ std::size_t size() const
+ {
+ return data.size();
+ }
+ const_iterator begin() const { return data.begin(); }
+ const_iterator end() const { return data.end(); }
+ };
+
+ using INIStructure = INIMap<INIMap<std::string>>;
+
+ namespace INIParser
+ {
+ using T_ParseValues = std::pair<std::string, std::string>;
+
+ enum class PDataType : char
+ {
+ PDATA_NONE,
+ PDATA_COMMENT,
+ PDATA_SECTION,
+ PDATA_KEYVALUE,
+ PDATA_UNKNOWN
+ };
+
+ inline PDataType parseLine(std::string line, T_ParseValues& parseData)
+ {
+ parseData.first.clear();
+ parseData.second.clear();
+ INIStringUtil::trim(line);
+ if (line.empty())
+ {
+ return PDataType::PDATA_NONE;
+ }
+ char firstCharacter = line[0];
+ if (firstCharacter == ';')
+ {
+ return PDataType::PDATA_COMMENT;
+ }
+ if (firstCharacter == '[')
+ {
+ auto commentAt = line.find_first_of(';');
+ if (commentAt != std::string::npos)
+ {
+ line = line.substr(0, commentAt);
+ }
+ auto closingBracketAt = line.find_last_of(']');
+ if (closingBracketAt != std::string::npos)
+ {
+ auto section = line.substr(1, closingBracketAt - 1);
+ INIStringUtil::trim(section);
+ parseData.first = section;
+ return PDataType::PDATA_SECTION;
+ }
+ }
+ auto lineNorm = line;
+ INIStringUtil::replace(lineNorm, "\\=", " ");
+ auto equalsAt = lineNorm.find_first_of('=');
+ if (equalsAt != std::string::npos)
+ {
+ auto key = line.substr(0, equalsAt);
+ INIStringUtil::trim(key);
+ INIStringUtil::replace(key, "\\=", "=");
+ auto value = line.substr(equalsAt + 1);
+ INIStringUtil::trim(value);
+ parseData.first = key;
+ parseData.second = value;
+ return PDataType::PDATA_KEYVALUE;
+ }
+ return PDataType::PDATA_UNKNOWN;
+ }
+ };
+
+ class INIReader
+ {
+ public:
+ using T_LineData = std::vector<std::string>;
+ using T_LineDataPtr = std::shared_ptr<T_LineData>;
+
+ private:
+ std::ifstream fileReadStream;
+ T_LineDataPtr lineData;
+
+ T_LineData readFile()
+ {
+ std::string fileContents;
+ fileReadStream.seekg(0, std::ios::end);
+ fileContents.resize(fileReadStream.tellg());
+ fileReadStream.seekg(0, std::ios::beg);
+ std::size_t fileSize = fileContents.size();
+ fileReadStream.read(&fileContents[0], fileSize);
+ fileReadStream.close();
+ T_LineData output;
+ if (fileSize == 0)
+ {
+ return output;
+ }
+ std::string buffer;
+ buffer.reserve(50);
+ for (std::size_t i = 0; i < fileSize; ++i)
+ {
+ char& c = fileContents[i];
+ if (c == '\n')
+ {
+ output.emplace_back(buffer);
+ buffer.clear();
+ continue;
+ }
+ if (c != '\0' && c != '\r')
+ {
+ buffer += c;
+ }
+ }
+ output.emplace_back(buffer);
+ return output;
+ }
+
+ public:
+ INIReader(std::string const& filename, bool keepLineData = false)
+ {
+ fileReadStream.open(filename, std::ios::in | std::ios::binary);
+ if (keepLineData)
+ {
+ lineData = std::make_shared<T_LineData>();
+ }
+ }
+ ~INIReader() { }
+
+ bool operator>>(INIStructure& data)
+ {
+ if (!fileReadStream.is_open())
+ {
+ return false;
+ }
+ T_LineData fileLines = readFile();
+ std::string section;
+ bool inSection = false;
+ INIParser::T_ParseValues parseData;
+ for (auto const& line : fileLines)
+ {
+ auto parseResult = INIParser::parseLine(line, parseData);
+ if (parseResult == INIParser::PDataType::PDATA_SECTION)
+ {
+ inSection = true;
+ data[section = parseData.first];
+ }
+ else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE)
+ {
+ auto const& key = parseData.first;
+ auto const& value = parseData.second;
+ data[section][key] = value;
+ }
+ if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN)
+ {
+ if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection)
+ {
+ continue;
+ }
+ lineData->emplace_back(line);
+ }
+ }
+ return true;
+ }
+ T_LineDataPtr getLines()
+ {
+ return lineData;
+ }
+ };
+
+ class INIGenerator
+ {
+ private:
+ std::ofstream fileWriteStream;
+
+ public:
+ bool prettyPrint = false;
+
+ INIGenerator(std::string const& filename)
+ {
+ fileWriteStream.open(filename, std::ios::out | std::ios::binary);
+ }
+ ~INIGenerator() { }
+
+ bool operator<<(INIStructure const& data)
+ {
+ if (!fileWriteStream.is_open())
+ {
+ return false;
+ }
+ if (!data.size())
+ {
+ return true;
+ }
+ auto it = data.begin();
+ for (;;)
+ {
+ auto const& section = it->first;
+ auto const& collection = it->second;
+ fileWriteStream
+ << "["
+ << section
+ << "]";
+ if (collection.size())
+ {
+ fileWriteStream << INIStringUtil::endl;
+ auto it2 = collection.begin();
+ for (;;)
+ {
+ auto key = it2->first;
+ INIStringUtil::replace(key, "=", "\\=");
+ auto value = it2->second;
+ INIStringUtil::trim(value);
+ fileWriteStream
+ << key
+ << ((prettyPrint) ? " = " : "=")
+ << value;
+ if (++it2 == collection.end())
+ {
+ break;
+ }
+ fileWriteStream << INIStringUtil::endl;
+ }
+ }
+ if (++it == data.end())
+ {
+ break;
+ }
+ fileWriteStream << INIStringUtil::endl;
+ if (prettyPrint)
+ {
+ fileWriteStream << INIStringUtil::endl;
+ }
+ }
+ return true;
+ }
+ };
+
+ class INIWriter
+ {
+ private:
+ using T_LineData = std::vector<std::string>;
+ using T_LineDataPtr = std::shared_ptr<T_LineData>;
+
+ std::string filename;
+
+ T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original)
+ {
+ T_LineData output;
+ INIParser::T_ParseValues parseData;
+ std::string sectionCurrent;
+ bool parsingSection = false;
+ bool continueToNextSection = false;
+ bool discardNextEmpty = false;
+ bool writeNewKeys = false;
+ std::size_t lastKeyLine = 0;
+ for (auto line = lineData->begin(); line != lineData->end(); ++line)
+ {
+ if (!writeNewKeys)
+ {
+ auto parseResult = INIParser::parseLine(*line, parseData);
+ if (parseResult == INIParser::PDataType::PDATA_SECTION)
+ {
+ if (parsingSection)
+ {
+ writeNewKeys = true;
+ parsingSection = false;
+ --line;
+ continue;
+ }
+ sectionCurrent = parseData.first;
+ if (data.has(sectionCurrent))
+ {
+ parsingSection = true;
+ continueToNextSection = false;
+ discardNextEmpty = false;
+ output.emplace_back(*line);
+ lastKeyLine = output.size();
+ }
+ else
+ {
+ continueToNextSection = true;
+ discardNextEmpty = true;
+ continue;
+ }
+ }
+ else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE)
+ {
+ if (continueToNextSection)
+ {
+ continue;
+ }
+ if (data.has(sectionCurrent))
+ {
+ auto& collection = data[sectionCurrent];
+ auto const& key = parseData.first;
+ auto const& value = parseData.second;
+ if (collection.has(key))
+ {
+ auto outputValue = collection[key];
+ if (value == outputValue)
+ {
+ output.emplace_back(*line);
+ }
+ else
+ {
+ INIStringUtil::trim(outputValue);
+ auto lineNorm = *line;
+ INIStringUtil::replace(lineNorm, "\\=", " ");
+ auto equalsAt = lineNorm.find_first_of('=');
+ auto valueAt = lineNorm.find_first_not_of(
+ INIStringUtil::whitespaceDelimiters,
+ equalsAt + 1
+ );
+ std::string outputLine = line->substr(0, valueAt);
+ if (prettyPrint && equalsAt + 1 == valueAt)
+ {
+ outputLine += " ";
+ }
+ outputLine += outputValue;
+ output.emplace_back(outputLine);
+ }
+ lastKeyLine = output.size();
+ }
+ }
+ }
+ else
+ {
+ if (discardNextEmpty && line->empty())
+ {
+ discardNextEmpty = false;
+ }
+ else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN)
+ {
+ output.emplace_back(*line);
+ }
+ }
+ }
+ if (writeNewKeys || std::next(line) == lineData->end())
+ {
+ T_LineData linesToAdd;
+ if (data.has(sectionCurrent) && original.has(sectionCurrent))
+ {
+ auto const& collection = data[sectionCurrent];
+ auto const& collectionOriginal = original[sectionCurrent];
+ for (auto const& it : collection)
+ {
+ auto key = it.first;
+ if (collectionOriginal.has(key))
+ {
+ continue;
+ }
+ auto value = it.second;
+ INIStringUtil::replace(key, "=", "\\=");
+ INIStringUtil::trim(value);
+ linesToAdd.emplace_back(
+ key + ((prettyPrint) ? " = " : "=") + value
+ );
+ }
+ }
+ if (!linesToAdd.empty())
+ {
+ output.insert(
+ output.begin() + lastKeyLine,
+ linesToAdd.begin(),
+ linesToAdd.end()
+ );
+ }
+ if (writeNewKeys)
+ {
+ writeNewKeys = false;
+ --line;
+ }
+ }
+ }
+ for (auto const& it : data)
+ {
+ auto const& section = it.first;
+ if (original.has(section))
+ {
+ continue;
+ }
+ if (prettyPrint && output.size() > 0 && !output.back().empty())
+ {
+ output.emplace_back();
+ }
+ output.emplace_back("[" + section + "]");
+ auto const& collection = it.second;
+ for (auto const& it2 : collection)
+ {
+ auto key = it2.first;
+ auto value = it2.second;
+ INIStringUtil::replace(key, "=", "\\=");
+ INIStringUtil::trim(value);
+ output.emplace_back(
+ key + ((prettyPrint) ? " = " : "=") + value
+ );
+ }
+ }
+ return output;
+ }
+
+ public:
+ bool prettyPrint = false;
+
+ INIWriter(std::string const& filename)
+ : filename(filename)
+ {
+ }
+ ~INIWriter() { }
+
+ bool operator<<(INIStructure& data)
+ {
+ struct stat buf;
+ bool fileExists = (stat(filename.c_str(), &buf) == 0);
+ if (!fileExists)
+ {
+ INIGenerator generator(filename);
+ generator.prettyPrint = prettyPrint;
+ return generator << data;
+ }
+ INIStructure originalData;
+ T_LineDataPtr lineData;
+ bool readSuccess = false;
+ {
+ INIReader reader(filename, true);
+ if ((readSuccess = reader >> originalData))
+ {
+ lineData = reader.getLines();
+ }
+ }
+ if (!readSuccess)
+ {
+ return false;
+ }
+ T_LineData output = getLazyOutput(lineData, data, originalData);
+ std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary);
+ if (fileWriteStream.is_open())
+ {
+ if (output.size())
+ {
+ auto line = output.begin();
+ for (;;)
+ {
+ fileWriteStream << *line;
+ if (++line == output.end())
+ {
+ break;
+ }
+ fileWriteStream << INIStringUtil::endl;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ class INIFile
+ {
+ private:
+ std::string filename;
+
+ public:
+ INIFile(std::string const& filename)
+ : filename(filename)
+ { }
+
+ ~INIFile() { }
+
+ bool read(INIStructure& data) const
+ {
+ if (data.size())
+ {
+ data.clear();
+ }
+ if (filename.empty())
+ {
+ return false;
+ }
+ INIReader reader(filename);
+ return reader >> data;
+ }
+ bool generate(INIStructure const& data, bool pretty = false) const
+ {
+ if (filename.empty())
+ {
+ return false;
+ }
+ INIGenerator generator(filename);
+ generator.prettyPrint = pretty;
+ return generator << data;
+ }
+ bool write(INIStructure& data, bool pretty = false) const
+ {
+ if (filename.empty())
+ {
+ return false;
+ }
+ INIWriter writer(filename);
+ writer.prettyPrint = pretty;
+ return writer << data;
+ }
+ };
+}
+
+#endif // MINI_INI_H_
diff --git a/src/extras/ini_parser.hpp b/src/extras/ini_parser.hpp
deleted file mode 100644
index 7bea024c..00000000
--- a/src/extras/ini_parser.hpp
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (c) 2013-2015 Denilson das Mercês Amorim <dma_2012@hotmail.com>
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- *
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- *
- * 3. This notice may not be removed or altered from any source
- * distribution.
- *
- */
-#ifndef LINB_INI_PARSER_HPP
-#define LINB_INI_PARSER_HPP
-
-/*
- * STL-like INI Container
- */
-
-#include <string> // for std::string
-#include <map> // for std::map
-#include <cstdio> // for std::FILE
-#include <algorithm> // for std::find_if
-#include <functional> // for std::function
-
-namespace linb
-{
- template<
- class CharT = char, /* Not compatible with other type here, since we're using C streams */
- class StringType = std::basic_string<CharT>,
- class KeyContainer = std::map<StringType, StringType>,
- class SectionContainer = std::map<StringType, KeyContainer>
- > class basic_ini
- {
- public:
- typedef CharT char_type;
- typedef StringType string_type;
- typedef KeyContainer key_container;
- typedef SectionContainer section_container;
-
- // Typedef container values types
- typedef typename section_container::value_type value_type;
- typedef typename section_container::key_type key_type;
- typedef typename section_container::mapped_type mapped_type;
-
- // Typedef common types
- typedef typename section_container::size_type size_type;
- typedef typename section_container::difference_type difference_type;
-
- // Typedef iterators
- typedef typename section_container::iterator iterator;
- typedef typename section_container::const_iterator const_iterator;
- typedef typename section_container::reverse_iterator reverse_iterator;
- typedef typename section_container::const_reverse_iterator const_reverse_iterator;
-
- // typedef References and pointers
- typedef typename section_container::reference reference;
- typedef typename section_container::const_reference const_reference;
- typedef typename section_container::pointer pointer;
- typedef typename section_container::const_pointer const_pointer;
-
- private:
- section_container data;
-
- public:
-
- basic_ini()
- { }
-
- basic_ini(const char_type* filename)
- { this->read_file(filename); }
-
- /* Iterator methods */
- iterator begin()
- { return data.begin(); }
- const_iterator begin() const
- { return data.begin(); }
- iterator end()
- { return data.end(); }
- const_iterator end() const
- { return data.end(); }
- const_iterator cbegin() const
- { return data.cbegin(); }
- const_iterator cend() const
- { return data.cend(); }
-
- /* Reverse iterator methods */
- reverse_iterator rbegin()
- { return data.rbegin(); }
- const_reverse_iterator rbegin() const
- { return data.rbegin(); }
- reverse_iterator rend()
- { return data.rend(); }
- const_reverse_iterator rend() const
- { return data.rend(); }
- const_reverse_iterator crbegin() const
- { return data.crbegin(); }
- const_reverse_iterator crend() const
- { return data.crend(); }
-
- /* Acessing index methods */
- mapped_type& operator[](const string_type& sect)
- { return data[sect]; }
- mapped_type& operator[](string_type&& sect)
- { return data[std::forward<string_type>(sect)]; }
- mapped_type& at( const string_type& sect)
- { return data.at(sect); }
- const mapped_type& at(const string_type& sect) const
- { return data.at(sect); }
-
- /* Capacity information */
- bool empty() const
- { return data.empty(); }
- size_type size() const
- { return data.size(); }
- size_type max_size() const
- { return data.max_size(); }
-
- /* Modifiers */
- void clear()
- { return data.clear(); }
-
- /* Lookup */
- size_type count(const string_type& sect)
- { return data.count(sect); }
- iterator find(const string_type& sect)
- { return data.find(sect); }
-
- /* Gets a value from the specified section & key, default_value is returned if the sect & key doesn't exist */
- string_type get(const string_type& sect, const key_type& key, const string_type& default_value)
- {
- auto it = this->find(sect);
- if(it != this->end())
- {
- auto itv = it->second.find(key);
- if(itv != it->second.end())
- return itv->second;
- }
- return default_value;
- }
-
- /* Sets the value of a value in the ini */
- void set(const string_type& sect, const key_type& key, const string_type& value)
- {
- (*this)[sect][key] = value; // no emplace since overwrite!
- }
-
- /* Too lazy to continue this container... If you need more methods, just add it */
-
- // re3
- void remove(const string_type& sect, const key_type& key)
- {
- auto it = this->find(sect);
- if(it != this->end())
- {
- it->second.erase(key);
- }
- }
-
- int category_size(const string_type& sect)
- {
- auto it = this->find(sect);
- if(it != this->end())
- {
- return it->second.size();
- }
- return 0;
- }
-
-#if 1
- bool read_file(const char_type* filename)
- {
- /* Using C stream in a STL-like container, funny?
- */
- if(FILE* f = fopen(filename, "r"))
- {
- key_container* keys = nullptr;
- char_type buf[2048];
- string_type line;
- string_type key;
- string_type value;
- string_type null_string;
- size_type pos;
-
- // Trims an string
- auto trim = [](string_type& s, bool trimLeft, bool trimRight) -> string_type&
- {
- if(s.size())
- {
- // Ignore UTF-8 BOM
- while(s.size() >= 3 && s[0] == (char)(0xEF) && s[1] == (char)(0xBB) && s[2] == (char)(0xBF))
- s.erase(s.begin(), s.begin() + 3);
-
- if(trimLeft)
- s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::function<int(int)>(::isspace))));
- if(trimRight)
- s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::function<int(int)>(::isspace))).base(), s.end());
- }
- return s;
- };
-
- // Start parsing
- while(fgets(buf, sizeof(buf), f))
- {
- // What a thing, reading into a char buffer and then putting in the string...
- line = buf;
-
- // Find comment and remove anything after it from the line
- if((pos = line.find_first_of(';')) != line.npos)
- line.erase(pos);
-
- // Trim the string, and if it gets empty, skip this line
- if(trim(line, true, true).empty())
- continue;
-
- // Find section name
- if(line.front() == '[' && line.back() == ']')
- {
- pos = line.length() - 1; //line.find_first_of(']');
- if(pos != line.npos)
- {
- trim(key.assign(line, 1, pos-1), true, true);
- keys = &data[std::move(key)]; // Create section
- }
- else
- keys = nullptr;
- }
- else
- {
- // Find key and value positions
- pos = line.find_first_of('=');
- if(pos == line.npos)
- {
- // There's only the key
- key = line; // No need for trim, line is already trimmed
- value.clear();
- }
- else
- {
- // There's the key and the value
- trim(key.assign(line, 0, pos), false, true); // trim the right
- trim(value.assign(line, pos + 1, line.npos), true, false); // trim the left
- }
-
- // Put the key/value into the current keys object, or into the section "" if no section has been found
- #if __cplusplus >= 201103L || _MSC_VER >= 1800
- (keys ? *keys : data[null_string]).emplace(std::move(key), std::move(value));
- #else
- (keys ? *keys : data[null_string])[key] = value;
- key.clear(); value.clear();
- #endif
- }
- }
-
- fclose(f);
- return true;
- }
- return false;
- }
-
- /*
- * Dumps the content of this container into an ini file
- */
- bool write_file(const char_type* filename)
- {
- if(FILE* f = fopen(filename, "w"))
- {
- bool first = true;
- for(auto& sec : this->data)
- {
- fprintf(f, first? "[%s]\n" : "\n[%s]\n", sec.first.c_str());
- first = false;
- for(auto& kv : sec.second)
- {
- if(kv.second.empty())
- fprintf(f, "%s\n", kv.first.c_str());
- else
- fprintf(f, "%s = %s\n", kv.first.c_str(), kv.second.c_str());
- }
- }
- fclose(f);
- return true;
- }
- return false;
- }
-
-
- /*
- */
- bool load_file(const char_type* filename)
- {
- return read_file(filename);
- }
-
- bool load_file(const StringType& filename)
- {
- return load_file(filename.c_str());
- }
-
- bool write_file(const StringType& filename)
- {
- return write_file(filename.c_str());
- }
-#endif
-
-
-
- };
-
-
- /* Use default basic_ini
- *
- * Limitations:
- * * Not unicode aware
- * * Case sensitive
- * * Sections must have unique keys
- */
- typedef basic_ini<> ini;
-}
-
-#endif
-