Commits

Kirill Simonov committed 61ce6d0

Add support for recursive nodes to Composer. Constructor does not support recursive objects though.

Comments (0)

Files changed (4)

lib/yaml/composer.py

 class Composer:
 
     def __init__(self):
-        self.all_anchors = {}
-        self.complete_anchors = {}
+        self.anchors = {}
 
     def check_node(self):
         # If there are more documents available?
         # Drop the DOCUMENT-END event.
         self.get_event()
 
-        self.all_anchors = {}
         self.complete_anchors = {}
         return node
 
         if self.check_event(AliasEvent):
             event = self.get_event()
             anchor = event.anchor
-            if anchor not in self.all_anchors:
+            if anchor not in self.anchors:
                 raise ComposerError(None, None, "found undefined alias %r"
                         % anchor.encode('utf-8'), event.start_mark)
-            if anchor not in self.complete_anchors:
-                collection_event = self.all_anchors[anchor]
-                raise ComposerError("while composing a collection",
-                        collection_event.start_mark,
-                        "found recursive anchor %r" % anchor.encode('utf-8'),
-                        event.start_mark)
-            return self.complete_anchors[anchor]
+            return self.anchors[anchor]
         event = self.peek_event()
         anchor = event.anchor
         if anchor is not None:
-            if anchor in self.all_anchors:
+            if anchor in self.anchors:
                 raise ComposerError("found duplicate anchor %r; first occurence"
-                        % anchor.encode('utf-8'), self.all_anchors[anchor].start_mark,
+                        % anchor.encode('utf-8'), self.anchors[anchor].start_mark,
                         "second occurence", event.start_mark)
-            self.all_anchors[anchor] = event
         self.descend_resolver(parent, index)
         if self.check_event(ScalarEvent):
-            node = self.compose_scalar_node()
+            node = self.compose_scalar_node(anchor)
         elif self.check_event(SequenceStartEvent):
-            node = self.compose_sequence_node()
+            node = self.compose_sequence_node(anchor)
         elif self.check_event(MappingStartEvent):
-            node = self.compose_mapping_node()
-        if anchor is not None:
-            self.complete_anchors[anchor] = node
+            node = self.compose_mapping_node(anchor)
         self.ascend_resolver()
         return node
 
-    def compose_scalar_node(self):
+    def compose_scalar_node(self, anchor):
         event = self.get_event()
         tag = event.tag
         if tag is None or tag == u'!':
             tag = self.resolve(ScalarNode, event.value, event.implicit)
-        return ScalarNode(tag, event.value,
+        node = ScalarNode(tag, event.value,
                 event.start_mark, event.end_mark, style=event.style)
+        if anchor is not None:
+            self.anchors[anchor] = node
+        return node
 
-    def compose_sequence_node(self):
+    def compose_sequence_node(self, anchor):
         start_event = self.get_event()
         tag = start_event.tag
         if tag is None or tag == u'!':
         node = SequenceNode(tag, [],
                 start_event.start_mark, None,
                 flow_style=start_event.flow_style)
+        if anchor is not None:
+            self.anchors[anchor] = node
         index = 0
         while not self.check_event(SequenceEndEvent):
             node.value.append(self.compose_node(node, index))
         node.end_mark = end_event.end_mark
         return node
 
-    def compose_mapping_node(self):
+    def compose_mapping_node(self, anchor):
         start_event = self.get_event()
         tag = start_event.tag
         if tag is None or tag == u'!':
         node = MappingNode(tag, {},
                 start_event.start_mark, None,
                 flow_style=start_event.flow_style)
+        if anchor is not None:
+            self.anchors[anchor] = node
         while not self.check_event(MappingEndEvent):
             key_event = self.peek_event()
             item_key = self.compose_node(node, None)

lib/yaml/constructor.py

 
     def __init__(self):
         self.constructed_objects = {}
+        self.recursive_objects = {}
 
     def check_data(self):
         # If there are more documents available?
     def construct_document(self, node):
         data = self.construct_object(node)
         self.constructed_objects = {}
+        self.recursive_objects = {}
         return data
 
     def construct_object(self, node):
         if node in self.constructed_objects:
             return self.constructed_objects[node]
+        if node in self.recursive_objects:
+            raise ConstructorError(None, None,
+                    "found recursive node", node.start_mark)
+        self.recursive_objects[node] = None
         constructor = None
         if node.tag in self.yaml_constructors:
             constructor = lambda node: self.yaml_constructors[node.tag](self, node)
                     print node.tag
         data = constructor(node)
         self.constructed_objects[node] = data
+        del self.recursive_objects[node]
         return data
 
     def construct_scalar(self, node):

tests/test_recursive.py

+
+import unittest
+from yaml import *
+
+RECURSIVE = """
+--- &A
+- *A: *A
+"""
+
+class TestRecursive(unittest.TestCase):
+
+    def testRecursive(self):
+        node = compose(RECURSIVE)
+        self._check(node)
+        document = serialize(node)
+        node = compose(document)
+        self._check(node)
+
+    def _check(self, node):
+        self.failUnless(node in node.value[0].value)
+        self.failUnless(node.value[0].value[node] is node)
+

tests/test_yaml.py

 from test_constructor import *
 from test_emitter import *
 from test_representer import *
+from test_recursive import *
 from test_syck import *
 
 def main(module='__main__'):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.