Fuck git
This commit is contained in:
8
external/mINI/.gitignore
vendored
Normal file
8
external/mINI/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
*.vim
|
||||
/tests/*.ini
|
||||
/tests/*.test
|
||||
/tests/*.exe
|
||||
/tests/build
|
||||
63
external/mINI/CHANGELOG.md
vendored
Normal file
63
external/mINI/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
## 0.9.18 (March 30, 2025)
|
||||
- `FEATURE` Replaces string paths with std::filesystem::path. ([pull #42](https://github.com/metayeti/mINI/pull/42))
|
||||
|
||||
## 0.9.17 (September 30, 2024)
|
||||
- `FEATURE` Adds `CMakeLists.txt`. ([pull #38](https://github.com/metayeti/mINI/pull/38))
|
||||
|
||||
## 0.9.16 (August 13, 2024)
|
||||
- `BUGFIX` Fixes a serious regression bug where removing a section would break the file in random ways due to the assignment operator introduced in the previous version. This version removes the assignment operator until it can be implemented in a way that is compliant with expected behavior. ([issue #36](https://github.com/metayeti/mINI/issues/36))
|
||||
|
||||
## 0.9.15 (January 11, 2024)
|
||||
- `BUGFIX` Fixes G++ warnings and implements a copy assignment operator for mINI::INIMap. ([pull #28](https://github.com/metayeti/mINI/pull/28))
|
||||
|
||||
## 0.9.14 (May 27, 2022)
|
||||
- `BUGFIX` Fixes C4310 warning. ([issue #19](https://github.com/metayeti/mINI/issues/19))
|
||||
|
||||
## 0.9.13 (April 25, 2022)
|
||||
- `BUGFIX` Writer now understands UTF-8 BOM-encoded files. ([issue #7](https://github.com/metayeti/mINI/issues/17))
|
||||
- `BUGFIX` Fixes a bug introduced in 0.9.12 where reader would break when reading empty files.
|
||||
|
||||
## 0.9.12 (April 24, 2022)
|
||||
- `BUGFIX` Fixes parser breaking for UTF-8 BOM-encoded files. ([issue #7](https://github.com/metayeti/mINI/issues/17))
|
||||
|
||||
## 0.9.11 (October 6, 2021)
|
||||
- `BUGFIX` Fixes various compiler warnings.
|
||||
|
||||
## 0.9.10 (March 4, 2021)
|
||||
- `BUGFIX` Change delimiter constants to `const char* const` to prevent unnecessary allocations. ([issue #5](https://github.com/metayeti/mINI/issues/5))
|
||||
|
||||
## 0.9.9 (February 22, 2021)
|
||||
- `BUGFIX` Adds missing cctype header. ([pull #4](https://github.com/metayeti/mINI/pull/4))
|
||||
|
||||
## 0.9.8 (February 14, 2021)
|
||||
- `BUGFIX` Avoid C4244 warning. ([pull #2](https://github.com/metayeti/mINI/pull/2))
|
||||
|
||||
## 0.9.7 (August 14, 2018)
|
||||
- `FEATURE` Adds case sensitivity toggle via a macro definition.
|
||||
|
||||
## 0.9.6 (May 30, 2018)
|
||||
- `BUGFIX` Changed how files are written / generated. Proper line endings are selected depending on the system.
|
||||
- `FEATURE` Support UTF-8 encoding.
|
||||
|
||||
## 0.9.5 (May 28, 2018)
|
||||
- `BUGFIX` Fixes a bug where writer would skip escaped `=` sequences for new sections.
|
||||
|
||||
## 0.9.4 (May 28, 2018)
|
||||
- `BUGFIX / FEATURE` Equals (`=`) characters within key names are now allowed. When writing or generating a file, key values containing the `=` characters will be escaped with the `\=` sequence. Upon reading the file back, the escape sequences will again be converted back to `=`. Values do not use escape sequences and may contain `=` characters.
|
||||
- `BUGFIX` Square bracket characters (`[` and `]`) are now valid within section names.
|
||||
- `BUGFIX` Trailing comments on section lines are now parsed properly. Fixes a bug where a trailing comment containing the `]` character would break the parser.
|
||||
- `BUGFIX` Values being written or generated are now stripped of leading and trailing whitespace to conform to the specified format.
|
||||
|
||||
## 0.9.3 (May 24, 2018)
|
||||
- `BUGFIX` Fixes inconsistent behavior with empty section and key names where read would ignore empty names and write would allow them.
|
||||
- `FEATURE` Empty key and section names are now allowed.
|
||||
|
||||
## 0.9.2 (May 24, 2018)
|
||||
- `BUGFIX` Fixes the multiple definition bug [issue #1](/../../issues/1)
|
||||
- `BUGFIX` Fixes a bug where a `write()` call to an empty file would begin writing at line 2 instead of 1 due to a reader bug.
|
||||
|
||||
## 0.9.1 (May 20, 2018)
|
||||
- `BUGFIX` Fixed a bug where the writer would skip writing new keys and values following an empty section.
|
||||
|
||||
## 0.9.0 (May 20, 2018)
|
||||
- Release v0.9.0
|
||||
14
external/mINI/CMakeLists.txt
vendored
Normal file
14
external/mINI/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(mINI CXX)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
# Check GCC version and add -lstdc++fs if necessary
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
|
||||
if (GCC_VERSION VERSION_LESS 9.1)
|
||||
link_libraries("-lstdc++fs")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(mINI INTERFACE)
|
||||
target_include_directories(mINI INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
19
external/mINI/LICENSE
vendored
Normal file
19
external/mINI/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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.
|
||||
333
external/mINI/README.md
vendored
Normal file
333
external/mINI/README.md
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
# mINI <img align="left" src="icon.png?raw=true" height="96">
|
||||
|
||||
v0.9.18
|
||||
|
||||
## Info
|
||||
|
||||
This is a tiny, header only C++ library for manipulating INI files.
|
||||
|
||||
It conforms to the following format:
|
||||
- section and key names are case insensitive by default
|
||||
- whitespace around sections, keys and values is ignored
|
||||
- empty section and key names are allowed
|
||||
- keys that do not belong to a section are ignored
|
||||
- comments are lines where the first non-whitespace character is a semicolon (`;`)
|
||||
- trailing comments are allowed on section lines, but not key/value lines
|
||||
- every entry exists on a single line and multiline is not supported
|
||||
|
||||
|
||||
```INI
|
||||
; comment
|
||||
[section]
|
||||
key = value
|
||||
```
|
||||
|
||||
|
||||
Files are read on demand in one go, after which the data is kept in memory and is ready to be manipulated. Files are closed after read or write operations. This utility supports lazy writing, which only writes changes and updates and preserves custom formatting and comments. A lazy write invoked by a `write()` call will read the output file, find which 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 and write operations. Iterating through data will take the same order as the original file or the order in which keys were added to the structure.
|
||||
|
||||
This library operates with the `std::string` type to hold values and relies on your host environment for encoding. It should play nicely with UTF-8 but your mileage may vary.
|
||||
|
||||
## Installation
|
||||
|
||||
This is a header-only library. To install it, just copy everything in `/src/` into your own project's source code folder, or use a custom location and just make sure your compiler sees the additional include directory. Then include the file somewhere in your code:
|
||||
|
||||
```C++
|
||||
#include "mini/ini.h"
|
||||
```
|
||||
|
||||
You're good to go!
|
||||
|
||||
## Basic examples
|
||||
|
||||
### Reading / writing
|
||||
|
||||
Start with an INI file named `myfile.ini`:
|
||||
```INI
|
||||
; amounts of fruits
|
||||
[fruits]
|
||||
apples=20
|
||||
oranges=30
|
||||
```
|
||||
|
||||
Our code:
|
||||
```C++
|
||||
// first, create a file instance
|
||||
mINI::INIFile file("myfile.ini");
|
||||
|
||||
// next, create a structure that will hold data
|
||||
mINI::INIStructure ini;
|
||||
|
||||
// now we can read the file
|
||||
file.read(ini);
|
||||
|
||||
// read a value
|
||||
std::string& amountOfApples = ini["fruits"]["apples"];
|
||||
|
||||
// update a value
|
||||
ini["fruits"]["oranges"] = "50";
|
||||
|
||||
// add a new entry
|
||||
ini["fruits"]["bananas"] = "100";
|
||||
|
||||
// write updates to file
|
||||
file.write(ini);
|
||||
```
|
||||
|
||||
After running the code, our INI file now looks like this:
|
||||
```INI
|
||||
; amounts of fruits
|
||||
[fruits]
|
||||
apples=20
|
||||
oranges=50
|
||||
bananas=100
|
||||
```
|
||||
|
||||
### Generating a file
|
||||
|
||||
```C++
|
||||
// create a file instance
|
||||
mINI::INIFile file("myfile.ini");
|
||||
|
||||
// create a data structure
|
||||
mINI::INIStructure ini;
|
||||
|
||||
// populate the structure
|
||||
ini["things"]["chairs"] = "20";
|
||||
ini["things"]["balloons"] = "100";
|
||||
|
||||
// generate an INI file (overwrites any previous file)
|
||||
file.generate(ini);
|
||||
```
|
||||
|
||||
## Manipulating files
|
||||
|
||||
The `INIFile` class holds the filename and exposes functions for reading, writing and generating INI files. It does not keep the file open but merely provides an abstraction you can use to access physical files.
|
||||
|
||||
To create a file instance:
|
||||
```C++
|
||||
mINI::INIFile file("myfile.ini");
|
||||
```
|
||||
|
||||
You will also need a structure you can operate on:
|
||||
```C++
|
||||
mINI::INIStructure ini;
|
||||
```
|
||||
|
||||
To read from a file:
|
||||
```C++
|
||||
bool readSuccess = file.read(ini);
|
||||
```
|
||||
|
||||
To write back to a file while preserving comments and custom formatting:
|
||||
```C++
|
||||
bool writeSuccess = file.write(ini);
|
||||
```
|
||||
|
||||
You can set the second parameter to `write()` to `true` if you want the file to be written with pretty-print. Pretty-print adds spaces between key-value pairs and blank lines between sections in the output file:
|
||||
```C++
|
||||
bool writeSuccess = file.write(ini, true);
|
||||
```
|
||||
|
||||
A `write()` call will attempt to preserve any custom formatting the original INI file uses and will only use pretty-print for creation of new keys and sections.
|
||||
|
||||
To generate a file:
|
||||
```C++
|
||||
file.generate(ini);
|
||||
```
|
||||
|
||||
Note that `generate()` will overwrite any custom formatting and comments from the original file!
|
||||
|
||||
You can use pretty-print with `generate()` as well:
|
||||
```C++
|
||||
file.generate(ini, true);
|
||||
```
|
||||
|
||||
Example output for a generated INI file *without* pretty-print:
|
||||
```INI
|
||||
[section1]
|
||||
key1=value1
|
||||
key2=value2
|
||||
[section2]
|
||||
key1=value1
|
||||
```
|
||||
|
||||
Example output for a generated INI file *with* pretty-print:
|
||||
```INI
|
||||
[section1]
|
||||
key1 = value1
|
||||
key2 = value2
|
||||
|
||||
[section2]
|
||||
key1 = value1
|
||||
```
|
||||
|
||||
## Manipulating data
|
||||
|
||||
### Reading data
|
||||
|
||||
There are two ways to read data from the INI structure. You can either use the `[]` operator or the `get()` function:
|
||||
|
||||
```C++
|
||||
// read value - if key or section don't exist, they will be created
|
||||
// returns reference to real value
|
||||
std::string& value = ini["section"]["key"];
|
||||
|
||||
// read value safely - if key or section don't exist they will NOT be created
|
||||
// returns a copy
|
||||
std::string value = ini.get("section").get("key");
|
||||
```
|
||||
|
||||
The difference between `[]` and `get()` operations is that `[]` returns a reference to **real** data (that you may modify) and creates a new item automatically if one does not already exist, whereas `get()` returns a **copy** of the data and doesn't create new items in the structure. Use `has()` before doing any operations with `[]` if you wish to avoid altering the structure.
|
||||
|
||||
You may combine usage of `[]` and `get()`.
|
||||
|
||||
Section and key names are case insensitive and are stripped of leading and trailing whitespace. `ini["section"]` is the same as `ini["SECTION"]` is the same as `ini[" sEcTiOn "]` and so on, and same for keys. Generated files always use lower case for section and key names. Writing to an existing file will preserve letter cases of the original file whenever those keys or sections already exists.
|
||||
|
||||
### Updating data
|
||||
|
||||
To set or update a value:
|
||||
```C++
|
||||
ini["section"]["key"] = "value";
|
||||
```
|
||||
|
||||
Note that when writing to a file, values will be stripped of leading and trailing whitespace . For example, the following value will be converted to just `"c"` when reading back from a file: `ini["a"]["b"] = " c ";`
|
||||
|
||||
You can set multiple values at once by using `set()`:
|
||||
```C++
|
||||
ini["section"].set({
|
||||
{"key1", "value1"},
|
||||
{"key2", "value2"}
|
||||
});
|
||||
```
|
||||
|
||||
To create an empty section, simply do:
|
||||
```C++
|
||||
ini["section"];
|
||||
```
|
||||
|
||||
Similarly, to create an empty key:
|
||||
```C++
|
||||
ini["section"]["key"];
|
||||
```
|
||||
|
||||
To remove a single key from a section:
|
||||
```C++
|
||||
bool removeSuccess = ini["section"].remove("key");
|
||||
```
|
||||
|
||||
To remove a section:
|
||||
```C++
|
||||
bool removeSuccess = ini.remove("section");
|
||||
```
|
||||
|
||||
To remove all keys from a section:
|
||||
```C++
|
||||
ini["section"].clear();
|
||||
```
|
||||
|
||||
To remove all data in structure:
|
||||
```C++
|
||||
ini.clear();
|
||||
```
|
||||
|
||||
### Other functions
|
||||
|
||||
To check if a section is present:
|
||||
```C++
|
||||
bool hasSection = ini.has("section");
|
||||
```
|
||||
|
||||
To check if a key within a section is present:
|
||||
```C++
|
||||
bool hasKey = ini["section"].has("key");
|
||||
```
|
||||
|
||||
To get the number of keys in a section:
|
||||
```C++
|
||||
size_t n_keys = ini["section"].size();
|
||||
```
|
||||
|
||||
To get the number of sections in the structure:
|
||||
```C++
|
||||
size_t n_sections = ini.size();
|
||||
```
|
||||
|
||||
### Nitty-gritty
|
||||
|
||||
Keep in mind that `[]` will always create a new item if the item does not already exist! You can use `has()` to check if an item exists before performing further operations. Remember that `get()` will return a copy of data, so you should **not** be doing removes or updates to data with it!
|
||||
|
||||
Usage of the `[]` operator shouldn't be a problem in most real-world cases where you're doing lookups on known keys and you may not care if empty keys or sections get created. However - if you have a situation where you do not want new items to be added to the structure, either use `get()` to retreive items, or if you don't want to be working with copies of data, use `has()` before using the `[]` operator if you want to be on the safe side.
|
||||
|
||||
Short example that demonstrates safe manipulation of data:
|
||||
```C++
|
||||
if (ini.has("section"))
|
||||
{
|
||||
// we have section, we can access it safely without creating a new one
|
||||
auto& collection = ini["section"];
|
||||
if (collection.has("key"))
|
||||
{
|
||||
// we have key, we can access it safely without creating a new one
|
||||
auto& value = collection["key"];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Iteration
|
||||
|
||||
You can traverse the structure in order of insertion. The following example loops through the structure and displays results in a familiar format:
|
||||
```C++
|
||||
for (auto const& it : ini)
|
||||
{
|
||||
auto const& section = it.first;
|
||||
auto const& collection = it.second;
|
||||
std::cout << "[" << section << "]" << std::endl;
|
||||
for (auto const& it2 : collection)
|
||||
{
|
||||
auto const& key = it2.first;
|
||||
auto const& value = it2.second;
|
||||
std::cout << key << "=" << value << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`it.first` is always `std::string` type.
|
||||
|
||||
`it.second` is an object which is either a `mINI::INIMap` type on the first level or `std::string` type on the second level.
|
||||
|
||||
The API only exposes a `const_iterator`, so you can't use iterators to manipulate data directly. You can however access the structure as normal while iterating:
|
||||
|
||||
```C++
|
||||
// change all values in the structure to "banana"
|
||||
for (auto const& it : ini)
|
||||
{
|
||||
auto const& section = it.first;
|
||||
auto const& collection = it.second;
|
||||
for (auto const& it2 : collection)
|
||||
{
|
||||
auto const& key = it2.first;
|
||||
ini[section][key] = "banana"; // O(1) because hashmaps
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Case sensitivity
|
||||
|
||||
If you wish to make the library not ignore letter case, add the directive `#define MINI_CASE_SENSITIVE` **before** including the library:
|
||||
```C++
|
||||
#define MINI_CASE_SENSITIVE
|
||||
#include "mini/ini.h"
|
||||
```
|
||||
|
||||
This will affect reading and writing from files and access to the structure.
|
||||
|
||||
## Thanks
|
||||
|
||||
- [lest](https://github.com/martinmoene/lest) - testing framework
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2018 Danijel Durakovic
|
||||
|
||||
Licensed under the terms of the [MIT license](LICENSE)
|
||||
BIN
external/mINI/icon.png
vendored
Normal file
BIN
external/mINI/icon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
780
external/mINI/src/mini/ini.h
vendored
Normal file
780
external/mINI/src/mini/ini.h
vendored
Normal file
@@ -0,0 +1,780 @@
|
||||
/*
|
||||
* 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.18
|
||||
// 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 <cctype>
|
||||
#include <filesystem>
|
||||
|
||||
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<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;
|
||||
|
||||
std::size_t setEmpty(std::string& key)
|
||||
{
|
||||
const 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() = default;
|
||||
|
||||
INIMap(INIMap const& other) : dataIndexMap(other.dataIndexMap), data(other.data)
|
||||
{
|
||||
}
|
||||
|
||||
T& operator[](std::string key)
|
||||
{
|
||||
INIStringUtil::trim(key);
|
||||
#ifndef MINI_CASE_SENSITIVE
|
||||
INIStringUtil::toLower(key);
|
||||
#endif
|
||||
auto it = dataIndexMap.find(key);
|
||||
const bool hasIt = (it != dataIndexMap.end());
|
||||
const std::size_t index = (hasIt) ? it->second : setEmpty(key);
|
||||
return data[index].second;
|
||||
}
|
||||
[[nodiscard]] 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);
|
||||
}
|
||||
[[nodiscard]] 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();
|
||||
}
|
||||
[[nodiscard]] std::size_t size() const
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
[[nodiscard]] const_iterator begin() const { return data.begin(); }
|
||||
[[nodiscard]] 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;
|
||||
}
|
||||
const 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>;
|
||||
|
||||
bool isBOM = false;
|
||||
|
||||
private:
|
||||
std::ifstream fileReadStream;
|
||||
T_LineDataPtr lineData;
|
||||
|
||||
T_LineData readFile()
|
||||
{
|
||||
fileReadStream.seekg(0, std::ios::end);
|
||||
const std::size_t fileSize = static_cast<std::size_t>(fileReadStream.tellg());
|
||||
fileReadStream.seekg(0, std::ios::beg);
|
||||
if (fileSize >= 3) {
|
||||
const char header[3] = {
|
||||
static_cast<char>(fileReadStream.get()),
|
||||
static_cast<char>(fileReadStream.get()),
|
||||
static_cast<char>(fileReadStream.get())
|
||||
};
|
||||
isBOM = (
|
||||
header[0] == static_cast<char>(0xEF) &&
|
||||
header[1] == static_cast<char>(0xBB) &&
|
||||
header[2] == static_cast<char>(0xBF)
|
||||
);
|
||||
}
|
||||
else {
|
||||
isBOM = false;
|
||||
}
|
||||
std::string fileContents;
|
||||
fileContents.resize(fileSize);
|
||||
fileReadStream.seekg(isBOM ? 3 : 0, std::ios::beg);
|
||||
fileReadStream.read(fileContents.data(), 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)
|
||||
{
|
||||
const 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::filesystem::path const& filename, bool keepLineData = false)
|
||||
{
|
||||
fileReadStream.open(filename, std::ios::in | std::ios::binary);
|
||||
if (keepLineData)
|
||||
{
|
||||
lineData = std::make_shared<T_LineData>();
|
||||
}
|
||||
}
|
||||
~INIReader() = default;
|
||||
|
||||
bool operator>>(INIStructure& data)
|
||||
{
|
||||
if (!fileReadStream.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const 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::filesystem::path const& filename)
|
||||
{
|
||||
fileWriteStream.open(filename, std::ios::out | std::ios::binary);
|
||||
}
|
||||
~INIGenerator() = default;
|
||||
|
||||
bool operator<<(INIStructure const& data)
|
||||
{
|
||||
if (!fileWriteStream.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (data.size() == 0U)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto it = data.begin();
|
||||
for (;;)
|
||||
{
|
||||
auto const& section = it->first;
|
||||
auto const& collection = it->second;
|
||||
fileWriteStream
|
||||
<< "["
|
||||
<< section
|
||||
<< "]";
|
||||
if (collection.size() != 0U)
|
||||
{
|
||||
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::filesystem::path filename;
|
||||
|
||||
T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original) const
|
||||
{
|
||||
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.empty() && !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::filesystem::path filename)
|
||||
: filename(std::move(filename))
|
||||
{
|
||||
}
|
||||
~INIWriter() = default;
|
||||
|
||||
bool operator<<(INIStructure& data)
|
||||
{
|
||||
if (!std::filesystem::exists(filename))
|
||||
{
|
||||
INIGenerator generator(filename);
|
||||
generator.prettyPrint = prettyPrint;
|
||||
return generator << data;
|
||||
}
|
||||
INIStructure originalData;
|
||||
T_LineDataPtr lineData;
|
||||
bool readSuccess = false;
|
||||
bool fileIsBOM = false;
|
||||
{
|
||||
INIReader reader(filename, true);
|
||||
readSuccess = reader >> originalData;
|
||||
if (readSuccess)
|
||||
{
|
||||
lineData = reader.getLines();
|
||||
fileIsBOM = reader.isBOM;
|
||||
}
|
||||
}
|
||||
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 (fileIsBOM) {
|
||||
const char utf8_BOM[3] = {
|
||||
static_cast<char>(0xEF),
|
||||
static_cast<char>(0xBB),
|
||||
static_cast<char>(0xBF)
|
||||
};
|
||||
fileWriteStream.write(utf8_BOM, 3);
|
||||
}
|
||||
if (!output.empty())
|
||||
{
|
||||
auto line = output.begin();
|
||||
for (;;)
|
||||
{
|
||||
fileWriteStream << *line;
|
||||
if (++line == output.end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
fileWriteStream << INIStringUtil::endl;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class INIFile
|
||||
{
|
||||
private:
|
||||
std::filesystem::path filename;
|
||||
|
||||
public:
|
||||
INIFile(std::filesystem::path filename)
|
||||
: filename(std::move(filename))
|
||||
{ }
|
||||
|
||||
~INIFile() = default;
|
||||
|
||||
bool read(INIStructure& data) const
|
||||
{
|
||||
if (data.size() != 0U)
|
||||
{
|
||||
data.clear();
|
||||
}
|
||||
if (filename.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
INIReader reader(filename);
|
||||
return reader >> data;
|
||||
}
|
||||
[[nodiscard]] 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_
|
||||
Reference in New Issue
Block a user