summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxi <xi@18f92427-320e-0410-9341-c67f048884a3>2006-04-01 21:28:20 +0000
committerxi <xi@18f92427-320e-0410-9341-c67f048884a3>2006-04-01 21:28:20 +0000
commit32bf6e148723d37bca70ebf895de9c5cbe390433 (patch)
tree7dbada52c486e9d1eeed5efcd411d8d65680f7b7
parentc6f2fc9875595b01f4249fbe87b0b846d0a2adc9 (diff)
downloadpyyaml-working-on-emitter.tar.gz
Add block styles.working-on-emitter
git-svn-id: http://svn.pyyaml.org/pyyaml/branches/working-on-emitter@129 18f92427-320e-0410-9341-c67f048884a3
-rw-r--r--lib/yaml/emitter.py328
-rw-r--r--tests/data/aliases.events8
-rw-r--r--tests/data/documents.events11
-rw-r--r--tests/data/mappings.events44
-rw-r--r--tests/data/scalars.events24
-rw-r--r--tests/data/sequences.events81
-rw-r--r--tests/test_emitter.py53
7 files changed, 487 insertions, 62 deletions
diff --git a/lib/yaml/emitter.py b/lib/yaml/emitter.py
index d2b372f..b4298b8 100644
--- a/lib/yaml/emitter.py
+++ b/lib/yaml/emitter.py
@@ -20,13 +20,26 @@ class Emitter:
self.writer = writer
self.states = []
self.state = self.expect_stream_start
- self.levels = []
- self.level = 0
- self.soft_space = False
+ self.indents = []
+ self.indent = None
+ self.flow_level = 0
+ self.key_context = False
+ self.space = True
+ self.line = True
+ self.allow_inline_collection = False
+ self.allow_indentless_sequence = False
+ self.simple_key = False
+ self.event_queue = []
def emit(self, event):
+ if self.event_queue:
+ self.event_queue.append(event)
+ event = self.event_queue.pop(0)
self.state(event)
+ def push_back(self, event):
+ self.event_queue.insert(0, event)
+
def expect_stream_start(self, event):
if isinstance(event, StreamStartEvent):
self.state = self.expect_document_start
@@ -38,6 +51,8 @@ class Emitter:
self.write_document_start()
self.states.append(self.expect_document_end)
self.state = self.expect_root_node
+ self.allow_inline_collection = False
+ self.allow_indentless_sequence = False
elif isinstance(event, StreamEndEvent):
self.writer.flush()
self.state = self.expect_nothing
@@ -48,6 +63,8 @@ class Emitter:
if isinstance(event, DocumentEndEvent):
self.write_document_end()
self.state = self.expect_document_start
+ self.allow_inline_collection = False
+ self.allow_indentless_sequence = False
else:
raiseEmitterError("expected DocumentEndEvent, but got %s" % event.__class__.__name__)
@@ -55,126 +72,313 @@ class Emitter:
self.expect_node(event)
def expect_node(self, event):
+ empty = None
+ if isinstance(event, (SequenceEvent, MappingEvent)):
+ if not self.event_queue:
+ return self.push_back(event)
+ empty = isinstance(self.event_queue[0], CollectionEndEvent)
if isinstance(event, AliasEvent):
- self.write_anchor("*", event.anchor)
- self.state = self.states.pop()
- elif isinstance(event, NodeEvent):
+ self.expect_alias(event)
+ elif isinstance(event, (ScalarEvent, SequenceEvent, MappingEvent)):
if event.anchor:
self.write_anchor("&", event.anchor)
- if event.tag:
+ self.allow_inline_collection = False
+ if event.tag not in [None, u'!']:
self.write_tag(event.tag)
+ self.allow_inline_collection = False
if isinstance(event, ScalarEvent):
- self.write_scalar(event.value)
- self.state = self.states.pop()
+ self.expect_scalar(event)
elif isinstance(event, SequenceEvent):
- self.write_collection_start("[")
- self.level += 1
- self.state = self.expect_first_sequence_item
+ self.expect_sequence(event, empty)
elif isinstance(event, MappingEvent):
- self.write_collection_start("{")
- self.level += 1
- self.state = self.expect_first_mapping_key
+ self.expect_mapping(event, empty)
else:
raise EmitterError("Expected NodeEvent, but got %s" % event.__class__.__name__)
- def expect_first_sequence_item(self, event):
+ def expect_alias(self, event):
+ self.write_anchor("*", event.anchor)
+ self.state = self.states.pop()
+
+ def expect_scalar(self, event):
+ self.indents.append(self.indent)
+ if self.indent is None:
+ self.indent = 2
+ else:
+ self.indent += 2
+ self.write_scalar(event.value, event.implicit, event.style)
+ self.indent = self.indents.pop()
+ self.state = self.states.pop()
+ self.allow_inline_collection = False
+ self.allow_indentless_sequence = False
+
+ def expect_sequence(self, event, empty):
+ if self.flow_level or event.flow or empty:
+ self.write_indicator("[", need_space=True, provide_space=True)
+ self.flow_level += 1
+ self.indents.append(self.indent)
+ if self.indent is None:
+ self.indent = 2
+ else:
+ self.indent += 2
+ self.state = self.expect_first_flow_sequence_item
+ else:
+ self.indents.append(self.indent)
+ if self.indent is None:
+ self.indent = 0
+ else:
+ self.indent += 2
+ self.state = self.expect_first_block_sequence_item
+
+ def expect_mapping(self, event, empty):
+ if self.flow_level or event.flow or empty:
+ self.write_indicator("{", need_space=True, provide_space=True)
+ self.flow_level += 1
+ self.indents.append(self.indent)
+ if self.indent is None:
+ self.indent = 2
+ else:
+ self.indent += 2
+ self.state = self.expect_first_flow_mapping_key
+ else:
+ self.indents.append(self.indent)
+ if self.indent is None:
+ self.indent = 0
+ else:
+ self.indent += 2
+ self.state = self.expect_first_block_mapping_key
+
+ def expect_first_flow_sequence_item(self, event):
if isinstance(event, CollectionEndEvent):
- self.write_collection_end("]")
+ self.indent = self.indents.pop()
+ self.write_indicator("]", provide_space=True)
+ self.flow_level -= 1
self.state = self.states.pop()
else:
self.write_indent()
- self.states.append(self.expect_sequence_item)
+ self.states.append(self.expect_flow_sequence_item)
+ self.state = self.expect_node
self.expect_node(event)
- def expect_sequence_item(self, event):
+ def expect_flow_sequence_item(self, event):
if isinstance(event, CollectionEndEvent):
- self.level -= 1
+ self.write_indicator(",")
+ self.indent = self.indents.pop()
self.write_indent()
- self.write_collection_end("]")
+ self.write_indicator("]")
+ self.flow_level -= 1
self.state = self.states.pop()
else:
self.write_indicator(",")
self.write_indent()
- self.states.append(self.expect_sequence_item)
+ self.states.append(self.expect_flow_sequence_item)
+ self.state = self.expect_node
self.expect_node(event)
-
- def expect_first_mapping_key(self, event):
+
+ def expect_first_block_sequence_item(self, event):
+ assert not isinstance(event, CollectionEndEvent)
+ if not self.allow_inline_collection:
+ if self.allow_indentless_sequence:
+ self.indent = self.indents.pop()
+ self.indents.append(self.indent)
+ self.write_indent()
+ self.write_indicator("-", need_space=True)
+ self.allow_indentless_sequence = False
+ self.allow_inline_collection = True
+ self.states.append(self.expect_block_sequence_item)
+ self.state = self.expect_node
+ self.expect_node(event)
+
+ def expect_block_sequence_item(self, event):
if isinstance(event, CollectionEndEvent):
- self.write_collection_end("}")
+ self.indent = self.indents.pop()
self.state = self.states.pop()
else:
self.write_indent()
- self.write_indicator("?")
- self.states.append(self.expect_mapping_value)
+ self.write_indicator("-", need_space=True)
+ self.allow_indentless_sequence = False
+ self.allow_inline_collection = True
+ self.states.append(self.expect_block_sequence_item)
+ self.state = self.expect_node
self.expect_node(event)
- def expect_mapping_key(self, event):
+ def expect_first_flow_mapping_key(self, event):
if isinstance(event, CollectionEndEvent):
- self.level -= 1
+ self.indent = self.indents.pop()
+ self.write_indicator("}")
+ self.flow_level -= 1
+ self.state = self.states.pop()
+ else:
self.write_indent()
- self.write_collection_end("}")
+ if self.is_simple(event):
+ self.simple_key = True
+ else:
+ self.write_indicator("?", need_space=True)
+ self.states.append(self.expect_flow_mapping_value)
+ self.state = self.expect_node
+ self.expect_node(event)
+
+ def expect_flow_mapping_key(self, event):
+ if isinstance(event, CollectionEndEvent):
+ self.indent = self.indents.pop()
+ self.write_indent()
+ self.write_indicator("}")
+ self.flow_level -= 1
self.state = self.states.pop()
else:
self.write_indicator(",")
self.write_indent()
- self.write_indicator("?")
- self.states.append(self.expect_mapping_value)
+ if self.is_simple(event):
+ self.simple_key = True
+ else:
+ self.write_indicator("?", need_space=True)
+ self.states.append(self.expect_flow_mapping_value)
+ self.state = self.expect_node
self.expect_node(event)
- def expect_mapping_value(self, event):
- self.write_indent()
- self.write_indicator(":")
- self.states.append(self.expect_mapping_key)
+ def expect_flow_mapping_value(self, event):
+ if self.simple_key:
+ self.write_indicator(":", need_space=False)
+ self.simple_key = False
+ else:
+ self.write_indent()
+ self.write_indicator(":", need_space=True)
+ self.states.append(self.expect_flow_mapping_key)
+ self.state = self.expect_node
+ self.expect_node(event)
+
+ def expect_first_block_mapping_key(self, event):
+ assert not isinstance(event, CollectionEndEvent)
+ simple = self.is_simple(event)
+ if simple is None:
+ return self.push_back(event)
+ if not self.allow_inline_collection:
+ self.write_indent()
+ if self.is_simple(event):
+ self.allow_indentless_sequence = True
+ self.allow_inline_collection = False
+ self.simple_key = True
+ else:
+ self.write_indicator("?", need_space=True)
+ self.allow_indentless_sequence = True
+ self.allow_inline_collection = True
+ self.states.append(self.expect_block_mapping_value)
+ self.state = self.expect_node
+ self.expect_node(event)
+
+ def expect_block_mapping_key(self, event):
+ if isinstance(event, CollectionEndEvent):
+ self.indent = self.indents.pop()
+ self.state = self.states.pop()
+ else:
+ self.write_indent()
+ if self.is_simple(event):
+ self.allow_indentless_sequence = True
+ self.allow_inline_collection = False
+ self.simple_key = True
+ else:
+ self.write_indicator("?", need_space=True)
+ self.allow_indentless_sequence = True
+ self.allow_inline_collection = True
+ self.states.append(self.expect_block_mapping_value)
+ self.state = self.expect_node
+ self.expect_node(event)
+
+ def expect_block_mapping_value(self, event):
+ if self.simple_key:
+ self.write_indicator(":", need_space=False)
+ self.allow_indentless_sequence = True
+ self.allow_inline_collection = False
+ self.simple_key = False
+ else:
+ self.write_indent()
+ self.write_indicator(":", need_space=True)
+ self.allow_indentless_sequence = True
+ self.allow_inline_collection = True
+ self.states.append(self.expect_block_mapping_key)
+ self.state = self.expect_node
self.expect_node(event)
def expect_nothing(self, event):
raise EmitterError("expected nothing, but got %s" % event.__class__.__name__)
def write_document_start(self):
- self.writer.write("%YAML 1.1\n")
+ self.writer.write("%YAML 1.1")
+ self.write_line_break()
self.writer.write("---")
- self.soft_space = True
+ self.space = False
+ self.line = False
def write_document_end(self):
- self.writer.write("\n...\n")
- self.soft_space = False
-
- def write_collection_start(self, indicator):
- if self.soft_space:
- self.writer.write(" ")
- self.writer.write(indicator)
- self.soft_space = False
+ if not self.line:
+ self.write_line_break()
+ self.writer.write("...")
+ self.write_line_break()
- def write_collection_end(self, indicator):
- self.writer.write(indicator)
- self.soft_space = True
+ def write_line_break(self):
+ self.writer.write('\n')
+ self.space = True
+ self.line = True
def write_anchor(self, indicator, name):
- if self.soft_space:
+ if not self.space:
self.writer.write(" ")
self.writer.write("%s%s" % (indicator, name))
- self.soft_space = True
+ self.space = False
+ self.line = False
def write_tag(self, tag):
- if self.soft_space:
+ if not self.space:
self.writer.write(" ")
if tag.startswith("tag:yaml.org,2002:"):
self.writer.write("!!%s" % tag[len("tag.yaml.org,2002:"):])
else:
self.writer.write("!<%s>" % tag)
- self.soft_space = True
+ self.space = False
+ self.line = False
- def write_scalar(self, value):
- if self.soft_space:
- self.writer.write(" ")
- self.writer.write("\"%s\"" % value.encode('utf-8'))
- self.soft_space = True
+ def is_simple(self, event):
+ if not isinstance(event, ScalarEvent):
+ return False
+ if '\n' in event.value or len(event.value) > 128:
+ return False
+ if event.style and event.style in '|>':
+ return False
+ return True
- def write_indicator(self, indicator):
+ def write_scalar(self, value, implicit, style):
+ if implicit:
+ if not self.space:
+ self.writer.write(" ")
+ self.writer.write(value.encode('utf-8'))
+ self.space = False
+ self.line = False
+ elif style in ['>', '|'] and not self.flow_level and not self.simple_key:
+ if not self.space:
+ self.writer.write(" ")
+ self.writer.write("|-")
+ self.write_line_break()
+ self.write_indent()
+ self.writer.write(value.encode('utf-8'))
+ self.write_line_break()
+ else:
+ if not self.space:
+ self.writer.write(" ")
+ self.writer.write("\"%s\"" % value.encode('utf-8'))
+ self.space = False
+ self.line = False
+
+ def write_indicator(self, indicator, need_space=False, provide_space=False):
+ if need_space and not self.space:
+ self.writer.write(" ")
self.writer.write(indicator)
- self.soft_space = True
+ self.space = provide_space
+ self.line = False
def write_indent(self):
- self.writer.write("\n"+" "*(self.level*2))
- self.soft_space = False
+ if not self.line:
+ self.write_line_break()
+ if self.indent:
+ self.writer.write(" "*self.indent)
+ self.line = False
+ self.space = True
diff --git a/tests/data/aliases.events b/tests/data/aliases.events
new file mode 100644
index 0000000..b24ab06
--- /dev/null
+++ b/tests/data/aliases.events
@@ -0,0 +1,8 @@
+- !StreamStart
+- !DocumentStart
+- !Sequence
+- !Scalar { anchor: 'myanchor', tag: '!mytag', value: 'data' }
+- !Alias { anchor: 'myanchor' }
+- !CollectionEnd
+- !DocumentEnd
+- !StreamEnd
diff --git a/tests/data/documents.events b/tests/data/documents.events
new file mode 100644
index 0000000..223a314
--- /dev/null
+++ b/tests/data/documents.events
@@ -0,0 +1,11 @@
+- !StreamStart
+- !DocumentStart
+- !Scalar { implicit: true }
+- !DocumentEnd
+- !DocumentStart
+- !Scalar { implicit: true }
+- !DocumentEnd
+- !DocumentStart
+- !Scalar { implicit: true }
+- !DocumentEnd
+- !StreamEnd
diff --git a/tests/data/mappings.events b/tests/data/mappings.events
new file mode 100644
index 0000000..bcdbd02
--- /dev/null
+++ b/tests/data/mappings.events
@@ -0,0 +1,44 @@
+- !StreamStart
+
+- !DocumentStart
+- !Mapping
+- !Scalar { implicit: true, value: 'key' }
+- !Scalar { implicit: true, value: 'value' }
+- !Scalar { implicit: true, value: 'empty mapping' }
+- !Mapping
+- !CollectionEnd
+- !Scalar { implicit: true, value: 'empty mapping with tag' }
+- !Mapping { tag: '!mytag' }
+- !CollectionEnd
+- !Scalar { implicit: true, value: 'block mapping' }
+- !Mapping
+- !Mapping
+- !Scalar { implicit: true, value: 'complex' }
+- !Scalar { implicit: true, value: 'key' }
+- !Scalar { implicit: true, value: 'complex' }
+- !Scalar { implicit: true, value: 'key' }
+- !CollectionEnd
+- !Mapping
+- !Scalar { implicit: true, value: 'complex' }
+- !Scalar { implicit: true, value: 'key' }
+- !CollectionEnd
+- !CollectionEnd
+- !Scalar { implicit: true, value: 'flow mapping' }
+- !Mapping { flow: true }
+- !Scalar { implicit: true, value: 'key' }
+- !Scalar { implicit: true, value: 'value' }
+- !Mapping
+- !Scalar { implicit: true, value: 'complex' }
+- !Scalar { implicit: true, value: 'key' }
+- !Scalar { implicit: true, value: 'complex' }
+- !Scalar { implicit: true, value: 'key' }
+- !CollectionEnd
+- !Mapping
+- !Scalar { implicit: true, value: 'complex' }
+- !Scalar { implicit: true, value: 'key' }
+- !CollectionEnd
+- !CollectionEnd
+- !CollectionEnd
+- !DocumentEnd
+
+- !StreamEnd
diff --git a/tests/data/scalars.events b/tests/data/scalars.events
new file mode 100644
index 0000000..93d4649
--- /dev/null
+++ b/tests/data/scalars.events
@@ -0,0 +1,24 @@
+- !StreamStart
+
+- !DocumentStart
+- !Mapping
+- !Scalar { implicit: true, value: 'empty scalar' }
+- !Scalar { implicit: true, value: '' }
+- !Scalar { implicit: true, value: 'implicit scalar' }
+- !Scalar { implicit: true, value: 'data' }
+- !Scalar { implicit: true, value: 'quoted scalar' }
+- !Scalar { value: 'data', style: '"' }
+- !Scalar { implicit: true, value: 'block scalar' }
+- !Scalar { value: 'data', style: '|' }
+- !Scalar { implicit: true, value: 'empty scalar with tag' }
+- !Scalar { implicit: true, tag: '!mytag', value: '' }
+- !Scalar { implicit: true, value: 'implicit scalar with tag' }
+- !Scalar { implicit: true, tag: '!mytag', value: 'data' }
+- !Scalar { implicit: true, value: 'quoted scalar with tag' }
+- !Scalar { value: 'data', style: '"', tag: '!mytag' }
+- !Scalar { implicit: true, value: 'block scalar with tag' }
+- !Scalar { value: 'data', style: '|', tag: '!mytag' }
+- !CollectionEnd
+- !DocumentEnd
+
+- !StreamEnd
diff --git a/tests/data/sequences.events b/tests/data/sequences.events
new file mode 100644
index 0000000..206f48c
--- /dev/null
+++ b/tests/data/sequences.events
@@ -0,0 +1,81 @@
+- !StreamStart
+
+- !DocumentStart
+- !Sequence
+- !CollectionEnd
+- !DocumentEnd
+
+- !DocumentStart
+- !Sequence { tag: '!mytag' }
+- !CollectionEnd
+- !DocumentEnd
+
+- !DocumentStart
+- !Sequence
+- !Sequence
+- !CollectionEnd
+- !Sequence { tag: '!mytag' }
+- !CollectionEnd
+- !Sequence
+- !Scalar
+- !Scalar { value: 'data' }
+- !Scalar { tag: '!mytag', value: 'data' }
+- !CollectionEnd
+- !Sequence
+- !Sequence
+- !Sequence
+- !Scalar
+- !CollectionEnd
+- !CollectionEnd
+- !CollectionEnd
+- !Sequence
+- !Sequence { tag: '!mytag' }
+- !Sequence
+- !Scalar { value: 'data' }
+- !CollectionEnd
+- !CollectionEnd
+- !CollectionEnd
+- !CollectionEnd
+- !DocumentEnd
+
+- !DocumentStart
+- !Sequence
+- !Mapping
+- !Scalar { value: 'key1' }
+- !Sequence
+- !Scalar { value: 'data1' }
+- !Scalar { value: 'data2' }
+- !CollectionEnd
+- !Scalar { value: 'key2' }
+- !Sequence { tag: '!mytag1' }
+- !Scalar { value: 'data3' }
+- !Sequence
+- !Scalar { value: 'data4' }
+- !Scalar { value: 'data5' }
+- !CollectionEnd
+- !Sequence { tag: '!mytag2' }
+- !Scalar { value: 'data6' }
+- !Scalar { value: 'data7' }
+- !CollectionEnd
+- !CollectionEnd
+- !CollectionEnd
+- !CollectionEnd
+- !DocumentEnd
+
+- !DocumentStart
+- !Sequence
+- !Sequence { flow: true }
+- !Sequence
+- !CollectionEnd
+- !Scalar
+- !Scalar { value: 'data' }
+- !Scalar { tag: '!mytag', value: 'data' }
+- !Sequence { tag: '!mytag' }
+- !Scalar { value: 'data' }
+- !Scalar { value: 'data' }
+- !CollectionEnd
+- !CollectionEnd
+- !CollectionEnd
+- !DocumentEnd
+
+- !StreamEnd
diff --git a/tests/test_emitter.py b/tests/test_emitter.py
index fed6953..698ea50 100644
--- a/tests/test_emitter.py
+++ b/tests/test_emitter.py
@@ -2,6 +2,7 @@
import test_appliance, sys, StringIO
from yaml import *
+import yaml
class TestEmitterOnCanonical(test_appliance.TestAppliance):
@@ -15,6 +16,58 @@ class TestEmitterOnCanonical(test_appliance.TestAppliance):
#print file(canonical_filename, 'rb').read()
for event in events:
emitter.emit(event)
+ data = writer.getvalue()
+ new_events = list(parse(data))
+ self.failUnlessEqual(len(events), len(new_events))
+ for event, new_event in zip(events, new_events):
+ self.failUnlessEqual(event.__class__, new_event.__class__)
TestEmitterOnCanonical.add_tests('testEmitterOnCanonical', '.canonical')
+class EventsConstructor(Constructor):
+
+ def construct_event(self, node):
+ if isinstance(node, ScalarNode):
+ mapping = {}
+ else:
+ mapping = self.construct_mapping(node)
+ class_name = str(node.tag[1:])+'Event'
+ if class_name in ['AliasEvent', 'ScalarEvent', 'SequenceEvent', 'MappingEvent']:
+ mapping.setdefault('anchor', None)
+ if class_name in ['ScalarEvent', 'SequenceEvent', 'MappingEvent']:
+ mapping.setdefault('tag', None)
+ if class_name == 'ScalarEvent':
+ mapping.setdefault('value', '')
+ value = getattr(yaml, class_name)(**mapping)
+ return value
+
+EventsConstructor.add_constructor(None, EventsConstructor.construct_event)
+
+class TestEmitter(test_appliance.TestAppliance):
+
+ def _testEmitter(self, test_name, events_filename):
+ events = load_document(file(events_filename, 'rb'), Constructor=EventsConstructor)
+ self._dump(events_filename, events)
+ writer = StringIO.StringIO()
+ emitter = Emitter(writer)
+ for event in events:
+ emitter.emit(event)
+ data = writer.getvalue()
+ new_events = list(parse(data))
+ self.failUnlessEqual(len(events), len(new_events))
+ for event, new_event in zip(events, new_events):
+ self.failUnlessEqual(event.__class__, new_event.__class__)
+
+ def _dump(self, events_filename, events):
+ writer = sys.stdout
+ emitter = Emitter(writer)
+ print "="*30
+ print "EVENTS:"
+ print file(events_filename, 'rb').read()
+ print '-'*30
+ print "OUTPUT:"
+ for event in events:
+ emitter.emit(event)
+
+TestEmitter.add_tests('testEmitter', '.events')
+