143 lines
4.9 KiB
C++
143 lines
4.9 KiB
C++
#ifndef SIMCONFIG_H
|
|
#define SIMCONFIG_H
|
|
|
|
// Minimal YAML-subset reader for simulation configs, plus dated history logging.
|
|
// Supported: nested maps by indentation, "key: value", inline lists "[a, b]", "#" comments.
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <sys/stat.h>
|
|
|
|
class SimConfig {
|
|
public:
|
|
std::string path; // file the config was loaded from
|
|
std::string rawText; // full file contents, used for history/ROOT embedding
|
|
bool loaded = false;
|
|
|
|
bool Load(const std::string& file) {
|
|
std::ifstream in(file);
|
|
if (!in) return false;
|
|
path = file;
|
|
std::stringstream ss;
|
|
ss << in.rdbuf();
|
|
rawText = ss.str();
|
|
Parse(rawText);
|
|
loaded = true;
|
|
return true;
|
|
}
|
|
|
|
bool Has(const std::string& key) const { return values_.count(key) > 0; }
|
|
|
|
std::string Str(const std::string& key, const std::string& def) const {
|
|
auto it = values_.find(key);
|
|
return it == values_.end() ? def : it->second;
|
|
}
|
|
double Double(const std::string& key, double def) const {
|
|
auto it = values_.find(key);
|
|
return it == values_.end() ? def : std::atof(it->second.c_str());
|
|
}
|
|
int Int(const std::string& key, int def) const {
|
|
auto it = values_.find(key);
|
|
return it == values_.end() ? def : std::atoi(it->second.c_str());
|
|
}
|
|
bool Bool(const std::string& key, bool def) const {
|
|
auto it = values_.find(key);
|
|
if (it == values_.end()) return def;
|
|
return it->second == "true" || it->second == "yes" || it->second == "1";
|
|
}
|
|
std::vector<double> DoubleList(const std::string& key, const std::vector<double>& def) const {
|
|
auto it = values_.find(key);
|
|
if (it == values_.end()) return def;
|
|
std::vector<double> out;
|
|
for (auto& s : SplitList(it->second)) out.push_back(std::atof(s.c_str()));
|
|
return out;
|
|
}
|
|
|
|
// Saves a timestamped copy under historyDir and appends a line to historyDir/index.log.
|
|
// Returns the path of the saved copy ("" on failure).
|
|
std::string LogToHistory(const std::string& historyDir, int numEvents,
|
|
const std::string& outputFile) const {
|
|
mkdir(historyDir.c_str(), 0755);
|
|
char stamp[32];
|
|
std::time_t now = std::time(nullptr);
|
|
std::strftime(stamp, sizeof(stamp), "%Y-%m-%d_%H%M%S", std::localtime(&now));
|
|
std::string copy = historyDir + "/" + stamp + ".yaml";
|
|
std::ofstream out(copy);
|
|
if (!out) return "";
|
|
out << "# run: " << stamp << " events: " << numEvents << " output: " << outputFile
|
|
<< " source: " << (loaded ? path : "<built-in defaults>") << "\n"
|
|
<< rawText;
|
|
if (!rawText.empty() && rawText.back() != '\n') out << "\n";
|
|
std::ofstream idx(historyDir + "/index.log", std::ios::app);
|
|
idx << stamp << "\t" << numEvents << "\t" << outputFile << "\t" << copy << "\n";
|
|
return copy;
|
|
}
|
|
|
|
private:
|
|
std::map<std::string, std::string> values_; // dotted key -> raw scalar/list text
|
|
|
|
static std::string Trim(const std::string& s) {
|
|
size_t a = s.find_first_not_of(" \t\r");
|
|
if (a == std::string::npos) return "";
|
|
size_t b = s.find_last_not_of(" \t\r");
|
|
return s.substr(a, b - a + 1);
|
|
}
|
|
|
|
static std::string StripQuotes(const std::string& s) {
|
|
if (s.size() >= 2 && ((s.front() == '"' && s.back() == '"') || (s.front() == '\'' && s.back() == '\'')))
|
|
return s.substr(1, s.size() - 2);
|
|
return s;
|
|
}
|
|
|
|
static std::vector<std::string> SplitList(const std::string& s) {
|
|
std::string t = Trim(s);
|
|
if (!t.empty() && t.front() == '[') t.erase(0, 1);
|
|
if (!t.empty() && t.back() == ']') t.pop_back();
|
|
std::vector<std::string> out;
|
|
std::stringstream ss(t);
|
|
std::string item;
|
|
while (std::getline(ss, item, ',')) {
|
|
item = Trim(item);
|
|
if (!item.empty()) out.push_back(StripQuotes(item));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void Parse(const std::string& text) {
|
|
std::vector<std::pair<int, std::string>> stack; // (indent, key)
|
|
std::stringstream ss(text);
|
|
std::string line;
|
|
while (std::getline(ss, line)) {
|
|
// drop comments (a '#' outside quotes)
|
|
bool inQ = false;
|
|
char q = 0;
|
|
for (size_t i = 0; i < line.size(); i++) {
|
|
char c = line[i];
|
|
if (inQ) { if (c == q) inQ = false; }
|
|
else if (c == '"' || c == '\'') { inQ = true; q = c; }
|
|
else if (c == '#') { line.erase(i); break; }
|
|
}
|
|
if (Trim(line).empty()) continue;
|
|
int indent = line.find_first_not_of(' ');
|
|
size_t colon = line.find(':', indent);
|
|
if (colon == std::string::npos) continue;
|
|
std::string key = Trim(line.substr(indent, colon - indent));
|
|
std::string val = Trim(line.substr(colon + 1));
|
|
while (!stack.empty() && stack.back().first >= indent) stack.pop_back();
|
|
std::string full;
|
|
for (auto& p : stack) full += p.second + ".";
|
|
full += key;
|
|
if (val.empty()) stack.push_back({indent, key});
|
|
else values_[full] = StripQuotes(val);
|
|
}
|
|
}
|
|
};
|
|
|
|
#endif
|