Skip to main content

yaml_edit/
visitor.rs

1//! Visitor pattern for traversing YAML AST nodes.
2//!
3//! Allows traversing and processing YAML structures without modifying node types.
4//! Useful for validation, transformation, collection, or analysis.
5//!
6//! # Example
7//!
8//! ```rust
9//! use yaml_edit::visitor::{YamlVisitor, YamlAccept};
10//! use yaml_edit::{Document, Scalar, Mapping, Sequence};
11//! use std::str::FromStr;
12//!
13//! struct ScalarCounter {
14//!     count: usize,
15//! }
16//!
17//! impl YamlVisitor for ScalarCounter {
18//!     fn visit_scalar(&mut self, _scalar: &Scalar) {
19//!         self.count += 1;
20//!     }
21//! }
22//!
23//! let doc = Document::from_str("key: value\nlist:\n  - item1\n  - item2").unwrap();
24//! let mut counter = ScalarCounter { count: 0 };
25//! doc.accept(&mut counter);
26//! assert_eq!(counter.count, 5); // "key", "value", "list", "item1", "item2"
27//! ```
28//!
29//! Default traversal implementations automatically visit child nodes. Override
30//! `visit_mapping`, `visit_sequence` or `visit_tagged_node` for custom
31//! traversal logic.
32//!
33//! A tagged node (`!!str x`, `!custom {a: 1}`) is handed to
34//! `visit_tagged_node`, which by default descends into the tagged content, so
35//! a visitor that only implements `visit_scalar` still sees the scalar
36//! inside `!!str x`.
37
38use crate::yaml::{Document, Mapping, Scalar, Sequence, TaggedNode, YamlFile};
39
40/// Trait for implementing the visitor pattern on YAML nodes.
41pub trait YamlVisitor {
42    /// Visit a YAML root node
43    fn visit_yaml(&mut self, yaml: &YamlFile) {
44        // Default implementation visits all documents
45        for doc in yaml.documents() {
46            self.visit_document(&doc);
47        }
48    }
49
50    /// Visit a document node
51    ///
52    /// The default implementation visits the document's root node.
53    fn visit_document(&mut self, document: &Document) {
54        self.walk_document(document);
55    }
56
57    /// Visit the root node of a document (helper for default traversal).
58    fn walk_document(&mut self, document: &Document) {
59        if let Some(tagged) = document.as_tagged() {
60            self.visit_tagged_node(&tagged);
61        } else if let Some(mapping) = document.as_mapping() {
62            self.visit_mapping(&mapping);
63        } else if let Some(sequence) = document.as_sequence() {
64            self.visit_sequence(&sequence);
65        } else if let Some(scalar) = document.as_scalar() {
66            self.visit_scalar(&scalar);
67        }
68    }
69
70    /// Visit a scalar node
71    fn visit_scalar(&mut self, _scalar: &Scalar) {}
72
73    /// Visit a mapping node
74    ///
75    /// The default implementation traverses all key-value pairs in the mapping,
76    /// visiting both keys and values recursively. Override this method if you need
77    /// custom logic when encountering mappings.
78    fn visit_mapping(&mut self, mapping: &Mapping) {
79        self.walk_mapping(mapping);
80    }
81
82    /// Visit a sequence node
83    ///
84    /// The default implementation traverses all items in the sequence recursively.
85    /// Override this method if you need custom logic when encountering sequences.
86    fn visit_sequence(&mut self, sequence: &Sequence) {
87        self.walk_sequence(sequence);
88    }
89
90    /// Visit a node carrying an explicit tag (`!!str x`, `!custom {a: 1}`).
91    ///
92    /// The default implementation descends into the tagged content, so a
93    /// visitor that only cares about scalars still sees `!!str x`. Override
94    /// this method to inspect the tag itself.
95    fn visit_tagged_node(&mut self, tagged: &TaggedNode) {
96        self.walk_tagged_node(tagged);
97    }
98
99    /// Traverse the content a tag is attached to (helper for default
100    /// traversal).
101    fn walk_tagged_node(&mut self, tagged: &TaggedNode) {
102        if let Some(mapping) = tagged.as_mapping() {
103            self.visit_mapping(&mapping);
104        } else if let Some(sequence) = tagged.as_sequence() {
105            self.visit_sequence(&sequence);
106        } else if let Some(scalar) = tagged.value() {
107            self.visit_scalar(&scalar);
108        }
109    }
110
111    /// Traverse all key-value pairs in a mapping (helper for default traversal).
112    ///
113    /// This method is called by the default `visit_mapping` implementation.
114    /// You can call it explicitly if you override `visit_mapping` and want to
115    /// preserve the default traversal behavior.
116    fn walk_mapping(&mut self, mapping: &Mapping) {
117        for (key_node, value_node) in mapping.pairs() {
118            visit_node(self, &key_node);
119            visit_node(self, &value_node);
120        }
121    }
122
123    /// Traverse all items in a sequence (helper for default traversal).
124    ///
125    /// This method is called by the default `visit_sequence` implementation.
126    /// You can call it explicitly if you override `visit_sequence` and want to
127    /// preserve the default traversal behavior.
128    fn walk_sequence(&mut self, sequence: &Sequence) {
129        for item in sequence.items() {
130            visit_node(self, &item);
131        }
132    }
133}
134
135/// Visit the scalar, tagged node, mapping or sequence `node` is or wraps.
136fn visit_node<V: YamlVisitor + ?Sized>(visitor: &mut V, node: &crate::yaml::SyntaxNode) {
137    use crate::yaml::extract;
138
139    if let Some(scalar) = extract(node) {
140        visitor.visit_scalar(&scalar);
141    } else if let Some(tagged) = extract(node) {
142        visitor.visit_tagged_node(&tagged);
143    } else if let Some(mapping) = extract(node) {
144        visitor.visit_mapping(&mapping);
145    } else if let Some(sequence) = extract(node) {
146        visitor.visit_sequence(&sequence);
147    }
148}
149
150/// Trait for nodes that can accept a visitor
151pub trait YamlAccept {
152    /// Accept a visitor for traversal
153    fn accept<V: YamlVisitor>(&self, visitor: &mut V);
154}
155
156impl YamlAccept for YamlFile {
157    fn accept<V: YamlVisitor>(&self, visitor: &mut V) {
158        visitor.visit_yaml(self);
159    }
160}
161
162impl YamlAccept for Document {
163    fn accept<V: YamlVisitor>(&self, visitor: &mut V) {
164        visitor.visit_document(self);
165    }
166}
167
168impl YamlAccept for Scalar {
169    fn accept<V: YamlVisitor>(&self, visitor: &mut V) {
170        visitor.visit_scalar(self);
171    }
172}
173
174impl YamlAccept for Mapping {
175    fn accept<V: YamlVisitor>(&self, visitor: &mut V) {
176        visitor.visit_mapping(self);
177    }
178}
179
180impl YamlAccept for Sequence {
181    fn accept<V: YamlVisitor>(&self, visitor: &mut V) {
182        visitor.visit_sequence(self);
183    }
184}
185
186/// A visitor that collects all scalar values from a YAML document
187#[derive(Debug, Clone)]
188pub struct ScalarCollector {
189    /// The collected scalar values
190    pub scalars: Vec<String>,
191}
192
193impl ScalarCollector {
194    /// Create a new scalar collector
195    pub fn new() -> Self {
196        Self {
197            scalars: Vec::new(),
198        }
199    }
200}
201
202impl Default for ScalarCollector {
203    fn default() -> Self {
204        Self::new()
205    }
206}
207
208impl YamlVisitor for ScalarCollector {
209    fn visit_scalar(&mut self, scalar: &Scalar) {
210        self.scalars.push(scalar.to_string());
211    }
212    // Default traversal methods handle mapping and sequence traversal automatically
213}
214
215/// A visitor that counts different types of nodes
216#[derive(Debug, Clone)]
217pub struct NodeCounter {
218    /// Number of document nodes encountered
219    pub document_count: usize,
220    /// Number of scalar nodes encountered
221    pub scalar_count: usize,
222    /// Number of mapping nodes encountered
223    pub mapping_count: usize,
224    /// Number of sequence nodes encountered
225    pub sequence_count: usize,
226}
227
228impl NodeCounter {
229    /// Create a new node counter
230    pub fn new() -> Self {
231        Self {
232            document_count: 0,
233            scalar_count: 0,
234            mapping_count: 0,
235            sequence_count: 0,
236        }
237    }
238
239    /// Get total node count
240    pub fn total(&self) -> usize {
241        self.document_count + self.scalar_count + self.mapping_count + self.sequence_count
242    }
243}
244
245impl Default for NodeCounter {
246    fn default() -> Self {
247        Self::new()
248    }
249}
250
251impl YamlVisitor for NodeCounter {
252    fn visit_document(&mut self, document: &Document) {
253        self.document_count += 1;
254        self.walk_document(document);
255    }
256
257    fn visit_scalar(&mut self, _scalar: &Scalar) {
258        self.scalar_count += 1;
259    }
260
261    fn visit_mapping(&mut self, mapping: &Mapping) {
262        self.mapping_count += 1;
263        // Call default traversal to visit children
264        self.walk_mapping(mapping);
265    }
266
267    fn visit_sequence(&mut self, sequence: &Sequence) {
268        self.sequence_count += 1;
269        // Call default traversal to visit children
270        self.walk_sequence(sequence);
271    }
272}
273
274/// A visitor that transforms scalar values
275pub struct ScalarTransformer<F>
276where
277    F: FnMut(&str) -> String,
278{
279    transform: F,
280    transformed: Vec<(String, String)>, // (original, transformed) pairs
281}
282
283impl<F> ScalarTransformer<F>
284where
285    F: FnMut(&str) -> String,
286{
287    /// Create a new scalar transformer with the given transformation function
288    pub fn new(transform: F) -> Self {
289        Self {
290            transform,
291            transformed: Vec::new(),
292        }
293    }
294
295    /// Get the transformed pairs
296    pub fn results(&self) -> &[(String, String)] {
297        &self.transformed
298    }
299}
300
301impl<F> YamlVisitor for ScalarTransformer<F>
302where
303    F: FnMut(&str) -> String,
304{
305    fn visit_scalar(&mut self, scalar: &Scalar) {
306        let original = scalar.to_string();
307        let transformed = (self.transform)(&original);
308        self.transformed.push((original, transformed));
309    }
310
311    // Default traversal methods handle mapping and sequence traversal automatically
312}