/* * MIT License * * Copyright (c) 2023 dujingning * * 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. */ #ifndef __JN_INICPP_H__ #define __JN_INICPP_H__ #include #include #include #include #include #include #include #include #ifdef _ENBABLE_INICPP_STD_WSTRING_ // Not all of C++ 11 support // for std::string <==> std::wstring convert #include #include #endif #ifdef INICPP_DEBUG #include #include #include class TimeFormatter { public: static std::string format(const std::string &format = "%Y-%m-%d %H:%M:%S") { std::time_t t = std::time(nullptr); std::tm tm = *std::localtime(&t); std::array buffer; std::strftime(buffer.data(), buffer.size(), format.c_str(), &tm); return buffer.data(); } }; #define CODE_INFO std::string(" \t``````|") + std::string(__FILE__) + ":" + std::to_string(__LINE__) + " fun:" + std::string(__FUNCTION__) #define INI_DEBUG(x) std::cout << "INICPP " << TimeFormatter::format() << " : " << x << CODE_INFO << std::endl #else // #ifdef INICPP_DEBUG #define INI_DEBUG(x) #endif // #ifdef INICPP_DEBUG namespace inicpp { typedef struct ValueNode { std::string Value = ""; int lineNumber = -1; // text line start with 1 } ValueNode; class parentHelper { public: virtual parentHelper *parent() { INI_DEBUG("called parentHelper virtual impl: need to impl parent"); return nullptr; } virtual void setParent(parentHelper *parent) { INI_DEBUG("called parentHelper virtual impl: need to impl setParent"); } virtual bool set(const std::string &Section, const std::string &Key, const std::string &Value, const std::string &comment = "") { INI_DEBUG("called parentHelper virtual impl: need to impl set"); return true; } }; class ValueProxy { public: ValueProxy(std::string &value) : _value(value) {} ~ValueProxy() {} template ValueProxy(const T &value) : _value(to_string(value)) {} template static std::string to_string(const T &value) { std::ostringstream oss; oss << value; return oss.str(); } template T get() const { static_assert(!std::is_pointer::value, "Pointer types are not supported for conversion."); std::istringstream iss(_value); T result; if (!(iss >> result)) { throw std::runtime_error("Type mismatch or invalid conversion. with(section-key-value): " +_sectionName +"-"+ _keyName +"-"+ _value); // error notify } return result; } operator char() const { return this->get(); } operator short() const { return this->get(); } operator int() const { return this->get(); } operator long() const { return this->get(); } operator long long() const { return this->get(); } operator float() const { return this->get(); } operator double() const { return this->get(); } operator unsigned char() const { return this->get(); } operator unsigned short() const { return this->get(); } operator unsigned int() const { return this->get(); } operator unsigned long() const { return this->get(); } operator unsigned long long() const { return this->get(); } // false:'0' or 'false', true : others operator bool() const { if (_value == "0" || _value == "false" || _value == "no") { return false; } return true; } operator std::string() const { return _value; } friend std::ostream &operator<<(std::ostream &os, const ValueProxy &proxy) { os << proxy._value; return os; } // assignment template ValueProxy &operator=(const T &other) { std::string value = this->to_string(other); if (_value != value) { set(value); } _value = value; return *this; } ValueProxy &operator=(const std::string &other) { if (_value != other) { INI_DEBUG("Value Proxy Wanna Set Value: " << other); set(other); } _value = other; return *this; } // specify std::string const std::string &String() noexcept { return _value; } inline void setWriteCB(parentHelper *sectionObj, const std::string §ionName, const std::string &keyName) { _section = sectionObj; _sectionName = sectionName; _keyName = keyName; } private: void set(const std::string &value) { if (value.empty() || _keyName.empty()) { return; } if (_section && _section->parent() && _section->parent()->parent()) { _section->parent()->parent()->set(_sectionName, _keyName, value); } } private: std::string &_value; std::string _sectionName, _keyName; parentHelper *_section = nullptr; }; } // namespace inicpp namespace inicpp { class section : parentHelper { public: section() : _sectionName() { } explicit section(const std::string §ionName) : _sectionName(sectionName) { } const std::string &name() { return _sectionName; } const std::string getValue(const std::string &Key) { if (!_sectionMap.count(Key)) { return ""; } return _sectionMap[Key].Value; } void setName(const std::string &name, const int &lineNumber) { _sectionName = name; _lineNumber = lineNumber; } void setValue(const std::string &Key, const std::string &Value, const int line) { _sectionMap[Key].Value = Value; _sectionMap[Key].lineNumber = line; } void append(section &sec) { _sectionMap.insert(sec._sectionMap.begin(), sec._sectionMap.end()); } bool isKeyExist(const std::string &Key) { return !_sectionMap.count(Key) ? false : true; } int getEndSection() { int line = -1; if (_sectionMap.empty() && _sectionName != "") { return _lineNumber; } for (const auto &data : _sectionMap) { if (data.second.lineNumber > line) { line = data.second.lineNumber; } } return line; } int getLine(const std::string &Key) { if (!_sectionMap.count(Key)) { return -1; } return _sectionMap[Key].lineNumber; } void clear() { _lineNumber = -1; _sectionName.clear(); _sectionMap.clear(); } bool isEmpty() const { return _sectionMap.empty(); } int toInt(const std::string &Key) noexcept { if (!_sectionMap.count(Key)) { return 0; } int result = 0; try { result = std::stoi(_sectionMap[Key].Value); } catch (const std::invalid_argument &e) { INI_DEBUG("Invalid argument: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); } catch (const std::out_of_range &e) { INI_DEBUG("Out of range: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); } return result; } std::string toString(const std::string &Key) noexcept { if (!_sectionMap.count(Key)) { return ""; } return _sectionMap[Key].Value; } #ifdef _ENBABLE_INICPP_STD_WSTRING_ std::wstring toWString(const std::string &Key) { std::wstring_convert> converter; return converter.from_bytes(toString(Key)); } #endif double toDouble(const std::string &Key) noexcept { if (!_sectionMap.count(Key)) { return 0.0; } double result = 0.0; try { result = std::stod(_sectionMap[Key].Value); } catch (const std::invalid_argument &e) { INI_DEBUG("Invalid argument: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); } catch (const std::out_of_range &e) { INI_DEBUG("Out of range: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); } return result; } std::map getSectionMap() { std::map sectionKVMap; for (auto &iter : _sectionMap) { sectionKVMap[iter.first] = iter.second.Value; } return sectionKVMap; } // Automatically converts to any type; throws std::runtime_error if not found or conversion fails ValueProxy operator[](const std::string &Key) { ValueProxy vp(_sectionMap[Key].Value); vp.setWriteCB(this, _sectionName, Key); return vp; } inline parentHelper *parent() override { return _parent; }; inline void setParent(parentHelper *parent) override { _parent = parent; }; private: std::string _sectionName; std::map _sectionMap; int _lineNumber = -1; // text line start with 1 parentHelper *_parent = nullptr; }; class ini : parentHelper { public: void addSection(section &sec) { if (_iniInfoMap.count(sec.name())) // if exist,need to merge { _iniInfoMap[sec.name()].append(sec); return; } _iniInfoMap.emplace(sec.name(), sec); return; } void removeSection(const std::string §ionName) { if (!_iniInfoMap.count(sectionName)) { return; } _iniInfoMap.erase(sectionName); return; } bool isSectionExists(const std::string §ionName) { return !_iniInfoMap.count(sectionName) ? false : true; } // may contains default of Unnamed section with "" std::list getSectionsList() { std::list sectionList; for (const auto &data : _iniInfoMap) { if (data.first == "" && data.second.isEmpty()) // default section: no section name,if empty,not count it. { continue; } sectionList.emplace_back(data.first); } return sectionList; } std::map getSectionMap(const std::string §ionName) { std::map kvMap; if (_iniInfoMap.count(sectionName) == 0) { return kvMap; } return _iniInfoMap[sectionName].getSectionMap(); } const section &operator[](const std::string §ionName) { _iniInfoMap[sectionName].setParent(this); if (_iniInfoMap[sectionName].name().empty()) { _iniInfoMap[sectionName].setName(sectionName, -1); } return _iniInfoMap[sectionName]; } inline std::size_t getSectionSize() { return _iniInfoMap.size(); } std::string getValue(const std::string §ionName, const std::string &Key) { if (!_iniInfoMap.count(sectionName)) { return ""; } return _iniInfoMap[sectionName][Key]; } // for none section int getLine(const std::string &Key) { if (!_iniInfoMap.count("")) { return -1; } return _iniInfoMap[""].getLine(Key); } // for section-key int getLine(const std::string §ionName, const std::string &Key) { if (!_iniInfoMap.count(sectionName)) { return -1; } return _iniInfoMap[sectionName].getLine(Key); } inline void clear() { _iniInfoMap.clear(); } inline bool empty() { return _iniInfoMap.empty(); } parentHelper *parent() override { return _parent; } void setParent(parentHelper *parent) override { _parent = parent; } protected: std::map _iniInfoMap; private: parentHelper *_parent = nullptr; }; class IniManager : parentHelper { public: #ifdef _ENBABLE_INICPP_STD_WSTRING_ explicit IniManager(const std::wstring &configFileName = L"") { std::wstring_convert> converter; std::string stringValue = converter.to_bytes(configFileName); _configFileName = stringValue; _iniData.setParent(this); parse(); } #else explicit IniManager(const std::string &configFileName = "") : _configFileName(configFileName) { _iniData.setParent(this); parse(); } #endif ~IniManager() { _iniFile.close(); } section operator[](const std::string §ionName) { return _iniData[sectionName]; } void parse() { if (_configFileName.empty()) { return; } if (!_iniFile.is_open()) { _iniFile.clear(); _iniFile.open(_configFileName, std::ifstream::in | std::ifstream::out | std::fstream::app); } if (!_iniFile.is_open()) { INI_DEBUG("Failed to open(WR),try to open with readonly(R)."); _iniFile.clear(); _iniFile.open(_configFileName, std::ifstream::in); } if (!_iniFile.is_open()) { INI_DEBUG("Failed to open the input INI file for parsing! file:" << _configFileName); return; } _iniData.clear(); _iniFile.seekg(0, _iniFile.beg); std::string data, sectionName; int sectionLine = -1; section sectionRecord; _SumOfLines = 1; do { std::getline(_iniFile, data); if (!filterData(data)) { ++_SumOfLines; continue; } if (data.find('[') == 0) // section { if (!sectionRecord.isEmpty() || sectionRecord.name() != "") { _iniData.addSection(sectionRecord); } size_t first = data.find('['); size_t last = data.find(']'); if (last == std::string::npos) { ++_SumOfLines; continue; } sectionName = data.substr(first + 1, last - first - 1); sectionLine = _SumOfLines; sectionRecord.clear(); sectionRecord.setName(sectionName, sectionLine); } size_t pos = data.find('='); if (pos != std::string::npos) { // k=v std::string key = data.substr(0, pos); std::string value = data.substr(pos + 1); trimEdges(key); trimEdges(value); sectionRecord.setValue(key, value, _SumOfLines); } ++_SumOfLines; } while (!_iniFile.eof()); if (!sectionRecord.isEmpty()) { sectionRecord.setName(sectionName, -1); _iniData.addSection(sectionRecord); } if (_iniFile.is_open()) { _iniFile.close(); } } bool set(const std::string &Section, const std::string &Key, const std::string &Value, const std::string &comment = "") override { parse(); std::string key = Key, value = Value; trimEdges(key); trimEdges(key); if (key == "" || value == "") { INI_DEBUG("Invalid parameter input: key[" << key << "],value[" << value << "]"); return false; } std::string keyValueData = key + "=" + value + "\n"; if (comment.length() > 0) { keyValueData = comment + "\n" + keyValueData; if (comment[0] != ';') { keyValueData = ";" + keyValueData; } } const std::string &tempFile = ".temp.ini"; std::fstream input(_configFileName, std::ifstream::in); std::ofstream output(tempFile); if (!input.is_open()) { INI_DEBUG("Failed to open the input INI file for modification! File name:" << _configFileName); return false; } if (!output.is_open()) { INI_DEBUG("Failed to open the output INI file for modification!"); return false; } int line_number_mark = -1; bool isInputDataWited = false; do { // exist key at one section replace it, or need to create it if (_iniData.isSectionExists(Section)) { line_number_mark = (*this)[Section].getLine(key); if (line_number_mark == -1) { // section exist, key not exist line_number_mark = (*this)[Section].getEndSection(); std::string lineData; int input_line_number = 0; while (std::getline(input, lineData)) { ++input_line_number; if (input_line_number == (line_number_mark + 1)) { // new line,append to next line isInputDataWited = true; output << keyValueData; } output << lineData << "\n"; } if (input.eof() && !isInputDataWited) { isInputDataWited = true; output << keyValueData; } break; } } if (line_number_mark <= 0) // not found key at config file { input.seekg(0, input.beg); bool isHoldSection = false; std::string newLine = "\n\n"; if (Section != "" && Section.find("[") == std::string::npos && Section.find("]") == std::string::npos && Section.find("=") == std::string::npos) { if (_iniData.empty() || _iniData.getSectionSize() <= 0) { newLine.clear(); } isHoldSection = true; } // 1.section is exist or empty section if (_iniData.isSectionExists(Section) || Section == "") { // write key/value to head if (isHoldSection) { output << newLine << "[" << Section << "]" << "\n"; } output << keyValueData; // write others std::string lineData; while (std::getline(input, lineData)) { output << lineData << "\n"; } } // 2.section is not exist else { // write others std::string lineData; while (std::getline(input, lineData)) { output << lineData << "\n"; } // write key/value to end if (isHoldSection) { output << newLine << "[" << Section << "]" << "\n"; } output << keyValueData; } break; } else { // found, replace it std::string lineData; int input_line_number = 0; while (std::getline(input, lineData)) { ++input_line_number; // delete old comment if new comment is set if (input_line_number == (line_number_mark - 1) && lineData.length() > 0 && lineData[0] == ';' && comment != "") { continue; } if (input_line_number == line_number_mark) { // replace to this line output << keyValueData; } else { output << lineData << "\n"; } } break; } INI_DEBUG("error! inicpp lost process of set function"); return false; } while (false); // clear work input.close(); output.close(); std::remove(_configFileName.c_str()); std::rename(tempFile.c_str(), _configFileName.c_str()); // reload parse(); return true; } bool set(const std::string &Section, const std::string &Key, const int Value, const std::string &comment = "") { std::string stringValue = std::to_string(Value); return set(Section, Key, stringValue, comment); } bool set(const std::string &Section, const std::string &Key, const double &Value, const std::string &comment = "") { std::string stringValue = std::to_string(Value); return set(Section, Key, stringValue, comment); } bool set(const std::string &Section, const std::string &Key, const char &Value, const std::string &comment = "") { std::string stringValue = ValueProxy::to_string(Value); return set(Section, Key, stringValue, comment); } // no sections: head of config file bool set(const std::string &Key, const std::string &Value) { return set("", Key, Value, ""); } bool set(const std::string &Key, const char *Value) { return set("", Key, Value, ""); } template bool set(const std::string &Key, const T &Value) { std::string stringValue = std::to_string(Value); return set("", Key, stringValue, ""); } #ifdef _ENBABLE_INICPP_STD_WSTRING_ bool set(const std::string &Section, const std::string &Key, const std::wstring &Value, const std::string &comment = "") { std::wstring_convert> converter; std::string stringValue = converter.to_bytes(Value); return set(Section, Key, stringValue, comment); } #endif // comment for section name of key bool setComment(const std::string &Section, const std::string &Key, const std::string &comment) { return set(Section, Key, (*this)[Section].toString(Key), comment); } // comment for no section name of key bool setComment(const std::string &Key, const std::string &comment) { return set("", Key, (*this)[""].toString(Key), comment); } bool isSectionExists(const std::string §ionName) { return _iniData.isSectionExists(sectionName); } inline std::list sectionsList() { return _iniData.getSectionsList(); } inline std::map sectionMap(const std::string §ionName) { return _iniData.getSectionMap(sectionName); } #ifdef _ENBABLE_INICPP_STD_WSTRING_ void setFileName(const std::wstring &fileName) { std::wstring_convert> converter; std::string name = converter.to_bytes(fileName); _configFileName = name; } #else void setFileName(const std::string &fileName) { _configFileName = fileName; } #endif private: bool filterData(std::string &data) { if (data.length() == 0) { return false; } if (data[0] == ';') { return false; } if (data[0] == '#') { return false; } return true; } void trimEdges(std::string &data) { // remove left ' ' and '\t' data.erase(data.begin(), std::find_if(data.begin(), data.end(), [](unsigned char c) { return !std::isspace(c); })); // remove right ' ' and '\t' data.erase(std::find_if(data.rbegin(), data.rend(), [](unsigned char c) { return !std::isspace(c); }) .base(), data.end()); // INI_DEBUG("trimEdges data:|" << data << "|"); } private: ini _iniData; int _SumOfLines; std::fstream _iniFile; std::string _configFileName; }; } // namespace inicpp #endif