summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/_yaml.pyx18
-rw-r--r--lib/yaml/__init__.py26
-rw-r--r--lib/yaml/composer.py21
-rw-r--r--lib/yaml/constructor.py7
-rw-r--r--tests/data/empty-documents.single-loader-error2
-rw-r--r--tests/data/explicit-document.single-loader-error4
-rw-r--r--tests/data/implicit-document.single-loader-error3
-rw-r--r--tests/test_errors.py18
8 files changed, 85 insertions, 14 deletions
diff --git a/ext/_yaml.pyx b/ext/_yaml.pyx
index ac8f803..76b307a 100644
--- a/ext/_yaml.pyx
+++ b/ext/_yaml.pyx
@@ -663,6 +663,24 @@ cdef class CParser:
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)
diff --git a/lib/yaml/__init__.py b/lib/yaml/__init__.py
index bd233a8..e131795 100644
--- a/lib/yaml/__init__.py
+++ b/lib/yaml/__init__.py
@@ -35,8 +35,7 @@ def compose(stream, Loader=Loader):
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):
"""
@@ -47,6 +46,14 @@ 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
@@ -56,14 +63,13 @@ def load_all(stream, Loader=Loader):
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):
"""
@@ -73,14 +79,6 @@ 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):
diff --git a/lib/yaml/composer.py b/lib/yaml/composer.py
index 9f5cd87..06e5ac7 100644
--- a/lib/yaml/composer.py
+++ b/lib/yaml/composer.py
@@ -26,6 +26,27 @@ class Composer(object):
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()
diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py
index a1295c8..cc68af1 100644
--- a/lib/yaml/constructor.py
+++ b/lib/yaml/constructor.py
@@ -37,6 +37,13 @@ class BaseConstructor(object):
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:
diff --git a/tests/data/empty-documents.single-loader-error b/tests/data/empty-documents.single-loader-error
new file mode 100644
index 0000000..f8dba8d
--- /dev/null
+++ b/tests/data/empty-documents.single-loader-error
@@ -0,0 +1,2 @@
+--- # first document
+--- # second document
diff --git a/tests/data/explicit-document.single-loader-error b/tests/data/explicit-document.single-loader-error
new file mode 100644
index 0000000..46c6f8b
--- /dev/null
+++ b/tests/data/explicit-document.single-loader-error
@@ -0,0 +1,4 @@
+---
+foo: bar
+---
+foo: bar
diff --git a/tests/data/implicit-document.single-loader-error b/tests/data/implicit-document.single-loader-error
new file mode 100644
index 0000000..f8c9a5c
--- /dev/null
+++ b/tests/data/implicit-document.single-loader-error
@@ -0,0 +1,3 @@
+foo: bar
+---
+foo: bar
diff --git a/tests/test_errors.py b/tests/test_errors.py
index 2a49660..a678f2b 100644
--- a/tests/test_errors.py
+++ b/tests/test_errors.py
@@ -16,6 +16,10 @@ class TestErrors(test_appliance.TestAppliance):
#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))
@@ -66,8 +70,22 @@ class TestErrors(test_appliance.TestAppliance):
#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')