From 8bf73dcf5983bd979a8c75b3176a853ff6033a73 Mon Sep 17 00:00:00 2001 From: iamAbhishekkumar Date: Mon, 14 Oct 2024 22:36:07 +0530 Subject: [PATCH] feat : ini file creartion and parser --- include/gitRepo.hpp | 5 +- include/iniHandler.hpp | 654 +++++++++++++++++++++++++++++++++++++++++ include/utils.hpp | 5 +- src/argParser.cpp | 2 +- src/gitRepo.cpp | 2 +- src/gitexceptions.cpp | 2 +- src/main.cpp | 9 +- src/utils.cpp | 15 +- 8 files changed, 682 insertions(+), 12 deletions(-) create mode 100644 include/iniHandler.hpp diff --git a/include/gitRepo.hpp b/include/gitRepo.hpp index 5b7efaa..5dfb046 100644 --- a/include/gitRepo.hpp +++ b/include/gitRepo.hpp @@ -1,10 +1,11 @@ #ifndef GITREPO_H #define GITREPO_H -#include #include -#include #include +#include "contants.hpp" +#include "gitexceptions.hpp" + namespace fs = std::filesystem; class GitRepository { diff --git a/include/iniHandler.hpp b/include/iniHandler.hpp new file mode 100644 index 0000000..ced8875 --- /dev/null +++ b/include/iniHandler.hpp @@ -0,0 +1,654 @@ +/* + * 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.17 +// 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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 +} // namespace INIStringUtil + +template +class INIMap { + private: + using T_DataIndexMap = std::unordered_map; + using T_DataItem = std::pair; + using T_DataContainer = std::vector; + using T_MultiArgs = typename std::vector>; + + T_DataIndexMap dataIndexMap; + T_DataContainer data; + + inline std::size_t setEmpty(std::string& key) { + 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() {} + + 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); + bool hasIt = (it != dataIndexMap.end()); + std::size_t index = (hasIt) ? it->second : setEmpty(key); + return data[index].second; + } + 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); + } + 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(); + } + std::size_t size() const { return data.size(); } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } +}; + +using INIStructure = INIMap>; + +namespace INIParser { +using T_ParseValues = std::pair; + +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; + } + 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; +} +} // namespace INIParser + +class INIReader { + public: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + 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(fileReadStream.tellg()); + fileReadStream.seekg(0, std::ios::beg); + if (fileSize >= 3) { + const char header[3] = {static_cast(fileReadStream.get()), + static_cast(fileReadStream.get()), + static_cast(fileReadStream.get())}; + isBOM = (header[0] == static_cast(0xEF) && + header[1] == static_cast(0xBB) && + header[2] == static_cast(0xBF)); + } else { + isBOM = false; + } + std::string fileContents; + fileContents.resize(fileSize); + fileReadStream.seekg(isBOM ? 3 : 0, std::ios::beg); + fileReadStream.read(&fileContents[0], 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) { + 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::string const& filename, bool keepLineData = false) { + fileReadStream.open(filename, std::ios::in | std::ios::binary); + if (keepLineData) { + lineData = std::make_shared(); + } + } + ~INIReader() {} + + bool operator>>(INIStructure& data) { + if (!fileReadStream.is_open()) { + return false; + } + 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::string const& filename) { + fileWriteStream.open(filename, std::ios::out | std::ios::binary); + } + ~INIGenerator() {} + + bool operator<<(INIStructure const& data) { + if (!fileWriteStream.is_open()) { + return false; + } + if (!data.size()) { + return true; + } + auto it = data.begin(); + for (;;) { + auto const& section = it->first; + auto const& collection = it->second; + fileWriteStream << "[" << section << "]"; + if (collection.size()) { + 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; + using T_LineDataPtr = std::shared_ptr; + + std::string filename; + + T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, + INIStructure& original) { + 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.size() > 0 && !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::string const& filename) : filename(filename) {} + ~INIWriter() {} + + bool operator<<(INIStructure& data) { + struct stat buf; + bool fileExists = (stat(filename.c_str(), &buf) == 0); + if (!fileExists) { + INIGenerator generator(filename); + generator.prettyPrint = prettyPrint; + return generator << data; + } + INIStructure originalData; + T_LineDataPtr lineData; + bool readSuccess = false; + bool fileIsBOM = false; + { + INIReader reader(filename, true); + if ((readSuccess = reader >> originalData)) { + 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(0xEF), + static_cast(0xBB), + static_cast(0xBF)}; + fileWriteStream.write(utf8_BOM, 3); + } + if (output.size()) { + auto line = output.begin(); + for (;;) { + fileWriteStream << *line; + if (++line == output.end()) { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + return false; + } +}; + +class INIFile { + private: + std::string filename; + + public: + INIFile(std::string const& filename) : filename(filename) {} + + ~INIFile() {} + + bool read(INIStructure& data) const { + if (data.size()) { + data.clear(); + } + if (filename.empty()) { + return false; + } + INIReader reader(filename); + return reader >> data; + } + 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; + } +}; +} // namespace mINI + +#endif // MINI_INI_H_ \ No newline at end of file diff --git a/include/utils.hpp b/include/utils.hpp index 1f3d582..993d909 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -1,10 +1,12 @@ #ifndef UTILS_H #define UTILS_H #include -#include #include #include +#include "gitRepo.hpp" +#include "iniHandler.hpp" + class Utils { private: GitRepository &repo; @@ -23,6 +25,7 @@ class Utils { bool create_dir(bool mkdir = false, Paths... paths); void write_to_file(std::string &&path, std::string &&content); + void create_default_configs(); }; template diff --git a/src/argParser.cpp b/src/argParser.cpp index 7e266a5..336b595 100644 --- a/src/argParser.cpp +++ b/src/argParser.cpp @@ -1,4 +1,4 @@ -#include <../include/argParser.hpp> +#include "argParser.hpp" ArgParser::ArgParser(int &argc, char **argv) { for (int i = 0; i < argc; i++) { diff --git a/src/gitRepo.cpp b/src/gitRepo.cpp index c59b4bb..7ff6fb2 100644 --- a/src/gitRepo.cpp +++ b/src/gitRepo.cpp @@ -1,4 +1,4 @@ -#include <../include/gitRepo.hpp> +#include "gitRepo.hpp" GitRepository::GitRepository(std::string path, bool force) { worktree = path; diff --git a/src/gitexceptions.cpp b/src/gitexceptions.cpp index ccfbb14..194cef1 100644 --- a/src/gitexceptions.cpp +++ b/src/gitexceptions.cpp @@ -1,4 +1,4 @@ -#include <../include/gitexceptions.hpp> +#include "gitexceptions.hpp" // Override the what() method to return our message const char* GitException::what() const throw() { return message.c_str(); } diff --git a/src/main.cpp b/src/main.cpp index bdeb27c..5f92f2d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ -#include <../include/argParser.hpp> -#include <../include/utils.hpp> #include +#include "argParser.hpp" #include "gitRepo.hpp" +#include "utils.hpp" void repo_create(std::string path) { GitRepository repo(path, true); @@ -27,8 +27,7 @@ void repo_create(std::string path) { "the repository.\n"); pu.write_to_file(pu.create_file(false, "HEAD"), "ref: refs/heads/main\n"); - // pu.write_to_file(pu.create_file(false, "config"), "ref: - // refs/heads/main\n"); + pu.create_default_configs(); } int main(int argc, char* argv[]) { @@ -66,4 +65,6 @@ int main(int argc, char* argv[]) { std::cout << std::endl; // std::cout << "Unknown command entered!!!" << std::endl; + + return 0; } diff --git a/src/utils.cpp b/src/utils.cpp index ff6c105..092438a 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,4 +1,4 @@ -#include <../include/utils.hpp> +#include "utils.hpp" Utils::Utils(GitRepository &repo) : repo(repo) {} @@ -8,8 +8,19 @@ void Utils::write_to_file(std::string &&path, std::string &&content) { if (of.is_open()) { of << std::move(content); of.close(); - std::cout << "File written successfully.\n"; + // std::cout << "File written successfully.\n"; in logs } else { throw GitException("Unable to open file for writing.\n"); } } + +void Utils::create_default_configs() { + std::string configFilePath = + Utils::repo_path(repo.getGitDir(), "config.ini"); + mINI::INIFile file(configFilePath); + mINI::INIStructure ini; + ini["core"]["repositoryformatversion"] = "0"; + ini["core"]["filemode"] = "false"; + ini["core"]["bare"] = "false"; + file.generate(ini); +}