#include "fgc/IniEdit.h" #include "fgc/Config.h" #include "ini.h" #include #include #include namespace fgc { namespace { std::string trim(const std::string& s) { const char* ws = " \t\r\n"; const size_t a = s.find_first_not_of(ws); if (a == std::string::npos) return ""; const size_t b = s.find_last_not_of(ws); return s.substr(a, b - a + 1); } int collect(void* user, const char* section, const char* name, const char* value) { auto* kv = static_cast*>(user); (*kv)[std::string(section) + "." + name] = value ? value : ""; return 1; } } // namespace std::size_t iniMaxLineContent() { return INI_MAX_LINE - 2; } int firstOverlongIniLine(const std::string& contents) { std::istringstream in(contents); std::string line; int n = 0; while (std::getline(in, line)) { ++n; if (!line.empty() && line.back() == '\r') line.pop_back(); if (line.size() > iniMaxLineContent()) return n; } return 0; } std::optional> splitIniKey(const std::string& flat) { const size_t dot = flat.rfind('.'); if (dot == std::string::npos || dot == 0 || dot + 1 == flat.size()) return std::nullopt; return std::make_pair(flat.substr(0, dot), flat.substr(dot + 1)); } std::map parseIniString(const std::string& contents) { std::map kv; const int rc = ini_parse_string(contents.c_str(), collect, &kv); if (rc != 0) throw std::runtime_error("does not parse as INI at line " + std::to_string(rc)); return kv; } std::string applyIniSets(const std::string& contents, const std::vector>& sets, const std::string& provenance) { // Per section, in first-mention order, so appended blocks come out in the // order the caller listed them. std::vector order; std::map>> by_section; for (const auto& [flat, value] : sets) { const auto k = splitIniKey(flat); if (!k) throw std::invalid_argument("'" + flat + "' is not Section.key"); if (value.find('\n') != std::string::npos) throw std::invalid_argument(flat + ": a value cannot span lines"); if (!by_section.count(k->first)) order.push_back(k->first); by_section[k->first].emplace_back(k->second, value); } std::string out = contents; for (const auto& section : order) out = updateIniSectionKeys(out, section, by_section[section], provenance); return out; } std::string dropIniKeys(const std::string& contents, const std::map& drops, std::vector* dropped) { std::istringstream in(contents); std::ostringstream out; std::string line, section; bool in_dropped_value = false; while (std::getline(in, line)) { const std::string t = trim(line); // With INI_ALLOW_MULTILINE, an indented line continues the value above // it. Once that key's line is gone, its continuation would attach itself // to whichever key precedes it - so it goes too. if (in_dropped_value && !line.empty() && std::isspace((unsigned char)line[0]) && !t.empty() && t[0] != ';' && t[0] != '#') { out << "; " << t << "\n"; continue; } in_dropped_value = false; if (t.size() >= 2 && t.front() == '[' && t.back() == ']') { section = trim(t.substr(1, t.size() - 2)); } else if (!t.empty() && t[0] != ';' && t[0] != '#') { const size_t eq = line.find('='); if (eq != std::string::npos) { const std::string key = trim(line.substr(0, eq)); const std::string flat = section + "." + key; auto it = drops.find(flat); if (it != drops.end()) { out << "; " << key << " " << it->second << "\n"; if (dropped) dropped->push_back(flat); in_dropped_value = true; continue; } } } out << line << "\n"; } return out.str(); } } // namespace fgc