Kirill Simonov avatar Kirill Simonov committed 0e24a4f

Make compose() and load() ensure that the input stream contains a single document. Fixes #54.

Comments (0)

Files changed (8)

         if self.parsed_event.type != YAML_STREAM_END_EVENT:
             return self._compose_document()
 
+    def get_single_node(self):
+        self._parse_next_event()
+        yaml_event_delete(&self.parsed_event)
+        self._parse_next_event()
+        document = None
+        if self.parsed_event.type != YAML_STREAM_END_EVENT:
+            document = self._compose_document()
+        self._parse_next_event()
+        if self.parsed_event.type != YAML_STREAM_END_EVENT:
+            mark = Mark(self.stream_name,
+                    self.parsed_event.start_mark.index,
+                    self.parsed_event.start_mark.line,
+                    self.parsed_event.start_mark.column,
+                    None, None)
+            raise ComposerError("expected a single document in the stream",
+                    document.start_mark, "but found another document", mark)
+        return document
+
     cdef object _compose_document(self):
         yaml_event_delete(&self.parsed_event)
         node = self._compose_node(None, None)

lib/yaml/__init__.py

     and produce the corresponding representation tree.
     """
     loader = Loader(stream)
-    if loader.check_node():
-        return loader.get_node()
+    return loader.get_single_node()
 
 def compose_all(stream, Loader=Loader):
     """
     while loader.check_node():
         yield loader.get_node()
 
+def load(stream, Loader=Loader):
+    """
+    Parse the first YAML document in a stream
+    and produce the corresponding Python object.
+    """
+    loader = Loader(stream)
+    return loader.get_single_data()
+
 def load_all(stream, Loader=Loader):
     """
     Parse all YAML documents in a stream
     while loader.check_data():
         yield loader.get_data()
 
-def load(stream, Loader=Loader):
+def safe_load(stream):
     """
     Parse the first YAML document in a stream
     and produce the corresponding Python object.
+    Resolve only basic YAML tags.
     """
-    loader = Loader(stream)
-    if loader.check_data():
-        return loader.get_data()
+    return load(stream, SafeLoader)
 
 def safe_load_all(stream):
     """
     """
     return load_all(stream, SafeLoader)
 
-def safe_load(stream):
-    """
-    Parse the first YAML document in a stream
-    and produce the corresponding Python object.
-    Resolve only basic YAML tags.
-    """
-    return load(stream, SafeLoader)
-
 def emit(events, stream=None, Dumper=Dumper,
         canonical=None, indent=None, width=None,
         allow_unicode=None, line_break=None):

lib/yaml/composer.py

         if not self.check_event(StreamEndEvent):
             return self.compose_document()
 
+    def get_single_node(self):
+        # Drop the STREAM-START event.
+        self.get_event()
+
+        # Compose a document if the stream is not empty.
+        document = None
+        if not self.check_event(StreamEndEvent):
+            document = self.compose_document()
+
+        # Ensure that the stream contains no more documents.
+        if not self.check_event(StreamEndEvent):
+            event = self.get_event()
+            raise ComposerError("expected a single document in the stream",
+                    document.start_mark, "but found another document",
+                    event.start_mark)
+
+        # Drop the STREAM-END event.
+        self.get_event()
+
+        return document
+
     def compose_document(self):
         # Drop the DOCUMENT-START event.
         self.get_event()

lib/yaml/constructor.py

         if self.check_node():
             return self.construct_document(self.get_node())
 
+    def get_single_data(self):
+        # Ensure that the stream contains a single document and construct it.
+        node = self.get_single_node()
+        if node is not None:
+            return self.construct_document(node)
+        return None
+
     def construct_document(self, node):
         data = self.construct_object(node)
         while self.state_generators:

tests/data/empty-documents.single-loader-error

+--- # first document
+--- # second document

tests/data/explicit-document.single-loader-error

+---
+foo: bar
+---
+foo: bar

tests/data/implicit-document.single-loader-error

+foo: bar
+---
+foo: bar

tests/test_errors.py

         #self._load_string(invalid_filename)
         self.failUnlessRaises(YAMLError, lambda: self._load_string(invalid_filename))
 
+    def _testLoaderSingleErrors(self, test_name, invalid_filename):
+        self._load_single(invalid_filename)
+        self.failUnlessRaises(YAMLError, lambda: self._load_single(invalid_filename))
+
     def _testEmitterErrors(self, test_name, invalid_filename):
         events = list(load(file(invalid_filename, 'rb').read(),
             Loader=test_emitter.EventsLoader))
             #print "%s:" % exc.__class__.__name__, exc
             raise
 
+    def _load_single(self, filename):
+        try:
+            return load(file(filename, 'rb').read())
+        except YAMLError, exc:
+        #except ScannerError, exc:
+        #except ParserError, exc:
+        #except ComposerError, exc:
+        #except ConstructorError, exc:
+            #print '.'*70
+            #print "%s:" % filename
+            #print "%s:" % exc.__class__.__name__, exc
+            raise
+
 TestErrors.add_tests('testLoaderErrors', '.loader-error')
 TestErrors.add_tests('testLoaderStringErrors', '.loader-error')
+TestErrors.add_tests('testLoaderSingleErrors', '.single-loader-error')
 TestErrors.add_tests('testEmitterErrors', '.emitter-error')
 TestErrors.add_tests('testDumperErrors', '.dumper-error')
 
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.