Photon 1.0.0
Loading...
Searching...
No Matches
ini.h
Go to the documentation of this file.
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.14
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
108 inline void toLower(std::string& str)
109 {
110 std::transform(str.begin(), str.end(), str.begin(), [](const char c) {
111 return static_cast<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
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 }
169 }
170
171 T& operator[](std::string key)
172 {
174#ifndef MINI_CASE_SENSITIVE
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 {
185#ifndef MINI_CASE_SENSITIVE
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 {
198#ifndef MINI_CASE_SENSITIVE
200#endif
201 return (dataIndexMap.count(key) == 1);
202 }
203 void set(std::string key, T obj)
204 {
206#ifndef MINI_CASE_SENSITIVE
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 {
232#ifndef MINI_CASE_SENSITIVE
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
267
268 namespace INIParser
269 {
270 using T_ParseValues = std::pair<std::string, std::string>;
271
272 enum class PDataType : char
273 {
279 };
280
281 inline PDataType parseLine(std::string line, T_ParseValues& parseData)
282 {
283 parseData.first.clear();
284 parseData.second.clear();
286 if (line.empty())
287 {
289 }
290 char firstCharacter = line[0];
291 if (firstCharacter == ';')
292 {
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);
307 parseData.first = 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);
318 INIStringUtil::replace(key, "\\=", "=");
319 auto value = line.substr(equalsAt + 1);
321 parseData.first = key;
322 parseData.second = value;
324 }
326 }
327 }
328
330 {
331 public:
332 using T_LineData = std::vector<std::string>;
333 using T_LineDataPtr = std::shared_ptr<T_LineData>;
334
335 bool isBOM = false;
336
337 private:
338 std::ifstream fileReadStream;
340
342 {
343 fileReadStream.seekg(0, std::ios::end);
344 const std::size_t fileSize = static_cast<std::size_t>(fileReadStream.tellg());
345 fileReadStream.seekg(0, std::ios::beg);
346 if (fileSize >= 3) {
347 const char header[3] = {
348 static_cast<char>(fileReadStream.get()),
349 static_cast<char>(fileReadStream.get()),
350 static_cast<char>(fileReadStream.get())
351 };
352 isBOM = (
353 header[0] == static_cast<char>(0xEF) &&
354 header[1] == static_cast<char>(0xBB) &&
355 header[2] == static_cast<char>(0xBF)
356 );
357 }
358 else {
359 isBOM = false;
360 }
361 std::string fileContents;
362 fileContents.resize(fileSize);
363 fileReadStream.seekg(isBOM ? 3 : 0, std::ios::beg);
364 fileReadStream.read(&fileContents[0], fileSize);
365 fileReadStream.close();
367 if (fileSize == 0)
368 {
369 return output;
370 }
371 std::string buffer;
372 buffer.reserve(50);
373 for (std::size_t i = 0; i < fileSize; ++i)
374 {
375 char& c = fileContents[i];
376 if (c == '\n')
377 {
378 output.emplace_back(buffer);
379 buffer.clear();
380 continue;
381 }
382 if (c != '\0' && c != '\r')
383 {
384 buffer += c;
385 }
386 }
387 output.emplace_back(buffer);
388 return output;
389 }
390
391 public:
392 INIReader(std::string const& filename, bool keepLineData = false)
393 {
394 fileReadStream.open(filename, std::ios::in | std::ios::binary);
395 if (keepLineData)
396 {
397 lineData = std::make_shared<T_LineData>();
398 }
399 }
401
403 {
404 if (!fileReadStream.is_open())
405 {
406 return false;
407 }
408 T_LineData fileLines = readFile();
409 std::string section;
410 bool inSection = false;
411 INIParser::T_ParseValues parseData;
412 for (auto const& line : fileLines)
413 {
414 auto parseResult = INIParser::parseLine(line, parseData);
415 if (parseResult == INIParser::PDataType::PDATA_SECTION)
416 {
417 inSection = true;
418 data[section = parseData.first];
419 }
420 else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE)
421 {
422 auto const& key = parseData.first;
423 auto const& value = parseData.second;
424 data[section][key] = value;
425 }
426 if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN)
427 {
428 if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection)
429 {
430 continue;
431 }
432 lineData->emplace_back(line);
433 }
434 }
435 return true;
436 }
438 {
439 return lineData;
440 }
441 };
442
444 {
445 private:
446 std::ofstream fileWriteStream;
447
448 public:
449 bool prettyPrint = false;
450
451 INIGenerator(std::string const& filename)
452 {
453 fileWriteStream.open(filename, std::ios::out | std::ios::binary);
454 }
456
458 {
459 if (!fileWriteStream.is_open())
460 {
461 return false;
462 }
463 if (!data.size())
464 {
465 return true;
466 }
467 auto it = data.begin();
468 for (;;)
469 {
470 auto const& section = it->first;
471 auto const& collection = it->second;
473 << "["
474 << section
475 << "]";
476 if (collection.size())
477 {
479 auto it2 = collection.begin();
480 for (;;)
481 {
482 auto key = it2->first;
483 INIStringUtil::replace(key, "=", "\\=");
484 auto value = it2->second;
487 << key
488 << ((prettyPrint) ? " = " : "=")
489 << value;
490 if (++it2 == collection.end())
491 {
492 break;
493 }
495 }
496 }
497 if (++it == data.end())
498 {
499 break;
500 }
502 if (prettyPrint)
503 {
505 }
506 }
507 return true;
508 }
509 };
510
512 {
513 private:
514 using T_LineData = std::vector<std::string>;
515 using T_LineDataPtr = std::shared_ptr<T_LineData>;
516
517 std::string filename;
518
520 {
522 INIParser::T_ParseValues parseData;
523 std::string sectionCurrent;
524 bool parsingSection = false;
525 bool continueToNextSection = false;
526 bool discardNextEmpty = false;
527 bool writeNewKeys = false;
528 std::size_t lastKeyLine = 0;
529 for (auto line = lineData->begin(); line != lineData->end(); ++line)
530 {
531 if (!writeNewKeys)
532 {
533 auto parseResult = INIParser::parseLine(*line, parseData);
534 if (parseResult == INIParser::PDataType::PDATA_SECTION)
535 {
536 if (parsingSection)
537 {
538 writeNewKeys = true;
539 parsingSection = false;
540 --line;
541 continue;
542 }
543 sectionCurrent = parseData.first;
544 if (data.has(sectionCurrent))
545 {
546 parsingSection = true;
547 continueToNextSection = false;
548 discardNextEmpty = false;
549 output.emplace_back(*line);
550 lastKeyLine = output.size();
551 }
552 else
553 {
554 continueToNextSection = true;
555 discardNextEmpty = true;
556 continue;
557 }
558 }
559 else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE)
560 {
561 if (continueToNextSection)
562 {
563 continue;
564 }
565 if (data.has(sectionCurrent))
566 {
567 auto& collection = data[sectionCurrent];
568 auto const& key = parseData.first;
569 auto const& value = parseData.second;
570 if (collection.has(key))
571 {
572 auto outputValue = collection[key];
573 if (value == outputValue)
574 {
575 output.emplace_back(*line);
576 }
577 else
578 {
579 INIStringUtil::trim(outputValue);
580 auto lineNorm = *line;
581 INIStringUtil::replace(lineNorm, "\\=", " ");
582 auto equalsAt = lineNorm.find_first_of('=');
583 auto valueAt = lineNorm.find_first_not_of(
585 equalsAt + 1
586 );
587 std::string outputLine = line->substr(0, valueAt);
588 if (prettyPrint && equalsAt + 1 == valueAt)
589 {
590 outputLine += " ";
591 }
592 outputLine += outputValue;
593 output.emplace_back(outputLine);
594 }
595 lastKeyLine = output.size();
596 }
597 }
598 }
599 else
600 {
601 if (discardNextEmpty && line->empty())
602 {
603 discardNextEmpty = false;
604 }
605 else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN)
606 {
607 output.emplace_back(*line);
608 }
609 }
610 }
611 if (writeNewKeys || std::next(line) == lineData->end())
612 {
613 T_LineData linesToAdd;
614 if (data.has(sectionCurrent) && original.has(sectionCurrent))
615 {
616 auto const& collection = data[sectionCurrent];
617 auto const& collectionOriginal = original[sectionCurrent];
618 for (auto const& it : collection)
619 {
620 auto key = it.first;
621 if (collectionOriginal.has(key))
622 {
623 continue;
624 }
625 auto value = it.second;
626 INIStringUtil::replace(key, "=", "\\=");
628 linesToAdd.emplace_back(
629 key + ((prettyPrint) ? " = " : "=") + value
630 );
631 }
632 }
633 if (!linesToAdd.empty())
634 {
635 output.insert(
636 output.begin() + lastKeyLine,
637 linesToAdd.begin(),
638 linesToAdd.end()
639 );
640 }
641 if (writeNewKeys)
642 {
643 writeNewKeys = false;
644 --line;
645 }
646 }
647 }
648 for (auto const& it : data)
649 {
650 auto const& section = it.first;
651 if (original.has(section))
652 {
653 continue;
654 }
655 if (prettyPrint && output.size() > 0 && !output.back().empty())
656 {
657 output.emplace_back();
658 }
659 output.emplace_back("[" + section + "]");
660 auto const& collection = it.second;
661 for (auto const& it2 : collection)
662 {
663 auto key = it2.first;
664 auto value = it2.second;
665 INIStringUtil::replace(key, "=", "\\=");
667 output.emplace_back(
668 key + ((prettyPrint) ? " = " : "=") + value
669 );
670 }
671 }
672 return output;
673 }
674
675 public:
676 bool prettyPrint = false;
677
678 INIWriter(std::string const& filename)
680 {
681 }
683
685 {
686 struct stat buf;
687 bool fileExists = (stat(filename.c_str(), &buf) == 0);
688 if (!fileExists)
689 {
690 INIGenerator generator(filename);
691 generator.prettyPrint = prettyPrint;
692 return generator << data;
693 }
694 INIStructure originalData;
695 T_LineDataPtr lineData;
696 bool readSuccess = false;
697 bool fileIsBOM = false;
698 {
699 INIReader reader(filename, true);
700 if ((readSuccess = reader >> originalData))
701 {
702 lineData = reader.getLines();
703 fileIsBOM = reader.isBOM;
704 }
705 }
706 if (!readSuccess)
707 {
708 return false;
709 }
710 T_LineData output = getLazyOutput(lineData, data, originalData);
711 std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary);
712 if (fileWriteStream.is_open())
713 {
714 if (fileIsBOM) {
715 const char utf8_BOM[3] = {
716 static_cast<char>(0xEF),
717 static_cast<char>(0xBB),
718 static_cast<char>(0xBF)
719 };
720 fileWriteStream.write(utf8_BOM, 3);
721 }
722 if (output.size())
723 {
724 auto line = output.begin();
725 for (;;)
726 {
727 fileWriteStream << *line;
728 if (++line == output.end())
729 {
730 break;
731 }
732 fileWriteStream << INIStringUtil::endl;
733 }
734 }
735 return true;
736 }
737 return false;
738 }
739 };
740
742 {
743 private:
744 std::string filename;
745
746 public:
747 INIFile(std::string const& filename)
749 { }
750
752
753 bool read(INIStructure& data) const
754 {
755 if (data.size())
756 {
757 data.clear();
758 }
759 if (filename.empty())
760 {
761 return false;
762 }
763 INIReader reader(filename);
764 return reader >> data;
765 }
766 bool generate(INIStructure const& data, bool pretty = false) const
767 {
768 if (filename.empty())
769 {
770 return false;
771 }
772 INIGenerator generator(filename);
773 generator.prettyPrint = pretty;
774 return generator << data;
775 }
776 bool write(INIStructure& data, bool pretty = false) const
777 {
778 if (filename.empty())
779 {
780 return false;
781 }
782 INIWriter writer(filename);
783 writer.prettyPrint = pretty;
784 return writer << data;
785 }
786 };
787}
788
789#endif // MINI_INI_H_
Definition core.h:1032
void clear()
Definition core.h:1114
Definition ini.h:742
bool read(INIStructure &data) const
Definition ini.h:753
std::string filename
Definition ini.h:744
bool generate(INIStructure const &data, bool pretty=false) const
Definition ini.h:766
~INIFile()
Definition ini.h:751
INIFile(std::string const &filename)
Definition ini.h:747
bool write(INIStructure &data, bool pretty=false) const
Definition ini.h:776
Definition ini.h:444
bool prettyPrint
Definition ini.h:449
bool operator<<(INIStructure const &data)
Definition ini.h:457
INIGenerator(std::string const &filename)
Definition ini.h:451
std::ofstream fileWriteStream
Definition ini.h:446
~INIGenerator()
Definition ini.h:455
Definition ini.h:136
T get(std::string key) const
Definition ini.h:182
typename T_DataContainer::const_iterator const_iterator
Definition ini.h:155
T_DataIndexMap dataIndexMap
Definition ini.h:143
void set(T_MultiArgs const &multiArgs)
Definition ini.h:220
const_iterator end() const
Definition ini.h:263
std::pair< std::string, T > T_DataItem
Definition ini.h:139
const_iterator begin() const
Definition ini.h:262
std::unordered_map< std::string, std::size_t > T_DataIndexMap
Definition ini.h:138
bool has(std::string key) const
Definition ini.h:195
std::vector< T_DataItem > T_DataContainer
Definition ini.h:140
T_DataContainer data
Definition ini.h:144
INIMap()
Definition ini.h:157
bool remove(std::string key)
Definition ini.h:229
T & operator[](std::string key)
Definition ini.h:171
void set(std::string key, T obj)
Definition ini.h:203
typename std::vector< std::pair< std::string, T > > T_MultiArgs
Definition ini.h:141
std::size_t setEmpty(std::string &key)
Definition ini.h:146
void clear()
Definition ini.h:253
INIMap(INIMap const &other)
Definition ini.h:159
std::size_t size() const
Definition ini.h:258
Definition ini.h:330
T_LineDataPtr lineData
Definition ini.h:339
~INIReader()
Definition ini.h:400
bool operator>>(INIStructure &data)
Definition ini.h:402
T_LineDataPtr getLines()
Definition ini.h:437
T_LineData readFile()
Definition ini.h:341
INIReader(std::string const &filename, bool keepLineData=false)
Definition ini.h:392
bool isBOM
Definition ini.h:335
std::vector< std::string > T_LineData
Definition ini.h:332
std::shared_ptr< T_LineData > T_LineDataPtr
Definition ini.h:333
std::ifstream fileReadStream
Definition ini.h:338
Definition ini.h:512
bool prettyPrint
Definition ini.h:676
std::vector< std::string > T_LineData
Definition ini.h:514
std::string filename
Definition ini.h:517
std::shared_ptr< T_LineData > T_LineDataPtr
Definition ini.h:515
bool operator<<(INIStructure &data)
Definition ini.h:684
INIWriter(std::string const &filename)
Definition ini.h:678
~INIWriter()
Definition ini.h:682
T_LineData getLazyOutput(T_LineDataPtr const &lineData, INIStructure &data, INIStructure &original)
Definition ini.h:519
Definition core.h:1598
PDataType parseLine(std::string line, T_ParseValues &parseData)
Definition ini.h:281
std::pair< std::string, std::string > T_ParseValues
Definition ini.h:270
PDataType
Definition ini.h:273
const char *const endl
Definition ini.h:130
const char *const whitespaceDelimiters
Definition ini.h:101
void replace(std::string &str, std::string const &a, std::string const &b)
Definition ini.h:115
void toLower(std::string &str)
Definition ini.h:108
void trim(std::string &str)
Definition ini.h:102
Definition ini.h:98
INIMap< INIMap< std::string > > INIStructure
Definition ini.h:266
Definition format.h:1901
section
Definition tag_strings.h:16
b
Definition tag_strings.h:61
a
Definition tag_strings.h:43
annotation output
Definition tag_strings.h:122
i
Definition tag_strings.h:60
header
Definition tag_strings.h:26