ANASEN_analysis/Armory/SimConfig.h
James Szalkie 30a1621023 yaml
2026-10-15 12:56:43 -04:00

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