From 32bf6e148723d37bca70ebf895de9c5cbe390433 Mon Sep 17 00:00:00 2001 From: xi Date: Sat, 1 Apr 2006 21:28:20 +0000 Subject: Add block styles. git-svn-id: http://svn.pyyaml.org/pyyaml/branches/working-on-emitter@129 18f92427-320e-0410-9341-c67f048884a3 --- lib/yaml/emitter.py | 328 +++++++++++++++++++++++++++++++++++--------- tests/data/aliases.events | 8 ++ tests/data/documents.events | 11 ++ tests/data/mappings.events | 44 ++++++ tests/data/scalars.events | 24 ++++ tests/data/sequences.events | 81 +++++++++++ tests/test_emitter.py | 53 +++++++ 7 files changed, 487 insertions(+), 62 deletions(-) create mode 100644 tests/data/aliases.events create mode 100644 tests/data/documents.events create mode 100644 tests/data/mappings.events create mode 100644 tests/data/scalars.events create mode 100644 tests/data/sequences.events 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') + -- cgit v1.2.1