#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 #include #include #include #include #include #include #include #include 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 DoubleList(const std::string& key, const std::vector& def) const { auto it = values_.find(key); if (it == values_.end()) return def; std::vector 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 : "") << "\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 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 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 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> 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