276 lines
10 KiB
C++
276 lines
10 KiB
C++
#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 node’s 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 child’s 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 item’s 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();
|
||
}
|
||
|
||
};
|