use crate::document::{RawNode, Scalar, check_write_depth};
use crate::error::WriteError;
use crate::formats::string_escape::{OML_ESCAPES, write_quoted};
use super::parser::RESERVED;
pub(super) fn write_edges(
edges: &[(String, RawNode)],
depth: usize,
indent: usize,
node_depth: usize,
) -> Result<String, WriteError> {
check_write_depth(node_depth, "$")?;
let pad = " ".repeat(indent * depth);
let mut lines = Vec::with_capacity(edges.len());
for (label, child) in edges {
let lab = write_label(label);
match child {
RawNode::Edges(inner) if inner.is_empty() => {
check_write_depth(node_depth + 1, "$")?;
lines.push(format!("{pad}{lab}: {{}}"));
}
RawNode::Edges(inner) => {
let body = write_edges(inner, depth + 1, indent, node_depth + 1)?;
lines.push(format!("{pad}{lab}: {{\n{body}\n{pad}}}"));
}
RawNode::Leaf(s) => {
check_write_depth(node_depth + 1, "$")?;
lines.push(format!("{pad}{lab}: {}", write_scalar(s)));
}
}
}
Ok(lines.join("\n"))
}
pub(super) fn write_edges_compact(
edges: &[(String, RawNode)],
node_depth: usize,
) -> Result<String, WriteError> {
check_write_depth(node_depth, "$")?;
let mut parts = Vec::with_capacity(edges.len());
for (label, child) in edges {
let lab = write_label(label);
match child {
RawNode::Edges(inner) if inner.is_empty() => {
check_write_depth(node_depth + 1, "$")?;
parts.push(format!("{lab}: {{}}"));
}
RawNode::Edges(inner) => {
let body = write_edges_compact(inner, node_depth + 1)?;
parts.push(format!("{lab}: {{ {body} }}"));
}
RawNode::Leaf(s) => {
check_write_depth(node_depth + 1, "$")?;
parts.push(format!("{lab}: {}", write_scalar(s)));
}
}
}
Ok(parts.join("; "))
}
fn is_bare_label(label: &str) -> bool {
let mut chars = label.chars();
match chars.next() {
Some(c) if c.is_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
&& !RESERVED.contains(&label)
&& label != "nan"
&& label != "inf"
}
fn write_label(label: &str) -> String {
if is_bare_label(label) {
label.to_string()
} else {
write_string(label)
}
}
pub(super) fn write_scalar(v: &Scalar) -> String {
match v {
Scalar::Null => "null".to_string(),
Scalar::Bool(b) => b.to_string(),
Scalar::Int(i) => i.to_string(),
Scalar::Float(f) => write_float(*f),
Scalar::Str(s) => write_string(s),
Scalar::Date(s) | Scalar::Time(s) | Scalar::Datetime(s) => s.clone(),
}
}
fn write_float(v: f64) -> String {
crate::formats::float_fmt::float_to_string(v, "nan", "inf", "-inf")
}
fn write_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
write_quoted(s, &OML_ESCAPES, &mut out);
out
}