Mailer
Loading...
Searching...
No Matches
ini.h
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
25//
26// /mINI/ v0.9.17
27// An INI file reader and writer for the modern age.
28//
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//
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//
78//
79// Long live the INI file!!!
80//
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
110 inline void toLower(std::string &str)
111 {
112 std::transform(str.begin(), str.end(), str.begin(), [](const char c)
113 { return static_cast<char>(std::tolower(c)); });
114 }
115#endif
120 inline void replace(std::string &str, std::string const &a, std::string const &b)
121 {
122 if (!a.empty())
123 {
124 std::size_t pos = 0;
125 while ((pos = str.find(a, pos)) != std::string::npos)
126 {
127 str.replace(pos, a.size(), b);
128 pos += b.size();
129 }
130 }
131 }
132#ifdef _WIN32
133 const char *const endl = "\r\n";
134#else
135 const char *const endl = "\n";
136#endif
137 }
138
141 template <typename T>
142 class INIMap
143 {
144 private:
145 using T_DataIndexMap = std::unordered_map<std::string, std::size_t>;
146 using T_DataItem = std::pair<std::string, T>;
147 using T_DataContainer = std::vector<T_DataItem>;
148 using T_MultiArgs = typename std::vector<std::pair<std::string, T>>;
149
150 T_DataIndexMap dataIndexMap;
151 T_DataContainer data;
152
153 inline std::size_t setEmpty(std::string &key)
154 {
155 std::size_t index = data.size();
156 dataIndexMap[key] = index;
157 data.emplace_back(key, T());
158 return index;
159 }
160
161 public:
163 using const_iterator = typename T_DataContainer::const_iterator;
164
167
170 INIMap(INIMap const &other) : dataIndexMap(other.dataIndexMap), data(other.data)
171 {
172 }
173
177 T &operator[](std::string key)
178 {
179 INIStringUtil::trim(key);
180#ifndef MINI_CASE_SENSITIVE
181 INIStringUtil::toLower(key);
182#endif
183 auto it = dataIndexMap.find(key);
184 bool hasIt = (it != dataIndexMap.end());
185 std::size_t index = (hasIt) ? it->second : setEmpty(key);
186 return data[index].second;
187 }
188
192 T get(std::string key) const
193 {
194 INIStringUtil::trim(key);
195#ifndef MINI_CASE_SENSITIVE
196 INIStringUtil::toLower(key);
197#endif
198 auto it = dataIndexMap.find(key);
199 if (it == dataIndexMap.end())
200 {
201 return T();
202 }
203 return T(data[it->second].second);
204 }
205
209 bool has(std::string key) const
210 {
211 INIStringUtil::trim(key);
212#ifndef MINI_CASE_SENSITIVE
213 INIStringUtil::toLower(key);
214#endif
215 return (dataIndexMap.count(key) == 1);
216 }
217
221 void set(std::string key, T obj)
222 {
223 INIStringUtil::trim(key);
224#ifndef MINI_CASE_SENSITIVE
225 INIStringUtil::toLower(key);
226#endif
227 auto it = dataIndexMap.find(key);
228 if (it != dataIndexMap.end())
229 {
230 data[it->second].second = obj;
231 }
232 else
233 {
234 dataIndexMap[key] = data.size();
235 data.emplace_back(key, obj);
236 }
237 }
238
241 void set(T_MultiArgs const &multiArgs)
242 {
243 for (auto const &it : multiArgs)
244 {
245 auto const &key = it.first;
246 auto const &obj = it.second;
247 set(key, obj);
248 }
249 }
250
254 bool remove(std::string key)
255 {
256 INIStringUtil::trim(key);
257#ifndef MINI_CASE_SENSITIVE
258 INIStringUtil::toLower(key);
259#endif
260 auto it = dataIndexMap.find(key);
261 if (it != dataIndexMap.end())
262 {
263 std::size_t index = it->second;
264 data.erase(data.begin() + index);
265 dataIndexMap.erase(it);
266 for (auto &it2 : dataIndexMap)
267 {
268 auto &vi = it2.second;
269 if (vi > index)
270 {
271 vi--;
272 }
273 }
274 return true;
275 }
276 return false;
277 }
278
280 void clear()
281 {
282 data.clear();
283 dataIndexMap.clear();
284 }
285
288 std::size_t size() const
289 {
290 return data.size();
291 }
292
295 const_iterator begin() const { return data.begin(); }
296
299 const_iterator end() const { return data.end(); }
300 };
301
302 using INIStructure = INIMap<INIMap<std::string>>;
303
304 namespace INIParser
305 {
306 using T_ParseValues = std::pair<std::string, std::string>;
307
308 enum class PDataType : char
309 {
310 PDATA_NONE,
311 PDATA_COMMENT,
312 PDATA_SECTION,
313 PDATA_KEYVALUE,
314 PDATA_UNKNOWN
315 };
316
321 inline PDataType parseLine(std::string line, T_ParseValues &parseData)
322 {
323 parseData.first.clear();
324 parseData.second.clear();
325 INIStringUtil::trim(line);
326 if (line.empty())
327 {
328 return PDataType::PDATA_NONE;
329 }
330 char firstCharacter = line[0];
331 if (firstCharacter == ';')
332 {
333 return PDataType::PDATA_COMMENT;
334 }
335 if (firstCharacter == '[')
336 {
337 auto commentAt = line.find_first_of(';');
338 if (commentAt != std::string::npos)
339 {
340 line = line.substr(0, commentAt);
341 }
342 auto closingBracketAt = line.find_last_of(']');
343 if (closingBracketAt != std::string::npos)
344 {
345 auto section = line.substr(1, closingBracketAt - 1);
346 INIStringUtil::trim(section);
347 parseData.first = section;
348 return PDataType::PDATA_SECTION;
349 }
350 }
351 auto lineNorm = line;
352 INIStringUtil::replace(lineNorm, "\\=", " ");
353 auto equalsAt = lineNorm.find_first_of('=');
354 if (equalsAt != std::string::npos)
355 {
356 auto key = line.substr(0, equalsAt);
357 INIStringUtil::trim(key);
358 INIStringUtil::replace(key, "\\=", "=");
359 auto value = line.substr(equalsAt + 1);
360 INIStringUtil::trim(value);
361 parseData.first = key;
362 parseData.second = value;
363 return PDataType::PDATA_KEYVALUE;
364 }
365 return PDataType::PDATA_UNKNOWN;
366 }
367 }
368
371 {
372 public:
374 using T_LineData = std::vector<std::string>;
376 using T_LineDataPtr = std::shared_ptr<T_LineData>;
377
379 bool isBOM = false;
380
381 private:
382 std::ifstream fileReadStream;
383 T_LineDataPtr lineData;
384
387 T_LineData readFile()
388 {
389 fileReadStream.seekg(0, std::ios::end);
390 const std::size_t fileSize = static_cast<std::size_t>(fileReadStream.tellg());
391 fileReadStream.seekg(0, std::ios::beg);
392 if (fileSize >= 3)
393 {
394 const char header[3] = {
395 static_cast<char>(fileReadStream.get()),
396 static_cast<char>(fileReadStream.get()),
397 static_cast<char>(fileReadStream.get())};
398 isBOM = (header[0] == static_cast<char>(0xEF) &&
399 header[1] == static_cast<char>(0xBB) &&
400 header[2] == static_cast<char>(0xBF));
401 }
402 else
403 {
404 isBOM = false;
405 }
406 std::string fileContents;
407 fileContents.resize(fileSize);
408 fileReadStream.seekg(isBOM ? 3 : 0, std::ios::beg);
409 fileReadStream.read(&fileContents[0], fileSize);
410 fileReadStream.close();
411 T_LineData output;
412 if (fileSize == 0)
413 {
414 return output;
415 }
416 std::string buffer;
417 buffer.reserve(50);
418 for (std::size_t i = 0; i < fileSize; ++i)
419 {
420 char &c = fileContents[i];
421 if (c == '\n')
422 {
423 output.emplace_back(buffer);
424 buffer.clear();
425 continue;
426 }
427 if (c != '\0' && c != '\r')
428 {
429 buffer += c;
430 }
431 }
432 output.emplace_back(buffer);
433 return output;
434 }
435
436 public:
440 INIReader(std::string const &filename, bool keepLineData = false)
441 {
442 fileReadStream.open(filename, std::ios::in | std::ios::binary);
443 if (keepLineData)
444 {
445 lineData = std::make_shared<T_LineData>();
446 }
447 }
450
455 {
456 if (!fileReadStream.is_open())
457 {
458 return false;
459 }
460 T_LineData fileLines = readFile();
461 std::string section;
462 bool inSection = false;
463 INIParser::T_ParseValues parseData;
464 for (auto const &line : fileLines)
465 {
466 auto parseResult = INIParser::parseLine(line, parseData);
467 if (parseResult == INIParser::PDataType::PDATA_SECTION)
468 {
469 inSection = true;
470 data[section = parseData.first];
471 }
472 else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE)
473 {
474 auto const &key = parseData.first;
475 auto const &value = parseData.second;
476 data[section][key] = value;
477 }
478 if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN)
479 {
480 if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection)
481 {
482 continue;
483 }
484 lineData->emplace_back(line);
485 }
486 }
487 return true;
488 }
489
493 {
494 return lineData;
495 }
496 };
497
500 {
501 private:
502 std::ofstream fileWriteStream;
503
504 public:
506 bool prettyPrint = false;
507
510 INIGenerator(std::string const &filename)
511 {
512 fileWriteStream.open(filename, std::ios::out | std::ios::binary);
513 }
516
520 bool operator<<(INIStructure const &data)
521 {
522 if (!fileWriteStream.is_open())
523 {
524 return false;
525 }
526 if (!data.size())
527 {
528 return true;
529 }
530 auto it = data.begin();
531 for (;;)
532 {
533 auto const &section = it->first;
534 auto const &collection = it->second;
535 fileWriteStream
536 << "["
537 << section
538 << "]";
539 if (collection.size())
540 {
541 fileWriteStream << INIStringUtil::endl;
542 auto it2 = collection.begin();
543 for (;;)
544 {
545 auto key = it2->first;
546 INIStringUtil::replace(key, "=", "\\=");
547 auto value = it2->second;
548 INIStringUtil::trim(value);
549 fileWriteStream
550 << key
551 << ((prettyPrint) ? " = " : "=")
552 << value;
553 if (++it2 == collection.end())
554 {
555 break;
556 }
557 fileWriteStream << INIStringUtil::endl;
558 }
559 }
560 if (++it == data.end())
561 {
562 break;
563 }
564 fileWriteStream << INIStringUtil::endl;
565 if (prettyPrint)
566 {
567 fileWriteStream << INIStringUtil::endl;
568 }
569 }
570 return true;
571 }
572 };
573
576 {
577 private:
578 using T_LineData = std::vector<std::string>;
579 using T_LineDataPtr = std::shared_ptr<T_LineData>;
580
581 std::string filename;
582
583 T_LineData getLazyOutput(T_LineDataPtr const &lineData, INIStructure &data, INIStructure &original)
584 {
585 T_LineData output;
586 INIParser::T_ParseValues parseData;
587 std::string sectionCurrent;
588 bool parsingSection = false;
589 bool continueToNextSection = false;
590 bool discardNextEmpty = false;
591 bool writeNewKeys = false;
592 std::size_t lastKeyLine = 0;
593 for (auto line = lineData->begin(); line != lineData->end(); ++line)
594 {
595 if (!writeNewKeys)
596 {
597 auto parseResult = INIParser::parseLine(*line, parseData);
598 if (parseResult == INIParser::PDataType::PDATA_SECTION)
599 {
600 if (parsingSection)
601 {
602 writeNewKeys = true;
603 parsingSection = false;
604 --line;
605 continue;
606 }
607 sectionCurrent = parseData.first;
608 if (data.has(sectionCurrent))
609 {
610 parsingSection = true;
611 continueToNextSection = false;
612 discardNextEmpty = false;
613 output.emplace_back(*line);
614 lastKeyLine = output.size();
615 }
616 else
617 {
618 continueToNextSection = true;
619 discardNextEmpty = true;
620 continue;
621 }
622 }
623 else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE)
624 {
625 if (continueToNextSection)
626 {
627 continue;
628 }
629 if (data.has(sectionCurrent))
630 {
631 auto &collection = data[sectionCurrent];
632 auto const &key = parseData.first;
633 auto const &value = parseData.second;
634 if (collection.has(key))
635 {
636 auto outputValue = collection[key];
637 if (value == outputValue)
638 {
639 output.emplace_back(*line);
640 }
641 else
642 {
643 INIStringUtil::trim(outputValue);
644 auto lineNorm = *line;
645 INIStringUtil::replace(lineNorm, "\\=", " ");
646 auto equalsAt = lineNorm.find_first_of('=');
647 auto valueAt = lineNorm.find_first_not_of(
648 INIStringUtil::whitespaceDelimiters,
649 equalsAt + 1);
650 std::string outputLine = line->substr(0, valueAt);
651 if (prettyPrint && equalsAt + 1 == valueAt)
652 {
653 outputLine += " ";
654 }
655 outputLine += outputValue;
656 output.emplace_back(outputLine);
657 }
658 lastKeyLine = output.size();
659 }
660 }
661 }
662 else
663 {
664 if (discardNextEmpty && line->empty())
665 {
666 discardNextEmpty = false;
667 }
668 else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN)
669 {
670 output.emplace_back(*line);
671 }
672 }
673 }
674 if (writeNewKeys || std::next(line) == lineData->end())
675 {
676 T_LineData linesToAdd;
677 if (data.has(sectionCurrent) && original.has(sectionCurrent))
678 {
679 auto const &collection = data[sectionCurrent];
680 auto const &collectionOriginal = original[sectionCurrent];
681 for (auto const &it : collection)
682 {
683 auto key = it.first;
684 if (collectionOriginal.has(key))
685 {
686 continue;
687 }
688 auto value = it.second;
689 INIStringUtil::replace(key, "=", "\\=");
690 INIStringUtil::trim(value);
691 linesToAdd.emplace_back(
692 key + ((prettyPrint) ? " = " : "=") + value);
693 }
694 }
695 if (!linesToAdd.empty())
696 {
697 output.insert(
698 output.begin() + lastKeyLine,
699 linesToAdd.begin(),
700 linesToAdd.end());
701 }
702 if (writeNewKeys)
703 {
704 writeNewKeys = false;
705 --line;
706 }
707 }
708 }
709 for (auto const &it : data)
710 {
711 auto const &section = it.first;
712 if (original.has(section))
713 {
714 continue;
715 }
716 if (prettyPrint && output.size() > 0 && !output.back().empty())
717 {
718 output.emplace_back();
719 }
720 output.emplace_back("[" + section + "]");
721 auto const &collection = it.second;
722 for (auto const &it2 : collection)
723 {
724 auto key = it2.first;
725 auto value = it2.second;
726 INIStringUtil::replace(key, "=", "\\=");
727 INIStringUtil::trim(value);
728 output.emplace_back(
729 key + ((prettyPrint) ? " = " : "=") + value);
730 }
731 }
732 return output;
733 }
734
735 public:
737 bool prettyPrint = false;
738
741 INIWriter(std::string const &filename)
742 : filename(filename)
743 {
744 }
747
752 {
753 struct stat buf;
754 bool fileExists = (stat(filename.c_str(), &buf) == 0);
755 if (!fileExists)
756 {
757 INIGenerator generator(filename);
758 generator.prettyPrint = prettyPrint;
759 return generator << data;
760 }
761 INIStructure originalData;
762 T_LineDataPtr lineData;
763 bool readSuccess = false;
764 bool fileIsBOM = false;
765 {
766 INIReader reader(filename, true);
767 if ((readSuccess = reader >> originalData))
768 {
769 lineData = reader.getLines();
770 fileIsBOM = reader.isBOM;
771 }
772 }
773 if (!readSuccess)
774 {
775 return false;
776 }
777 T_LineData output = getLazyOutput(lineData, data, originalData);
778 std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary);
779 if (fileWriteStream.is_open())
780 {
781 if (fileIsBOM)
782 {
783 const char utf8_BOM[3] = {
784 static_cast<char>(0xEF),
785 static_cast<char>(0xBB),
786 static_cast<char>(0xBF)};
787 fileWriteStream.write(utf8_BOM, 3);
788 }
789 if (output.size())
790 {
791 auto line = output.begin();
792 for (;;)
793 {
794 fileWriteStream << *line;
795 if (++line == output.end())
796 {
797 break;
798 }
799 fileWriteStream << INIStringUtil::endl;
800 }
801 }
802 return true;
803 }
804 return false;
805 }
806 };
807
810 {
811 private:
812 std::string filename;
813
814 public:
817 INIFile(std::string const &filename)
818 : filename(filename)
819 {
820 }
821
824
828 bool read(INIStructure &data) const
829 {
830 if (data.size())
831 {
832 data.clear();
833 }
834 if (filename.empty())
835 {
836 return false;
837 }
838 INIReader reader(filename);
839 return reader >> data;
840 }
841
846 bool generate(INIStructure const &data, bool pretty = false) const
847 {
848 if (filename.empty())
849 {
850 return false;
851 }
852 INIGenerator generator(filename);
853 generator.prettyPrint = pretty;
854 return generator << data;
855 }
856
861 bool write(INIStructure &data, bool pretty = false) const
862 {
863 if (filename.empty())
864 {
865 return false;
866 }
867 INIWriter writer(filename);
868 writer.prettyPrint = pretty;
869 return writer << data;
870 }
871 };
872}
873
874#endif // MINI_INI_H_
INIFile class.
Definition ini.h:810
bool read(INIStructure &data) const
Clears "data" contents and reads INI file.
Definition ini.h:828
bool generate(INIStructure const &data, bool pretty=false) const
Generate INI structure into data.
Definition ini.h:846
~INIFile()
Destructor.
Definition ini.h:823
INIFile(std::string const &filename)
Constructor.
Definition ini.h:817
bool write(INIStructure &data, bool pretty=false) const
Writes INI structure to INI file.
Definition ini.h:861
INIGenerator class.
Definition ini.h:500
bool prettyPrint
Beautifier flag.
Definition ini.h:506
bool operator<<(INIStructure const &data)
Write generated from "data" INI file.
Definition ini.h:520
INIGenerator(std::string const &filename)
Constructor.
Definition ini.h:510
~INIGenerator()
Destructor.
Definition ini.h:515
INIMap class.
Definition ini.h:143
T get(std::string key) const
Gets Value of Key.
Definition ini.h:192
typename T_DataContainer::const_iterator const_iterator
Iterator for T_DataContainer.
Definition ini.h:163
void set(T_MultiArgs const &multiArgs)
Adds or modificating an existing Key with value Obj.
Definition ini.h:241
const_iterator end() const
Points the last item of data.
Definition ini.h:299
const_iterator begin() const
Points the beginning item of data.
Definition ini.h:295
bool has(std::string key) const
Check if Key exists.
Definition ini.h:209
INIMap()
Constructor.
Definition ini.h:166
bool remove(std::string key)
Remove Key and its value from dataIndexMap.
Definition ini.h:254
T & operator[](std::string key)
Array item access operator.
Definition ini.h:177
void set(std::string key, T obj)
Adds or modificating an existing Key with value Obj.
Definition ini.h:221
void clear()
Clears all the data.
Definition ini.h:280
INIMap(INIMap const &other)
Constructor.
Definition ini.h:170
std::size_t size() const
Returns number of pairs.
Definition ini.h:288
INIReader class.
Definition ini.h:371
~INIReader()
Destructor.
Definition ini.h:449
bool operator>>(INIStructure &data)
Reads INI file contents into "data" INI file structure.
Definition ini.h:454
T_LineDataPtr getLines()
Returns contents of INI file.
Definition ini.h:492
INIReader(std::string const &filename, bool keepLineData=false)
Constructor.
Definition ini.h:440
bool isBOM
BOM flag, indicetes, that indicates, that it is UTF-8.
Definition ini.h:379
std::vector< std::string > T_LineData
Vector of strings to store INI file structure into.
Definition ini.h:374
std::shared_ptr< T_LineData > T_LineDataPtr
Shared pointer to T_LineData.
Definition ini.h:376
INIWriter class.
Definition ini.h:576
bool prettyPrint
Beautifier.
Definition ini.h:737
bool operator<<(INIStructure &data)
Write prepared INI structure "data" to INI file.
Definition ini.h:751
INIWriter(std::string const &filename)
Constructor.
Definition ini.h:741
~INIWriter()
Destructor.
Definition ini.h:746