cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

ini.h (18620B)


      1/*
      2 * The MIT License (MIT)
      3 * Copyright (c) 2018 Danijel Durakovic
      4 *
      5 * Permission is hereby granted, free of charge, to any person obtaining a copy of
      6 * this software and associated documentation files (the "Software"), to deal in
      7 * the Software without restriction, including without limitation the rights to
      8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
      9 * of the Software, and to permit persons to whom the Software is furnished to do
     10 * so, subject to the following conditions:
     11 *
     12 * The above copyright notice and this permission notice shall be included in all
     13 * copies or substantial portions of the Software.
     14 *
     15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
     17 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
     18 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21 *
     22 */
     23
     24///////////////////////////////////////////////////////////////////////////////
     25//
     26//  /mINI/ v0.9.10
     27//  An INI file reader and writer for the modern age.
     28//
     29///////////////////////////////////////////////////////////////////////////////
     30//
     31//  A tiny utility library for manipulating INI files with a straightforward
     32//  API and a minimal footprint. It conforms to the (somewhat) standard INI
     33//  format - sections and keys are case insensitive and all leading and
     34//  trailing whitespace is ignored. Comments are lines that begin with a
     35//  semicolon. Trailing comments are allowed on section lines.
     36//
     37//  Files are read on demand, upon which data is kept in memory and the file
     38//  is closed. This utility supports lazy writing, which only writes changes
     39//  and updates to a file and preserves custom formatting and comments. A lazy
     40//  write invoked by a write() call will read the output file, find what
     41//  changes have been made and update the file accordingly. If you only need to
     42//  generate files, use generate() instead. Section and key order is preserved
     43//  on read, write and insert.
     44//
     45///////////////////////////////////////////////////////////////////////////////
     46//
     47//  /* BASIC USAGE EXAMPLE: */
     48//
     49//  /* read from file */
     50//  mINI::INIFile file("myfile.ini");
     51//  mINI::INIStructure ini;
     52//  file.read(ini);
     53//
     54//  /* read value; gets a reference to actual value in the structure.
     55//     if key or section don't exist, a new empty value will be created */
     56//  std::string& value = ini["section"]["key"];
     57//
     58//  /* read value safely; gets a copy of value in the structure.
     59//     does not alter the structure */
     60//  std::string value = ini.get("section").get("key");
     61//
     62//  /* set or update values */
     63//  ini["section"]["key"] = "value";
     64//
     65//  /* set multiple values */
     66//  ini["section2"].set({
     67//      {"key1", "value1"},
     68//      {"key2", "value2"}
     69//  });
     70//
     71//  /* write updates back to file, preserving comments and formatting */
     72//  file.write(ini);
     73//
     74//  /* or generate a file (overwrites the original) */
     75//  file.generate(ini);
     76//
     77///////////////////////////////////////////////////////////////////////////////
     78//
     79//  Long live the INI file!!!
     80//
     81///////////////////////////////////////////////////////////////////////////////
     82
     83#ifndef MINI_INI_H_
     84#define MINI_INI_H_
     85
     86#include <string>
     87#include <sstream>
     88#include <algorithm>
     89#include <utility>
     90#include <unordered_map>
     91#include <vector>
     92#include <memory>
     93#include <fstream>
     94#include <sys/stat.h>
     95#include <cctype>
     96
     97namespace mINI
     98{
     99	namespace INIStringUtil
    100	{
    101		const char* const whitespaceDelimiters = " \t\n\r\f\v";
    102		inline void trim(std::string& str)
    103		{
    104			str.erase(str.find_last_not_of(whitespaceDelimiters) + 1);
    105			str.erase(0, str.find_first_not_of(whitespaceDelimiters));
    106		}
    107#ifndef MINI_CASE_SENSITIVE
    108		inline void toLower(std::string& str)
    109		{
    110			std::transform(str.begin(), str.end(), str.begin(), [](const char c) {
    111				return static_cast<const char>(std::tolower(c));
    112			});
    113		}
    114#endif
    115		inline void replace(std::string& str, std::string const& a, std::string const& b)
    116		{
    117			if (!a.empty())
    118			{
    119				std::size_t pos = 0;
    120				while ((pos = str.find(a, pos)) != std::string::npos)
    121				{
    122					str.replace(pos, a.size(), b);
    123					pos += b.size();
    124				}
    125			}
    126		}
    127#ifdef _WIN32
    128		const char* const endl = "\r\n";
    129#else
    130		const char* const endl = "\n";
    131#endif
    132	};
    133
    134	template<typename T>
    135	class INIMap
    136	{
    137	private:
    138		using T_DataIndexMap = std::unordered_map<std::string, std::size_t>;
    139		using T_DataItem = std::pair<std::string, T>;
    140		using T_DataContainer = std::vector<T_DataItem>;
    141		using T_MultiArgs = typename std::vector<std::pair<std::string, T>>;
    142
    143		T_DataIndexMap dataIndexMap;
    144		T_DataContainer data;
    145
    146		inline std::size_t setEmpty(std::string& key)
    147		{
    148			std::size_t index = data.size();
    149			dataIndexMap[key] = index;
    150			data.emplace_back(key, T());
    151			return index;
    152		}
    153
    154	public:
    155		using const_iterator = typename T_DataContainer::const_iterator;
    156
    157		INIMap() { }
    158
    159		INIMap(INIMap const& other)
    160		{
    161			std::size_t data_size = other.data.size();
    162			for (std::size_t i = 0; i < data_size; ++i)
    163			{
    164				auto const& key = other.data[i].first;
    165				auto const& obj = other.data[i].second;
    166				data.emplace_back(key, obj);
    167			}
    168			dataIndexMap = T_DataIndexMap(other.dataIndexMap);
    169		}
    170
    171		T& operator[](std::string key)
    172		{
    173			INIStringUtil::trim(key);
    174#ifndef MINI_CASE_SENSITIVE
    175			INIStringUtil::toLower(key);
    176#endif
    177			auto it = dataIndexMap.find(key);
    178			bool hasIt = (it != dataIndexMap.end());
    179			std::size_t index = (hasIt) ? it->second : setEmpty(key);
    180			return data[index].second;
    181		}
    182		T get(std::string key) const
    183		{
    184			INIStringUtil::trim(key);
    185#ifndef MINI_CASE_SENSITIVE
    186			INIStringUtil::toLower(key);
    187#endif
    188			auto it = dataIndexMap.find(key);
    189			if (it == dataIndexMap.end())
    190			{
    191				return T();
    192			}
    193			return T(data[it->second].second);
    194		}
    195		bool has(std::string key) const
    196		{
    197			INIStringUtil::trim(key);
    198#ifndef MINI_CASE_SENSITIVE
    199			INIStringUtil::toLower(key);
    200#endif
    201			return (dataIndexMap.count(key) == 1);
    202		}
    203		void set(std::string key, T obj)
    204		{
    205			INIStringUtil::trim(key);
    206#ifndef MINI_CASE_SENSITIVE
    207			INIStringUtil::toLower(key);
    208#endif
    209			auto it = dataIndexMap.find(key);
    210			if (it != dataIndexMap.end())
    211			{
    212				data[it->second].second = obj;
    213			}
    214			else
    215			{
    216				dataIndexMap[key] = data.size();
    217				data.emplace_back(key, obj);
    218			}
    219		}
    220		void set(T_MultiArgs const& multiArgs)
    221		{
    222			for (auto const& it : multiArgs)
    223			{
    224				auto const& key = it.first;
    225				auto const& obj = it.second;
    226				set(key, obj);
    227			}
    228		}
    229		bool remove(std::string key)
    230		{
    231			INIStringUtil::trim(key);
    232#ifndef MINI_CASE_SENSITIVE
    233			INIStringUtil::toLower(key);
    234#endif
    235			auto it = dataIndexMap.find(key);
    236			if (it != dataIndexMap.end())
    237			{
    238				std::size_t index = it->second;
    239				data.erase(data.begin() + index);
    240				dataIndexMap.erase(it);
    241				for (auto& it2 : dataIndexMap)
    242				{
    243					auto& vi = it2.second;
    244					if (vi > index)
    245					{
    246						vi--;
    247					}
    248				}
    249				return true;
    250			}
    251			return false;
    252		}
    253		void clear()
    254		{
    255			data.clear();
    256			dataIndexMap.clear();
    257		}
    258		std::size_t size() const
    259		{
    260			return data.size();
    261		}
    262		const_iterator begin() const { return data.begin(); }
    263		const_iterator end() const { return data.end(); }
    264	};
    265
    266	using INIStructure = INIMap<INIMap<std::string>>;
    267
    268	namespace INIParser
    269	{
    270		using T_ParseValues = std::pair<std::string, std::string>;
    271
    272		enum class PDataType : char
    273		{
    274			PDATA_NONE,
    275			PDATA_COMMENT,
    276			PDATA_SECTION,
    277			PDATA_KEYVALUE,
    278			PDATA_UNKNOWN
    279		};
    280
    281		inline PDataType parseLine(std::string line, T_ParseValues& parseData)
    282		{
    283			parseData.first.clear();
    284			parseData.second.clear();
    285			INIStringUtil::trim(line);
    286			if (line.empty())
    287			{
    288				return PDataType::PDATA_NONE;
    289			}
    290			char firstCharacter = line[0];
    291			if (firstCharacter == ';')
    292			{
    293				return PDataType::PDATA_COMMENT;
    294			}
    295			if (firstCharacter == '[')
    296			{
    297				auto commentAt = line.find_first_of(';');
    298				if (commentAt != std::string::npos)
    299				{
    300					line = line.substr(0, commentAt);
    301				}
    302				auto closingBracketAt = line.find_last_of(']');
    303				if (closingBracketAt != std::string::npos)
    304				{
    305					auto section = line.substr(1, closingBracketAt - 1);
    306					INIStringUtil::trim(section);
    307					parseData.first = section;
    308					return PDataType::PDATA_SECTION;
    309				}
    310			}
    311			auto lineNorm = line;
    312			INIStringUtil::replace(lineNorm, "\\=", "  ");
    313			auto equalsAt = lineNorm.find_first_of('=');
    314			if (equalsAt != std::string::npos)
    315			{
    316				auto key = line.substr(0, equalsAt);
    317				INIStringUtil::trim(key);
    318				INIStringUtil::replace(key, "\\=", "=");
    319				auto value = line.substr(equalsAt + 1);
    320				INIStringUtil::trim(value);
    321				parseData.first = key;
    322				parseData.second = value;
    323				return PDataType::PDATA_KEYVALUE;
    324			}
    325			return PDataType::PDATA_UNKNOWN;
    326		}
    327	};
    328
    329	class INIReader
    330	{
    331	public:
    332		using T_LineData = std::vector<std::string>;
    333		using T_LineDataPtr = std::shared_ptr<T_LineData>;
    334
    335	private:
    336		std::ifstream fileReadStream;
    337		T_LineDataPtr lineData;
    338
    339		T_LineData readFile()
    340		{
    341			std::string fileContents;
    342			fileReadStream.seekg(0, std::ios::end);
    343			fileContents.resize(fileReadStream.tellg());
    344			fileReadStream.seekg(0, std::ios::beg);
    345			std::size_t fileSize = fileContents.size();
    346			fileReadStream.read(&fileContents[0], fileSize);
    347			fileReadStream.close();
    348			T_LineData output;
    349			if (fileSize == 0)
    350			{
    351				return output;
    352			}
    353			std::string buffer;
    354			buffer.reserve(50);
    355			for (std::size_t i = 0; i < fileSize; ++i)
    356			{
    357				char& c = fileContents[i];
    358				if (c == '\n')
    359				{
    360					output.emplace_back(buffer);
    361					buffer.clear();
    362					continue;
    363				}
    364				if (c != '\0' && c != '\r')
    365				{
    366					buffer += c;
    367				}
    368			}
    369			output.emplace_back(buffer);
    370			return output;
    371		}
    372
    373	public:
    374		INIReader(std::string const& filename, bool keepLineData = false)
    375		{
    376			fileReadStream.open(filename, std::ios::in | std::ios::binary);
    377			if (keepLineData)
    378			{
    379				lineData = std::make_shared<T_LineData>();
    380			}
    381		}
    382		~INIReader() { }
    383
    384		bool operator>>(INIStructure& data)
    385		{
    386			if (!fileReadStream.is_open())
    387			{
    388				return false;
    389			}
    390			T_LineData fileLines = readFile();
    391			std::string section;
    392			bool inSection = false;
    393			INIParser::T_ParseValues parseData;
    394			for (auto const& line : fileLines)
    395			{
    396				auto parseResult = INIParser::parseLine(line, parseData);
    397				if (parseResult == INIParser::PDataType::PDATA_SECTION)
    398				{
    399					inSection = true;
    400					data[section = parseData.first];
    401				}
    402				else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE)
    403				{
    404					auto const& key = parseData.first;
    405					auto const& value = parseData.second;
    406					data[section][key] = value;
    407				}
    408				if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN)
    409				{
    410					if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection)
    411					{
    412						continue;
    413					}
    414					lineData->emplace_back(line);
    415				}
    416			}
    417			return true;
    418		}
    419		T_LineDataPtr getLines()
    420		{
    421			return lineData;
    422		}
    423	};
    424
    425	class INIGenerator
    426	{
    427	private:
    428		std::ofstream fileWriteStream;
    429
    430	public:
    431		bool prettyPrint = false;
    432
    433		INIGenerator(std::string const& filename)
    434		{
    435			fileWriteStream.open(filename, std::ios::out | std::ios::binary);
    436		}
    437		~INIGenerator() { }
    438
    439		bool operator<<(INIStructure const& data)
    440		{
    441			if (!fileWriteStream.is_open())
    442			{
    443				return false;
    444			}
    445			if (!data.size())
    446			{
    447				return true;
    448			}
    449			auto it = data.begin();
    450			for (;;)
    451			{
    452				auto const& section = it->first;
    453				auto const& collection = it->second;
    454				fileWriteStream
    455					<< "["
    456					<< section
    457					<< "]";
    458				if (collection.size())
    459				{
    460					fileWriteStream << INIStringUtil::endl;
    461					auto it2 = collection.begin();
    462					for (;;)
    463					{
    464						auto key = it2->first;
    465						INIStringUtil::replace(key, "=", "\\=");
    466						auto value = it2->second;
    467						INIStringUtil::trim(value);
    468						fileWriteStream
    469							<< key
    470							<< ((prettyPrint) ? " = " : "=")
    471							<< value;
    472						if (++it2 == collection.end())
    473						{
    474							break;
    475						}
    476						fileWriteStream << INIStringUtil::endl;
    477					}
    478				}
    479				if (++it == data.end())
    480				{
    481					break;
    482				}
    483				fileWriteStream << INIStringUtil::endl;
    484				if (prettyPrint)
    485				{
    486					fileWriteStream << INIStringUtil::endl;
    487				}
    488			}
    489			return true;
    490		}
    491	};
    492
    493	class INIWriter
    494	{
    495	private:
    496		using T_LineData = std::vector<std::string>;
    497		using T_LineDataPtr = std::shared_ptr<T_LineData>;
    498
    499		std::string filename;
    500
    501		T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original)
    502		{
    503			T_LineData output;
    504			INIParser::T_ParseValues parseData;
    505			std::string sectionCurrent;
    506			bool parsingSection = false;
    507			bool continueToNextSection = false;
    508			bool discardNextEmpty = false;
    509			bool writeNewKeys = false;
    510			std::size_t lastKeyLine = 0;
    511			for (auto line = lineData->begin(); line != lineData->end(); ++line)
    512			{
    513				if (!writeNewKeys)
    514				{
    515					auto parseResult = INIParser::parseLine(*line, parseData);
    516					if (parseResult == INIParser::PDataType::PDATA_SECTION)
    517					{
    518						if (parsingSection)
    519						{
    520							writeNewKeys = true;
    521							parsingSection = false;
    522							--line;
    523							continue;
    524						}
    525						sectionCurrent = parseData.first;
    526						if (data.has(sectionCurrent))
    527						{
    528							parsingSection = true;
    529							continueToNextSection = false;
    530							discardNextEmpty = false;
    531							output.emplace_back(*line);
    532							lastKeyLine = output.size();
    533						}
    534						else
    535						{
    536							continueToNextSection = true;
    537							discardNextEmpty = true;
    538							continue;
    539						}
    540					}
    541					else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE)
    542					{
    543						if (continueToNextSection)
    544						{
    545							continue;
    546						}
    547						if (data.has(sectionCurrent))
    548						{
    549							auto& collection = data[sectionCurrent];
    550							auto const& key = parseData.first;
    551							auto const& value = parseData.second;
    552							if (collection.has(key))
    553							{
    554								auto outputValue = collection[key];
    555								if (value == outputValue)
    556								{
    557									output.emplace_back(*line);
    558								}
    559								else
    560								{
    561									INIStringUtil::trim(outputValue);
    562									auto lineNorm = *line;
    563									INIStringUtil::replace(lineNorm, "\\=", "  ");
    564									auto equalsAt = lineNorm.find_first_of('=');
    565									auto valueAt = lineNorm.find_first_not_of(
    566										INIStringUtil::whitespaceDelimiters,
    567										equalsAt + 1
    568									);
    569									std::string outputLine = line->substr(0, valueAt);
    570									if (prettyPrint && equalsAt + 1 == valueAt)
    571									{
    572										outputLine += " ";
    573									}
    574									outputLine += outputValue;
    575									output.emplace_back(outputLine);
    576								}
    577								lastKeyLine = output.size();
    578							}
    579						}
    580					}
    581					else
    582					{
    583						if (discardNextEmpty && line->empty())
    584						{
    585							discardNextEmpty = false;
    586						}
    587						else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN)
    588						{
    589							output.emplace_back(*line);
    590						}
    591					}
    592				}
    593				if (writeNewKeys || std::next(line) == lineData->end())
    594				{
    595					T_LineData linesToAdd;
    596					if (data.has(sectionCurrent) && original.has(sectionCurrent))
    597					{
    598						auto const& collection = data[sectionCurrent];
    599						auto const& collectionOriginal = original[sectionCurrent];
    600						for (auto const& it : collection)
    601						{
    602							auto key = it.first;
    603							if (collectionOriginal.has(key))
    604							{
    605								continue;
    606							}
    607							auto value = it.second;
    608							INIStringUtil::replace(key, "=", "\\=");
    609							INIStringUtil::trim(value);
    610							linesToAdd.emplace_back(
    611								key + ((prettyPrint) ? " = " : "=") + value
    612							);
    613						}
    614					}
    615					if (!linesToAdd.empty())
    616					{
    617						output.insert(
    618							output.begin() + lastKeyLine,
    619							linesToAdd.begin(),
    620							linesToAdd.end()
    621						);
    622					}
    623					if (writeNewKeys)
    624					{
    625						writeNewKeys = false;
    626						--line;
    627					}
    628				}
    629			}
    630			for (auto const& it : data)
    631			{
    632				auto const& section = it.first;
    633				if (original.has(section))
    634				{
    635					continue;
    636				}
    637				if (prettyPrint && output.size() > 0 && !output.back().empty())
    638				{
    639					output.emplace_back();
    640				}
    641				output.emplace_back("[" + section + "]");
    642				auto const& collection = it.second;
    643				for (auto const& it2 : collection)
    644				{
    645					auto key = it2.first;
    646					auto value = it2.second;
    647					INIStringUtil::replace(key, "=", "\\=");
    648					INIStringUtil::trim(value);
    649					output.emplace_back(
    650						key + ((prettyPrint) ? " = " : "=") + value
    651					);
    652				}
    653			}
    654			return output;
    655		}
    656
    657	public:
    658		bool prettyPrint = false;
    659
    660		INIWriter(std::string const& filename)
    661		: filename(filename)
    662		{
    663		}
    664		~INIWriter() { }
    665
    666		bool operator<<(INIStructure& data)
    667		{
    668			struct stat buf;
    669			bool fileExists = (stat(filename.c_str(), &buf) == 0);
    670			if (!fileExists)
    671			{
    672				INIGenerator generator(filename);
    673				generator.prettyPrint = prettyPrint;
    674				return generator << data;
    675			}
    676			INIStructure originalData;
    677			T_LineDataPtr lineData;
    678			bool readSuccess = false;
    679			{
    680				INIReader reader(filename, true);
    681				if ((readSuccess = reader >> originalData))
    682				{
    683					lineData = reader.getLines();
    684				}
    685			}
    686			if (!readSuccess)
    687			{
    688				return false;
    689			}
    690			T_LineData output = getLazyOutput(lineData, data, originalData);
    691			std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary);
    692			if (fileWriteStream.is_open())
    693			{
    694				if (output.size())
    695				{
    696					auto line = output.begin();
    697					for (;;)
    698					{
    699						fileWriteStream << *line;
    700						if (++line == output.end())
    701						{
    702							break;
    703						}
    704						fileWriteStream << INIStringUtil::endl;
    705					}
    706				}
    707				return true;
    708			}
    709			return false;
    710		}
    711	};
    712
    713	class INIFile
    714	{
    715	private:
    716		std::string filename;
    717
    718	public:
    719		INIFile(std::string const& filename)
    720		: filename(filename)
    721		{ }
    722
    723		~INIFile() { }
    724
    725		bool read(INIStructure& data) const
    726		{
    727			if (data.size())
    728			{
    729				data.clear();
    730			}
    731			if (filename.empty())
    732			{
    733				return false;
    734			}
    735			INIReader reader(filename);
    736			return reader >> data;
    737		}
    738		bool generate(INIStructure const& data, bool pretty = false) const
    739		{
    740			if (filename.empty())
    741			{
    742				return false;
    743			}
    744			INIGenerator generator(filename);
    745			generator.prettyPrint = pretty;
    746			return generator << data;
    747		}
    748		bool write(INIStructure& data, bool pretty = false) const
    749		{
    750			if (filename.empty())
    751			{
    752				return false;
    753			}
    754			INIWriter writer(filename);
    755			writer.prettyPrint = pretty;
    756			return writer << data;
    757		}
    758	};
    759}
    760
    761#endif // MINI_INI_H_