use crate::node::{CollectionStyle, Entry, Node, NodeData, NodeId, ScalarStyle};
use crate::tag::{Tag, TagHandle};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct YamlVersion {
pub major: u32,
pub minor: u32,
}
impl YamlVersion {
pub const V1_1: YamlVersion = YamlVersion { major: 1, minor: 1 };
}
impl Default for YamlVersion {
fn default() -> Self {
Self::V1_1
}
}
#[derive(Clone, Debug)]
pub struct Document {
nodes: Vec<Node>,
root: Option<NodeId>,
pub version: Option<YamlVersion>,
pub tag_handles: Vec<TagHandle>,
}
impl Default for Document {
fn default() -> Self {
Self::new()
}
}
impl Document {
pub fn new() -> Self {
Self { nodes: Vec::new(), root: None, version: None, tag_handles: Vec::new() }
}
pub fn new_asdf() -> Self {
Self {
nodes: Vec::new(),
root: None,
version: Some(YamlVersion::V1_1),
tag_handles: vec![TagHandle::asdf_default()],
}
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn root(&self) -> Option<NodeId> {
self.root
}
pub fn set_root(&mut self, id: NodeId) {
self.root = Some(id);
}
pub fn add(&mut self, node: Node) -> NodeId {
let id = NodeId(u32::try_from(self.nodes.len()).expect("node arena overflow"));
self.nodes.push(node);
id
}
pub fn node(&self, id: NodeId) -> &Node {
&self.nodes[id.index()]
}
pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
&mut self.nodes[id.index()]
}
pub fn get(&self, id: NodeId) -> Option<&Node> {
self.nodes.get(id.index())
}
pub fn resolve(&self, mut id: NodeId) -> NodeId {
for _ in 0..=self.nodes.len() {
match self.nodes.get(id.index()).map(|n| &n.data) {
Some(NodeData::Alias(target)) => id = *target,
_ => return id,
}
}
id
}
pub fn resolved(&self, id: NodeId) -> &Node {
self.node(self.resolve(id))
}
pub fn tag_of(&self, id: NodeId) -> Option<&Tag> {
self.resolved(id).tag.as_ref()
}
pub fn add_scalar(&mut self, value: impl Into<String>) -> NodeId {
self.add(Node::scalar(value))
}
pub fn add_scalar_styled(&mut self, value: impl Into<String>, style: ScalarStyle) -> NodeId {
self.add(Node::scalar_styled(value, style))
}
pub fn add_sequence(&mut self, items: Vec<NodeId>) -> NodeId {
self.add(Node::new(NodeData::Sequence { items, style: CollectionStyle::Auto }))
}
pub fn add_mapping(&mut self, pairs: Vec<(NodeId, NodeId)>) -> NodeId {
let entries = pairs.into_iter().map(|(key, value)| Entry { key, value }).collect();
self.add(Node::new(NodeData::Mapping { entries, style: CollectionStyle::Auto }))
}
pub fn sequence_items(&self, id: NodeId) -> Option<&[NodeId]> {
match &self.resolved(id).data {
NodeData::Sequence { items, .. } => Some(items),
_ => None,
}
}
pub fn mapping_entries(&self, id: NodeId) -> Option<&[Entry]> {
match &self.resolved(id).data {
NodeData::Mapping { entries, .. } => Some(entries),
_ => None,
}
}
pub fn mapping_get(&self, id: NodeId, key: &str) -> Option<NodeId> {
let entries = self.mapping_entries(id)?;
entries.iter().find(|e| self.resolved(e.key).as_str() == Some(key)).map(|e| e.value)
}
pub fn mapping_set(&mut self, id: NodeId, key: &str, value: NodeId) -> Option<NodeId> {
let target = self.resolve(id);
let existing = self.mapping_entries(target).and_then(|entries| {
entries.iter().position(|e| self.resolved(e.key).as_str() == Some(key))
});
match existing {
Some(pos) => {
let NodeData::Mapping { entries, .. } = &mut self.node_mut(target).data else {
return None;
};
Some(core::mem::replace(&mut entries[pos].value, value))
}
None => {
let key_id = self.add_scalar(key);
let NodeData::Mapping { entries, .. } = &mut self.node_mut(target).data else {
return None;
};
entries.push(Entry { key: key_id, value });
None
}
}
}
pub fn mapping_remove(&mut self, id: NodeId, key: &str) -> Option<NodeId> {
let target = self.resolve(id);
let pos = self
.mapping_entries(target)?
.iter()
.position(|e| self.resolved(e.key).as_str() == Some(key))?;
let NodeData::Mapping { entries, .. } = &mut self.node_mut(target).data else {
return None;
};
Some(entries.remove(pos).value)
}
pub fn sequence_get(&self, id: NodeId, index: i64) -> Option<NodeId> {
let items = self.sequence_items(id)?;
let len = i64::try_from(items.len()).ok()?;
let idx = if index < 0 { len + index } else { index };
if idx < 0 || idx >= len {
return None;
}
items.get(usize::try_from(idx).ok()?).copied()
}
pub fn container_len(&self, id: NodeId) -> Option<usize> {
match &self.resolved(id).data {
NodeData::Sequence { items, .. } => Some(items.len()),
NodeData::Mapping { entries, .. } => Some(entries.len()),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tiny() -> (Document, NodeId) {
let mut doc = Document::new();
let v1 = doc.add_scalar("1");
let v2 = doc.add_scalar("2");
let k1 = doc.add_scalar("a");
let k2 = doc.add_scalar("b");
let map = doc.add_mapping(vec![(k1, v1), (k2, v2)]);
doc.set_root(map);
(doc, map)
}
#[test]
fn mapping_lookup_and_order() {
let (doc, map) = tiny();
assert_eq!(doc.container_len(map), Some(2));
let a = doc.mapping_get(map, "a").unwrap();
assert_eq!(doc.node(a).as_str(), Some("1"));
assert!(doc.mapping_get(map, "missing").is_none());
let keys: Vec<_> = doc
.mapping_entries(map)
.unwrap()
.iter()
.map(|e| doc.node(e.key).as_str().unwrap())
.collect();
assert_eq!(keys, ["a", "b"], "insertion order must be preserved");
}
#[test]
fn mapping_set_replaces_in_place() {
let (mut doc, map) = tiny();
let three = doc.add_scalar("3");
let old = doc.mapping_set(map, "a", three);
assert!(old.is_some());
assert_eq!(doc.node(doc.mapping_get(map, "a").unwrap()).as_str(), Some("3"));
assert_eq!(doc.container_len(map), Some(2));
let keys: Vec<_> = doc
.mapping_entries(map)
.unwrap()
.iter()
.map(|e| doc.node(e.key).as_str().unwrap())
.collect();
assert_eq!(keys, ["a", "b"]);
}
#[test]
fn mapping_set_appends_new_key() {
let (mut doc, map) = tiny();
let v = doc.add_scalar("9");
assert!(doc.mapping_set(map, "c", v).is_none());
assert_eq!(doc.container_len(map), Some(3));
assert_eq!(doc.node(doc.mapping_get(map, "c").unwrap()).as_str(), Some("9"));
}
#[test]
fn mapping_remove_works() {
let (mut doc, map) = tiny();
let removed = doc.mapping_remove(map, "a").unwrap();
assert_eq!(doc.node(removed).as_str(), Some("1"));
assert_eq!(doc.container_len(map), Some(1));
assert!(doc.mapping_get(map, "a").is_none());
}
#[test]
fn negative_sequence_indices_count_from_end() {
let mut doc = Document::new();
let b = doc.add_scalar("b");
let c = doc.add_scalar("c");
let seq = doc.add_sequence(vec![b, c]);
assert_eq!(doc.node(doc.sequence_get(seq, 0).unwrap()).as_str(), Some("b"));
assert_eq!(doc.node(doc.sequence_get(seq, -1).unwrap()).as_str(), Some("c"));
assert_eq!(doc.node(doc.sequence_get(seq, -2).unwrap()).as_str(), Some("b"));
assert!(doc.sequence_get(seq, 2).is_none());
assert!(doc.sequence_get(seq, -3).is_none());
}
#[test]
fn aliases_resolve_through() {
let mut doc = Document::new();
let target = doc.add_scalar("shared");
doc.node_mut(target).anchor = Some("anc".into());
let alias = doc.add(Node::new(NodeData::Alias(target)));
assert!(doc.node(alias).is_alias());
assert_eq!(doc.resolve(alias), target);
assert_eq!(doc.resolved(alias).as_str(), Some("shared"));
}
#[test]
fn alias_cycle_terminates() {
let mut doc = Document::new();
let a = doc.add(Node::new(NodeData::Alias(NodeId(1))));
let b = doc.add(Node::new(NodeData::Alias(NodeId(0))));
let _ = doc.resolve(a);
let _ = doc.resolve(b);
}
}