use crate::{parser::Anchors, repr::Repr, *};
use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
use core::fmt::Write;
pub const NL: &str = if cfg!(windows) { "\r\n" } else { "\n" };
#[derive(Eq, PartialEq)]
enum Root {
Scalar,
Map,
Array,
}
pub struct Dumper<'a, R: Repr> {
node: &'a Node<R>,
root: Root,
level: usize,
anchors: &'a Anchors<R>,
}
impl<'a, R: Repr> Dumper<'a, R> {
pub fn new(node: &'a Node<R>, anchors: &'a Anchors<R>) -> Self {
Self { node, root: Root::Scalar, level: 0, anchors }
}
fn part(&self, node: &'a Node<R>, root: Root, level: usize) -> String {
Self { node, root, level, anchors: self.anchors }.dump()
}
pub fn dump(&self) -> String {
let mut doc = String::new();
if let Some(a) = self
.anchors
.iter()
.find_map(|(k, v)| if v == self.node { Some(k) } else { None })
{
write!(doc, "&{a} ").unwrap();
}
let tag = self.node.tag();
if !tag.is_empty() && !tag.starts_with(parser::tag_prefix!()) {
if tag.starts_with(parser::tag_prefix!()) {
write!(doc, "!!{tag} ").unwrap();
} else if parser::Parser::new(tag.as_bytes()).identifier().is_ok() {
write!(doc, "!{tag} ").unwrap();
} else {
write!(doc, "!<{tag}> ").unwrap();
}
}
let ind = " ".repeat(self.level);
match &self.node.yaml() {
Yaml::Null => doc += "null",
Yaml::Bool(b) => write!(doc, "{b}").unwrap(),
Yaml::Int(n) | Yaml::Float(n) => doc += n,
Yaml::Str(s) => {
if s.lines().nth(1).is_some() {
let s = s
.lines()
.map(|s| {
if s.is_empty() {
String::new()
} else {
ind.to_string() + s.trim_end()
}
})
.collect::<Vec<_>>()
.join(NL);
write!(doc, "|{NL}{ind}{}", s.trim()).unwrap();
} else if parser::Parser::new(s.as_bytes())
.string_plain(0, false)
.is_err()
{
write!(doc, "{s:?}").unwrap();
} else {
doc += s;
}
}
Yaml::Seq(v) => {
let mut buf = NL.to_string();
for (i, node) in v.iter().enumerate() {
if i != 0 || self.level != 0 {
buf += &ind;
}
let s = self.part(node, Root::Array, self.level + 1);
write!(buf, "- {s}{NL}").unwrap();
}
buf.truncate(buf.len() - NL.len());
doc += &buf;
}
Yaml::Map(m) => {
let mut buf = match self.root {
Root::Map => NL.to_string(),
_ => String::new(),
};
for (i, (k, v)) in m.iter().enumerate() {
if i != 0 || self.root == Root::Map {
buf += &ind;
}
let s = self.part(k, Root::Map, self.level + 1);
if matches!(k.yaml(), Yaml::Map(_) | Yaml::Seq(_)) {
let pre_ind = " ".repeat(self.level + 1);
write!(buf, "?{pre_ind}{NL}{s}{NL}{ind}").unwrap();
} else {
buf += &s;
};
buf += ":";
buf += &match v.yaml() {
Yaml::Map(_) => self.part(v, Root::Map, self.level + 1),
Yaml::Seq(_) if self.root == Root::Array && i == 0 => {
self.part(v, Root::Map, self.level)
}
Yaml::Seq(_) => self.part(v, Root::Map, self.level + 1),
_ => format!(" {}", self.part(v, Root::Map, self.level + 1)),
};
buf += NL;
}
buf.truncate(buf.len() - NL.len());
doc += &buf;
}
Yaml::Alias(a) => write!(doc, "*{a}").unwrap(),
};
doc
}
}
pub fn dump<R: Repr>(nodes: &[Node<R>], anchors: &[Anchors<R>]) -> String {
let anchors_empty = Anchors::new();
nodes
.iter()
.enumerate()
.map(|(i, node)| {
let anchors = if i < anchors.len() {
&anchors[i]
} else {
&anchors_empty
};
let doc = Dumper::new(node, anchors).dump() + NL;
match i {
0 => doc,
_ => format!("---{NL}{}", doc.trim_start()),
}
})
.collect()
}