use crate::anchors_debug;
use crate::nodes::node::Node;
use crate::parser::ParseResult;
use crate::parser::utils::error_helpers;
use crate::utils::anchors_helpers;
use std::collections::HashMap;
#[allow(dead_code)]
pub(crate) fn collect_anchors(node: &Node, anchors: &mut HashMap<String, Node>) -> ParseResult<()> {
anchors_helpers::traverse_with_error(node, |n| {
if let Node::Anchored(inner, name) = n {
if name.trim().is_empty() {
anchors_debug!("Empty anchor name encountered");
return Some(error_helpers::empty_anchor_name().to_string());
}
anchors_debug!("Collecting anchor: {}", name);
anchors.insert(name.clone(), (**inner).clone());
}
None
})
}
#[allow(dead_code)]
pub(crate) fn replace_aliases(node: &mut Node, anchors: &HashMap<String, Node>) -> ParseResult<()> {
let mut err: Option<crate::error::YamlError> = None;
let mut replacer = |n: &mut Node| match n {
Node::Alias(name) => {
anchors_debug!("Replacing alias: {}", name);
match anchors_helpers::lookup_anchor(anchors, name) {
Ok(found) => *n = found.clone(),
Err(e) => err = Some(e),
}
}
Node::Anchored(inner, _name) => {
anchors_debug!("Replacing anchored node");
let replacement = (**inner).clone();
*n = replacement;
}
_ => {}
};
crate::parser::utils::visit::visit_mut(node, &mut replacer);
if let Some(e) = err { Err(e) } else { Ok(()) }
}
#[allow(dead_code)]
pub(crate) fn expand_merge_keys(
node: &mut Node,
anchors: &HashMap<String, Node>,
) -> ParseResult<()> {
let mut err: Option<crate::error::YamlError> = None;
let mut expander = |n: &mut Node| {
if let Node::Mapping(pairs) = n {
anchors_debug!("Expanding merge keys in mapping");
let mut combined: Vec<(Node, Node)> = Vec::new();
let snapshot = pairs.clone();
let mut i = 0usize;
while i < snapshot.len() {
let (k, v) = snapshot[i].clone();
let mut handled = false;
if let Node::Str(ks, _, _) = &k {
if ks.trim() == "<<" {
let mut expanded_pairs: Vec<(Node, Node)> = Vec::new();
match &v {
Node::Alias(name) => {
anchors_debug!("Expanding merge alias: {}", name);
match anchors_helpers::lookup_anchor(anchors, name)
.and_then(|src| anchors_helpers::as_mapping(src, name))
{
Ok(src_pairs) => expanded_pairs.extend(src_pairs.clone()),
Err(e) => {
err = Some(e);
break;
}
}
}
Node::Array(items) => {
anchors_debug!("Expanding merge array with {} items", items.len());
for it in items.iter() {
match it {
Node::Alias(name) => {
anchors_debug!(
"Expanding merge alias in array: {}",
name
);
match anchors_helpers::lookup_anchor(anchors, name)
.and_then(|src| {
anchors_helpers::as_mapping(src, name)
}) {
Ok(src_pairs) => {
expanded_pairs.extend(src_pairs.clone())
}
Err(e) => {
err = Some(e);
break;
}
}
}
Node::Mapping(src_pairs) => {
anchors_debug!("Expanding merge mapping in array");
expanded_pairs.extend(src_pairs.clone());
}
_ => {
err =
Some(error_helpers::invalid_merge_sequence_item());
break;
}
}
}
}
Node::Mapping(src_pairs) => {
expanded_pairs.extend(src_pairs.clone());
}
other => {
err = Some(error_helpers::invalid_merge_value(&format!(
"{:?}",
other
)));
}
}
combined.extend(expanded_pairs);
handled = true;
}
}
if handled {
i += 1;
continue;
}
if let Node::Str(_, _, _) = &k {
if let Node::Str(s, _, _) = &v {
let ts = s.trim_start();
if ts.starts_with("<<:") && ts.contains('*') {
if let Some(pos) = ts.find('*') {
let aname = ts[pos + 1..].trim().to_string();
let mut nested: Vec<(Node, Node)> = Vec::new();
let mut j = i + 1;
while j < snapshot.len() {
if let Node::Str(ns, _, _) = &snapshot[j].1 {
if ns.trim_start().starts_with("<<:") {
break;
}
}
if let Node::Str(key_name, _, _) = &snapshot[j].0 {
if key_name.contains("boolean")
|| key_name.contains("explicit")
{
break;
}
let trimmed_key = key_name.trim();
if trimmed_key == "explicit_boolean" {
break;
}
}
nested.push(snapshot[j].clone());
j += 1;
}
let mut merged_pairs: Vec<(Node, Node)> = Vec::new();
if let Some(src) = anchors.get(&aname) {
if let Node::Mapping(ap) = src {
merged_pairs.extend(ap.clone());
}
}
for (nk, nv) in nested.iter() {
let mut replaced = false;
if let Node::Str(nks, _, _) = nk {
for p in merged_pairs.iter_mut() {
if let Node::Str(pk, _, _) = &p.0 {
if pk == nks {
*p = (nk.clone(), nv.clone());
replaced = true;
break;
}
}
}
}
if !replaced {
merged_pairs.push((nk.clone(), nv.clone()));
}
}
combined.push((
k.clone(),
crate::parser::utils::node_utils::make_mapping_node(
merged_pairs,
),
));
i = j;
continue;
}
}
}
}
combined.push((k, v));
i += 1;
}
*pairs =
crate::parser::utils::node_utils::dedupe_mapping_pairs_by_last_occurrence(combined);
}
};
crate::parser::utils::visit::visit_mut(node, &mut expander);
if let Some(e) = err { Err(e) } else { Ok(()) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nodes::node::Node;
use std::collections::HashMap;
#[test]
fn test_collect_anchors_basic() {
let node = Node::Anchored(Box::new(Node::from(42)), "anchor1".to_string());
let mut anchors = HashMap::new();
let result = collect_anchors(&node, &mut anchors);
assert!(result.is_ok());
assert_eq!(anchors.get("anchor1"), Some(&Node::from(42)));
}
#[test]
fn test_collect_anchors_empty_name_error() {
let node = Node::Anchored(Box::new(Node::from(1)), " ".to_string());
let mut anchors = HashMap::new();
let result = collect_anchors(&node, &mut anchors);
assert!(result.is_err());
}
#[test]
fn test_collect_anchors_nested() {
let inner = Node::Anchored(Box::new(Node::from("foo")), "a2".to_string());
let node = Node::Mapping(vec![
(
Node::from("k1"),
Node::Anchored(Box::new(Node::from(42)), "a1".to_string()),
),
(Node::from("k2"), inner.clone()),
]);
let mut anchors = HashMap::new();
let result = collect_anchors(&node, &mut anchors);
assert!(result.is_ok());
assert_eq!(anchors.get("a1"), Some(&Node::from(42)));
assert_eq!(anchors.get("a2"), Some(&Node::from("foo")));
}
#[test]
fn test_replace_aliases_with_missing_anchor() {
let mut node = Node::Alias("missing".to_string());
let anchors = HashMap::new();
let result = replace_aliases(&mut node, &anchors);
assert!(result.is_err());
}
#[test]
fn test_replace_aliases_success() {
let mut anchors = HashMap::new();
anchors.insert("a1".to_string(), Node::from(123));
let mut node = Node::Alias("a1".to_string());
let result = replace_aliases(&mut node, &anchors);
assert!(result.is_ok());
}
}