Files
yamal/yamal.hpp
2025-11-23 22:50:54 +01:00

276 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <algorithm>
#include <cctype>
template<typename Value>
class ordered_map {
std::unordered_map<std::string, Value> map;
std::vector<std::string> keys;
public:
Value& operator[](const std::string& key) {
if (!map.contains(key)) keys.push_back(key);
return map[key];
}
bool contains(const std::string& key) const {
return map.find(key) != map.end();
}
void erase(const std::string& key) {
if (map.erase(key)) {
keys.erase(std::remove(keys.begin(), keys.end(), key), keys.end());
}
}
bool is_last(const std::string& key) const {
return !keys.empty() && keys.back() == key;
}
void clear() {
map.clear();
keys.clear();
}
size_t size() const { return keys.size(); }
std::vector<std::pair<std::string, Value>> items() const {
std::vector<std::pair<std::string, Value>> result;
for (const auto& k : keys) result.emplace_back(k, map.at(k));
return result;
}
const Value& at(const std::string& key) const { return map.at(key); }
Value& at(const std::string& key) { return map.at(key); }
};
class yamal {
public:
enum Mode { SCALAR, VECTOR, MAP };
Mode mode = SCALAR;
std::string value;
std::vector<yamal> vec_data;
ordered_map<yamal> map_data;
std::string pre_comment;
std::string inline_comment;
bool quoted = false;
bool inline_map = false;
bool blank_line = false;
// Mode checks
bool isScalar() const { return mode == SCALAR; }
bool isVector() const { return mode == VECTOR; }
bool isMap() const { return mode == MAP; }
// Mode setters
void clearToScalar() { mode = SCALAR; vec_data.clear(); map_data.clear(); }
void clearToVector() { mode = VECTOR; value.clear(); map_data.clear(); }
void clearToMap() { mode = MAP; value.clear(); vec_data.clear(); }
yamal& operator=(const char* v) { clearToScalar(); value = std::string(v); return *this; }
yamal& operator=(const std::string& v) { clearToScalar(); value = v; return *this; }
yamal& operator=(int v) { clearToScalar(); value = std::to_string(v); return *this; }
yamal& operator=(float v) { clearToScalar(); value = std::to_string(v); return *this; }
yamal& operator=(bool b) { clearToScalar(); value = b ? "true" : "false"; return *this; }
std::string asString() const { return value; }
int asInt() const { return std::stoi(value); }
float asFloat() const { return std::stof(value); }
bool asBool() const {
std::string s = value;
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
if (s == "true") return true;
if (s == "false") return false;
try {
size_t idx;
float f = std::stof(s, &idx);
return (idx != 0 && f != 0.0f);
} catch (...) {
return false;
}
}
// Comment accessors
const std::string& getComment() const { return pre_comment; }
yamal& setComment(const std::string& c) { pre_comment = c; blank_line = true; return *this; }
yamal& appendComment(const std::string& c) {
if (!pre_comment.empty()) pre_comment += "\n";
pre_comment += c;
return *this;
}
// Inline comment accessors
const std::string& getInlineComment() const { return inline_comment; }
yamal& setInlineComment(const std::string& c) { inline_comment = c; return *this; }
// Formatting
yamal& setQuoted(bool q = true) { quoted = q; return *this; }
yamal& setInline(bool in = true) { inline_map = in; return *this; }
yamal& setBlankLine(bool b = true) { blank_line = b; return *this; }
// Vector and map access
void push_back(const yamal& item) {
clearToVector();
vec_data.push_back(item);
}
yamal& operator[](size_t i) {
clearToVector();
if (vec_data.size() <= i) vec_data.resize(i+1);
return vec_data[i];
}
yamal& operator[](const std::string& key) {
clearToMap();
return map_data[key];
}
std::vector<std::pair<std::string, yamal>> attributes() const {
return map_data.items();
}
std::string serialize(int indent = 0, bool emit_self_comment = true) const {
std::ostringstream out;
switch (mode) {
case SCALAR: {
if (emit_self_comment && !pre_comment.empty()) {
out << formatComment(pre_comment, indent);
}
std::string val = quoted ? "\"" + value + "\"" : value;
out << std::string(indent, ' ') << val;
if (!inline_comment.empty()) out << " # " << inline_comment;
break;
}
case MAP: {
if (inline_map) {
if (emit_self_comment && blank_line) { out << "\n"; }
if (emit_self_comment && !pre_comment.empty()) {
out << formatComment(pre_comment, indent);
}
out << std::string(indent, ' ') << "{ ";
bool first = true;
for (auto& kv : map_data.items()) {
if (!first) out << ", ";
out << kv.first << ": " << kv.second.serialize(0, false);
first = false;
}
out << " }";
if (!inline_comment.empty()) out << " # " << inline_comment;
} else {
if (emit_self_comment && blank_line) { out << "\n"; }
// Only emit this nodes own comment at the start if requested
if (emit_self_comment && !pre_comment.empty()) {
out << formatComment(pre_comment, indent);
}
for (auto& kv : map_data.items()) {
// Emit childs pre-comment BEFORE the key line
if (kv.second.blank_line) { out << "\n"; }
if (!kv.second.pre_comment.empty()) {
out << formatComment(kv.second.pre_comment, indent);
}
out << std::string(indent, ' ') << kv.first << ": ";
if (kv.second.inline_map || kv.second.isScalar()) {
out << kv.second.serialize(0, false);
} else {
out << "\n" << kv.second.serialize(indent + 2, false);
}
//if (!kv.second.inline_comment.empty()) {
// out << " # " << kv.second.inline_comment;
//}
if (!map_data.is_last(kv.first)) out << "\n";
}
}
break;
}
case VECTOR: {
if (inline_map) {
if (emit_self_comment && blank_line) { out << "\n"; }
if (emit_self_comment && !pre_comment.empty()) {
out << formatComment(pre_comment, indent);
}
out << std::string(indent, ' ') << "[ ";
for (size_t i = 0; i < vec_data.size(); ++i) {
if (i > 0) out << ", ";
out << vec_data[i].serialize(0, false);
}
out << " ]";
if (!inline_comment.empty()) out << " # " << inline_comment;
} else {
if (emit_self_comment && blank_line) { out << "\n"; }
if (emit_self_comment && !pre_comment.empty()) {
out << formatComment(pre_comment, indent);
}
if (vec_data.empty()) {
out << std::string(indent, ' ') << "[]";
if (!inline_comment.empty()) out << " # " << inline_comment;
} else {
for (size_t i = 0; i < vec_data.size(); ++i) {
const yamal& item = vec_data[i];
if (emit_self_comment && item.blank_line) { out << "\n"; }
// Emit items comment BEFORE the dash line
if (!item.pre_comment.empty()) {
out << formatComment(item.pre_comment, indent);
}
if (i > 0) out << "\n";
out << std::string(indent, ' ') << "- ";
if (item.isScalar() || item.inline_map) {
out << item.serialize(0, false);
} else if (item.isMap()) {
auto attrs = item.attributes();
if (!attrs.empty()) {
out << attrs[0].first << ": " << attrs[0].second.serialize(0, false);
for (size_t j = 1; j < attrs.size(); ++j) {
out << "\n" << std::string(indent + 2, ' ')
<< attrs[j].first << ": ";
if (attrs[j].second.inline_map || attrs[j].second.isScalar()) {
out << attrs[j].second.serialize(0, false);
} else {
out << "\n" << attrs[j].second.serialize(indent + 4, false);
}
}
}
} else {
out << "\n" << item.serialize(indent + 2, false);
}
}
if (!inline_comment.empty()) out << " # " << inline_comment;
}
}
break;
}
}
return out.str();
}
private:
static std::string formatComment(const std::string& c, int indent) {
std::ostringstream out;
std::istringstream in(c);
std::string line;
while (std::getline(in, line)) {
if (line.empty()) {
// preserve blank line
out << "\n";
} else {
out << std::string(indent, ' ') << "# " << line << "\n";
}
}
return out.str();
}
};