fwt_software/src/core/IniEdit.cpp

119 lines
4.3 KiB
C++

#include "fgc/IniEdit.h"
#include "fgc/Config.h"
#include "ini.h"
#include <cctype>
#include <sstream>
#include <stdexcept>
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<std::map<std::string, std::string>*>(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<std::pair<std::string, std::string>> 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<std::string, std::string> parseIniString(const std::string& contents) {
std::map<std::string, std::string> 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<std::pair<std::string, std::string>>& 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<std::string> order;
std::map<std::string, std::vector<std::pair<std::string, std::string>>> 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<std::string, std::string>& drops,
std::vector<std::string>* 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