Mercurial > minori
comparison dep/mini/ini.h @ 101:c537996cf67b
*: multitude of config changes
1. theme is now configurable from the settings menu
(but you have to restart for it to apply)
2. config is now stored in an INI file, with no method of
conversion from json (this repo is private-ish anyway)
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Fri, 03 Nov 2023 14:06:02 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
100:f5940a575d83 | 101:c537996cf67b |
---|---|
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.14 | |
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 | |
97 namespace 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 | |
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 bool isBOM = false; | |
336 | |
337 private: | |
338 std::ifstream fileReadStream; | |
339 T_LineDataPtr lineData; | |
340 | |
341 T_LineData readFile() | |
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(); | |
366 T_LineData output; | |
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 } | |
400 ~INIReader() { } | |
401 | |
402 bool operator>>(INIStructure& data) | |
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 } | |
437 T_LineDataPtr getLines() | |
438 { | |
439 return lineData; | |
440 } | |
441 }; | |
442 | |
443 class INIGenerator | |
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 } | |
455 ~INIGenerator() { } | |
456 | |
457 bool operator<<(INIStructure const& data) | |
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; | |
472 fileWriteStream | |
473 << "[" | |
474 << section | |
475 << "]"; | |
476 if (collection.size()) | |
477 { | |
478 fileWriteStream << INIStringUtil::endl; | |
479 auto it2 = collection.begin(); | |
480 for (;;) | |
481 { | |
482 auto key = it2->first; | |
483 INIStringUtil::replace(key, "=", "\\="); | |
484 auto value = it2->second; | |
485 INIStringUtil::trim(value); | |
486 fileWriteStream | |
487 << key | |
488 << ((prettyPrint) ? " = " : "=") | |
489 << value; | |
490 if (++it2 == collection.end()) | |
491 { | |
492 break; | |
493 } | |
494 fileWriteStream << INIStringUtil::endl; | |
495 } | |
496 } | |
497 if (++it == data.end()) | |
498 { | |
499 break; | |
500 } | |
501 fileWriteStream << INIStringUtil::endl; | |
502 if (prettyPrint) | |
503 { | |
504 fileWriteStream << INIStringUtil::endl; | |
505 } | |
506 } | |
507 return true; | |
508 } | |
509 }; | |
510 | |
511 class INIWriter | |
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 | |
519 T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original) | |
520 { | |
521 T_LineData output; | |
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( | |
584 INIStringUtil::whitespaceDelimiters, | |
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, "=", "\\="); | |
627 INIStringUtil::trim(value); | |
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, "=", "\\="); | |
666 INIStringUtil::trim(value); | |
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) | |
679 : filename(filename) | |
680 { | |
681 } | |
682 ~INIWriter() { } | |
683 | |
684 bool operator<<(INIStructure& data) | |
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 | |
741 class INIFile | |
742 { | |
743 private: | |
744 std::string filename; | |
745 | |
746 public: | |
747 INIFile(std::string const& filename) | |
748 : filename(filename) | |
749 { } | |
750 | |
751 ~INIFile() { } | |
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_ |