///|
/// YAML Stringify implementation
/// Simplified version based on js-yaml v3.13.1
///|
fn StringifyOptions::new() -> StringifyOptions {
{
indent: 2,
array_indent: true,
skip_invalid: false,
flow_level: -1,
styles: Map::new(),
schema: "default",
sort_keys: false,
line_width: 80,
use_anchors: true,
compat_mode: true,
condense_flow: false,
}
}
// Dumper state
///|
struct DumperState {
options : StringifyOptions
mut indent_level : Int
mut output : String
}
///|
fn DumperState::new(options : StringifyOptions) -> DumperState {
{ options, indent_level: 0, output: "" }
}
///|
fn DumperState::write(self : DumperState, text : String) -> Unit {
self.output = self.output + text
}
///|
fn DumperState::write_line(self : DumperState, text : String) -> Unit {
self.output = self.output + text + "\n"
}
///|
fn DumperState::write_indent(self : DumperState) -> Unit {
for i = 0; i < self.indent_level * self.options.indent; i = i + 1 {
self.output = self.output + " "
}
}
///|
fn DumperState::increase_indent(self : DumperState) -> Unit {
self.indent_level = self.indent_level + 1
}
///|
fn DumperState::decrease_indent(self : DumperState) -> Unit {
if self.indent_level > 0 {
self.indent_level = self.indent_level - 1
}
}
///|
fn DumperState::write_scalar(
self : DumperState,
value : String,
needs_quotes : Bool,
) -> Unit {
if needs_quotes {
self.write_quoted_string(value)
} else {
self.write(value)
}
}
///|
fn DumperState::write_quoted_string(self : DumperState, value : String) -> Unit {
self.write("\"")
for i = 0; i < value.length(); i = i + 1 {
let ch = value[i]
match ch {
'"' => self.write("\\\"")
'\\' => self.write("\\\\")
'\n' => self.write("\\n")
'\t' => self.write("\\t")
'\r' => self.write("\\r")
_ => self.write(ch.to_string())
}
}
self.write("\"")
}
///|
fn DumperState::needs_quotes(self : DumperState, value : String) -> Bool {
if value.is_empty() {
return true
}
// Check if it looks like a number
match parse_number(value) {
Some(_) => return true
None => ()
}
// Check if it looks like a boolean or null
match value.to_lower() {
"true" | "false" | "yes" | "no" | "on" | "off" | "null" | "~" => return true
_ => ()
}
// Check for special characters
let special_chars = "[]{}|>*&!%#`@,"
for i = 0; i < value.length(); i = i + 1 {
let ch = value[i]
if special_chars.contains(ch.to_string()) {
return true
}
}
// Check if starts with whitespace or contains colon
let first_char = value[0]
if first_char == ' ' || first_char == '\t' || value.contains(":") {
return true
}
false
}
///|
fn DumperState::write_yaml_value(
self : DumperState,
value : YamlValue,
in_flow : Bool,
) -> Unit {
match value {
Null => self.write("null")
Bool(b) => self.write(if b { "true" } else { "false" })
Int(i) => self.write(i.to_string())
Float(f) => self.write(f.to_string())
String(s) => self.write_scalar(s, self.needs_quotes(s))
Array(arr) => self.write_array(arr, in_flow)
Object(obj) => self.write_object(obj, in_flow)
}
}
///|
fn DumperState::write_array(
self : DumperState,
arr : Array[YamlValue],
in_flow : Bool,
) -> Unit {
if arr.is_empty() {
self.write("[]")
return
}
let use_flow = in_flow ||
(
self.options.flow_level >= 0 &&
self.indent_level >= self.options.flow_level
)
if use_flow {
self.write("[")
for i = 0; i < arr.length(); i = i + 1 {
if i > 0 {
if self.options.condense_flow {
self.write(",")
} else {
self.write(", ")
}
}
self.write_yaml_value(arr[i], true)
}
self.write("]")
} else {
for i = 0; i < arr.length(); i = i + 1 {
if i > 0 {
self.write("\n")
}
self.write_indent()
self.write("- ")
if self.options.array_indent {
self.increase_indent()
self.write_yaml_value(arr[i], false)
self.decrease_indent()
} else {
self.write_yaml_value(arr[i], false)
}
}
}
}
///|
fn DumperState::write_object(
self : DumperState,
obj : Map[String, YamlValue],
in_flow : Bool,
) -> Unit {
if obj.size() == 0 {
self.write("{}")
return
}
let use_flow = in_flow ||
(
self.options.flow_level >= 0 &&
self.indent_level >= self.options.flow_level
)
let keys = []
obj
.iter()
.each(fn(entry) {
let (key, _) = entry
keys.push(key)
})
if self.options.sort_keys {
keys.sort()
}
if use_flow {
self.write("{")
for i = 0; i < keys.length(); i = i + 1 {
if i > 0 {
if self.options.condense_flow {
self.write(",")
} else {
self.write(", ")
}
}
let key = keys[i]
let value = obj.get(key).unwrap()
self.write_scalar(key, self.needs_quotes(key))
if self.options.condense_flow {
self.write(":")
} else {
self.write(": ")
}
self.write_yaml_value(value, true)
}
self.write("}")
} else {
for i = 0; i < keys.length(); i = i + 1 {
if i > 0 {
self.write("\n")
}
self.write_indent()
let key = keys[i]
let value = obj.get(key).unwrap()
self.write_scalar(key, self.needs_quotes(key))
self.write(": ")
// Check if value needs multiline formatting
match value {
Array(_) | Object(_) => {
self.write("\n")
self.increase_indent()
self.write_indent()
self.write_yaml_value(value, false)
self.decrease_indent()
}
_ => self.write_yaml_value(value, false)
}
}
}
}
///|
fn DumperState::dump(self : DumperState, value : YamlValue) -> String {
self.write_yaml_value(value, false)
self.output
}
///|
/// Convert a value to YAML string
pub fn stringify(
data : YamlValue,
options? : StringifyOptions = StringifyOptions::new(),
) -> String {
let state = DumperState::new(options)
let result = state.dump(data)
if result.is_empty() {
"null\n"
} else {
result + "\n"
}
}