use crate::as_yaml::AsYaml;
use crate::lex::SyntaxKind;
use crate::value::YamlValue;
use crate::yaml::SyntaxNode;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct AnchorRegistry {
anchors: HashMap<String, SyntaxNode>,
}
impl AnchorRegistry {
pub fn new() -> Self {
Self {
anchors: HashMap::new(),
}
}
pub fn from_document(doc: &crate::yaml::Document) -> Self {
if let Some(node) = doc.as_node() {
Self::from_tree(node)
} else {
Self::new()
}
}
pub fn from_tree(root: &SyntaxNode) -> Self {
let mut registry = Self::new();
registry.collect_anchors_from_tree(root);
registry
}
fn collect_anchors_from_tree(&mut self, node: &SyntaxNode) {
for child in node.children_with_tokens() {
if let Some(token) = child.as_token() {
if token.kind() == SyntaxKind::ANCHOR {
let text = token.text();
if let Some(name) = text.strip_prefix('&') {
if let Some(value_node) = self.find_anchored_value(node) {
self.anchors.insert(name.to_string(), value_node);
}
}
}
} else if let Some(child_node) = child.as_node() {
self.collect_anchors_from_tree(child_node);
}
}
}
fn find_anchored_value(&self, node: &SyntaxNode) -> Option<SyntaxNode> {
for child in node.children() {
if matches!(
child.kind(),
SyntaxKind::VALUE
| SyntaxKind::SCALAR
| SyntaxKind::MAPPING
| SyntaxKind::SEQUENCE
| SyntaxKind::TAGGED_NODE
) {
return Some(child);
}
}
None
}
pub fn resolve(&self, name: &str) -> Option<&SyntaxNode> {
self.anchors.get(name)
}
pub fn contains(&self, name: &str) -> bool {
self.anchors.contains_key(name)
}
pub fn anchor_names(&self) -> impl Iterator<Item = &str> {
self.anchors.keys().map(|s| s.as_str())
}
}
impl Default for AnchorRegistry {
fn default() -> Self {
Self::new()
}
}
pub trait DocumentResolvedExt {
fn get_resolved(&self, key: impl crate::AsYaml) -> Option<YamlValue>;
fn build_anchor_registry(&self) -> AnchorRegistry;
}
impl DocumentResolvedExt for crate::yaml::Document {
fn get_resolved(&self, key: impl crate::AsYaml) -> Option<YamlValue> {
use rowan::ast::AstNode;
let registry = self.build_anchor_registry();
let mapping = self.as_mapping()?;
let value = mapping
.get_node(&key)
.and_then(crate::value::YamlValue::cast)?;
if let Some(node) = mapping.get_node(&key) {
if let Some(alias_name) = find_alias_reference(&node) {
if let Some(resolved_node) = registry.resolve(&alias_name) {
return YamlValue::cast(resolved_node.clone());
}
}
}
if let Some(node) = mapping.get_node(&key) {
if let Some(result_mapping) = crate::yaml::Mapping::cast(node) {
if has_merge_keys(&result_mapping) {
return Some(YamlValue::Mapping(apply_merge_keys(
&result_mapping,
®istry,
)));
}
}
}
Some(value)
}
fn build_anchor_registry(&self) -> AnchorRegistry {
AnchorRegistry::from_document(self)
}
}
fn find_alias_reference(node: &SyntaxNode) -> Option<String> {
for child in node.children_with_tokens() {
if let Some(token) = child.as_token() {
if token.kind() == SyntaxKind::REFERENCE {
let text = token.text();
return text.strip_prefix('*').map(|s| s.to_string());
}
}
}
if let Some(parent) = node.parent() {
for child in parent.children_with_tokens() {
if let Some(token) = child.as_token() {
if token.kind() == SyntaxKind::REFERENCE {
let text = token.text();
return text.strip_prefix('*').map(|s| s.to_string());
}
}
}
}
None
}
fn node_as_string(node: &crate::as_yaml::YamlNode) -> Option<String> {
node.as_scalar().map(|s| s.as_string())
}
fn has_merge_keys(mapping: &crate::yaml::Mapping) -> bool {
for (key, _) in mapping.iter() {
if node_as_string(&key).as_deref() == Some("<<") {
return true;
}
}
false
}
fn apply_merge_keys(
mapping: &crate::yaml::Mapping,
registry: &AnchorRegistry,
) -> std::collections::BTreeMap<String, YamlValue> {
use crate::as_yaml::YamlNode;
use std::collections::BTreeMap;
let mut merged_pairs: BTreeMap<String, YamlValue> = BTreeMap::new();
for (key, value) in mapping.iter() {
let Some(key_str) = node_as_string(&key) else {
continue;
};
if key_str != "<<" {
continue;
}
match &value {
YamlNode::Scalar(_) => {
let Some(alias_text) = node_as_string(&value) else {
continue;
};
let Some(alias_name) = alias_text.strip_prefix('*') else {
continue;
};
merge_from_alias(&mut merged_pairs, alias_name, registry);
}
YamlNode::Sequence(seq) => {
for alias_node in seq.values() {
let Some(alias_text) = node_as_string(&alias_node) else {
continue;
};
let Some(alias_name) = alias_text.strip_prefix('*') else {
continue;
};
merge_from_alias(&mut merged_pairs, alias_name, registry);
}
}
_ => continue,
}
}
for (key, value) in mapping.iter() {
let Some(key_str) = node_as_string(&key) else {
continue;
};
if key_str == "<<" {
continue;
}
if let Some(yaml_value) = YamlValue::cast(value.syntax().clone()) {
merged_pairs.insert(key_str, yaml_value);
}
}
merged_pairs
}
fn merge_from_alias(
merged_pairs: &mut std::collections::BTreeMap<String, YamlValue>,
alias_name: &str,
registry: &AnchorRegistry,
) {
use rowan::ast::AstNode;
let Some(resolved_node) = registry.resolve(alias_name) else {
return;
};
if let Some(resolved_mapping) = crate::yaml::Mapping::cast(resolved_node.clone()) {
for (src_key, src_value) in resolved_mapping.iter() {
let Some(k_str) = node_as_string(&src_key) else {
continue;
};
if let Some(yaml_value) = YamlValue::cast(src_value.syntax().clone()) {
merged_pairs.insert(k_str, yaml_value);
}
}
}
}