codekingpro/portable-devtools
114k
1 2# Emitter expects events obeying the following grammar:3# stream ::= STREAM-START document* STREAM-END4# document ::= DOCUMENT-START node DOCUMENT-END5# node ::= SCALAR | sequence | mapping6# sequence ::= SEQUENCE-START node* SEQUENCE-END7# mapping ::= MAPPING-START (node node)* MAPPING-END8 9__all__ = ['Emitter', 'EmitterError']10 11from .error import YAMLError12from .events import *13 14class EmitterError(YAMLError):15 pass16 17class ScalarAnalysis:18 def __init__(self, scalar, empty, multiline,19 allow_flow_plain, allow_block_plain,20 allow_single_quoted, allow_double_quoted,21 allow_block):22 self.scalar = scalar23 self.empty = empty24 self.multiline = multiline25 self.allow_flow_plain = allow_flow_plain26 self.allow_block_plain = allow_block_plain27 self.allow_single_quoted = allow_single_quoted28 self.allow_double_quoted = allow_double_quoted29 self.allow_block = allow_block30 31class Emitter:32 33 DEFAULT_TAG_PREFIXES = {34 '!' : '!',35 'tag:yaml.org,2002:' : '!!',36 }37 38 def __init__(self, stream, canonical=None, indent=None, width=None,39 allow_unicode=None, line_break=None):40 41 # The stream should have the methods `write` and possibly `flush`.42 self.stream = stream43 44 # Encoding can be overridden by STREAM-START.45 self.encoding = None46 47 # Emitter is a state machine with a stack of states to handle nested48 # structures.49 self.states = []50 self.state = self.expect_stream_start51 52 # Current event and the event queue.53 self.events = []54 self.event = None55 56 # The current indentation level and the stack of previous indents.57 self.indents = []58 self.indent = None59 60 # Flow level.61 self.flow_level = 062 63 # Contexts.64 self.root_context = False65 self.sequence_context = False66 self.mapping_context = False67 self.simple_key_context = False68 69 # Characteristics of the last emitted character:70 # - current position.71 # - is it a whitespace?72 # - is it an indention character73 # (indentation space, '-', '?', or ':')?74 self.line = 075 self.column = 076 self.whitespace = True77 self.indention = True78 79 # Whether the document requires an explicit document indicator80 self.open_ended = False81 82 # Formatting details.83 self.canonical = canonical84 self.allow_unicode = allow_unicode85 self.best_indent = 286 if indent and 1 < indent < 10:87 self.best_indent = indent88 self.best_width = 8089 if width and width > self.best_indent*2:90 self.best_width = width91 self.best_line_break = '\n'92 if line_break in ['\r', '\n', '\r\n']:93 self.best_line_break = line_break94 95 # Tag prefixes.96 self.tag_prefixes = None97 98 # Prepared anchor and tag.99 self.prepared_anchor = None100 self.prepared_tag = None101 102 # Scalar analysis and style.103 self.analysis = None104 self.style = None105 106 def dispose(self):107 # Reset the state attributes (to clear self-references)108 self.states = []109 self.state = None110 111 def emit(self, event):112 self.events.append(event)113 while not self.need_more_events():114 self.event = self.events.pop(0)115 self.state()116 self.event = None117 118 # In some cases, we wait for a few next events before emitting.119 120 def need_more_events(self):121 if not self.events:122 return True123 event = self.events[0]124 if isinstance(event, DocumentStartEvent):125 return self.need_events(1)126 elif isinstance(event, SequenceStartEvent):127 return self.need_events(2)128 elif isinstance(event, MappingStartEvent):129 return self.need_events(3)130 else:131 return False132 133 def need_events(self, count):134 level = 0135 for event in self.events[1:]:136 if isinstance(event, (DocumentStartEvent, CollectionStartEvent)):137 level += 1138 elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)):139 level -= 1140 elif isinstance(event, StreamEndEvent):141 level = -1142 if level < 0:143 return False144 return (len(self.events) < count+1)145 146 def increase_indent(self, flow=False, indentless=False):147 self.indents.append(self.indent)148 if self.indent is None:149 if flow:150 self.indent = self.best_indent151 else:152 self.indent = 0153 elif not indentless:154 self.indent += self.best_indent155 156 # States.157 158 # Stream handlers.159 160 def expect_stream_start(self):161 if isinstance(self.event, StreamStartEvent):162 if self.event.encoding and not hasattr(self.stream, 'encoding'):163 self.encoding = self.event.encoding164 self.write_stream_start()165 self.state = self.expect_first_document_start166 else:167 raise EmitterError("expected StreamStartEvent, but got %s"168 % self.event)169 170 def expect_nothing(self):171 raise EmitterError("expected nothing, but got %s" % self.event)172 173 # Document handlers.174 175 def expect_first_document_start(self):176 return self.expect_document_start(first=True)177 178 def expect_document_start(self, first=False):179 if isinstance(self.event, DocumentStartEvent):180 if (self.event.version or self.event.tags) and self.open_ended:181 self.write_indicator('...', True)182 self.write_indent()183 if self.event.version:184 version_text = self.prepare_version(self.event.version)185 self.write_version_directive(version_text)186 self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy()187 if self.event.tags:188 handles = sorted(self.event.tags.keys())189 for handle in handles:190 prefix = self.event.tags[handle]191 self.tag_prefixes[prefix] = handle192 handle_text = self.prepare_tag_handle(handle)193 prefix_text = self.prepare_tag_prefix(prefix)194 self.write_tag_directive(handle_text, prefix_text)195 implicit = (first and not self.event.explicit and not self.canonical196 and not self.event.version and not self.event.tags197 and not self.check_empty_document())198 if not implicit:199 self.write_indent()200 self.write_indicator('---', True)201 if self.canonical:202 self.write_indent()203 self.state = self.expect_document_root204 elif isinstance(self.event, StreamEndEvent):205 if self.open_ended:206 self.write_indicator('...', True)207 self.write_indent()208 self.write_stream_end()209 self.state = self.expect_nothing210 else:211 raise EmitterError("expected DocumentStartEvent, but got %s"212 % self.event)213 214 def expect_document_end(self):215 if isinstance(self.event, DocumentEndEvent):216 self.write_indent()217 if self.event.explicit:218 self.write_indicator('...', True)219 self.write_indent()220 self.flush_stream()221 self.state = self.expect_document_start222 else:223 raise EmitterError("expected DocumentEndEvent, but got %s"224 % self.event)225 226 def expect_document_root(self):227 self.states.append(self.expect_document_end)228 self.expect_node(root=True)229 230 # Node handlers.231 232 def expect_node(self, root=False, sequence=False, mapping=False,233 simple_key=False):234 self.root_context = root235 self.sequence_context = sequence236 self.mapping_context = mapping237 self.simple_key_context = simple_key238 if isinstance(self.event, AliasEvent):239 self.expect_alias()240 elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)):241 self.process_anchor('&')242 self.process_tag()243 if isinstance(self.event, ScalarEvent):244 self.expect_scalar()245 elif isinstance(self.event, SequenceStartEvent):246 if self.flow_level or self.canonical or self.event.flow_style \247 or self.check_empty_sequence():248 self.expect_flow_sequence()249 else:250 self.expect_block_sequence()251 elif isinstance(self.event, MappingStartEvent):252 if self.flow_level or self.canonical or self.event.flow_style \253 or self.check_empty_mapping():254 self.expect_flow_mapping()255 else:256 self.expect_block_mapping()257 else:258 raise EmitterError("expected NodeEvent, but got %s" % self.event)259 260 def expect_alias(self):261 if self.event.anchor is None:262 raise EmitterError("anchor is not specified for alias")263 self.process_anchor('*')264 self.state = self.states.pop()265 266 def expect_scalar(self):267 self.increase_indent(flow=True)268 self.process_scalar()269 self.indent = self.indents.pop()270 self.state = self.states.pop()271 272 # Flow sequence handlers.273 274 def expect_flow_sequence(self):275 self.write_indicator('[', True, whitespace=True)276 self.flow_level += 1277 self.increase_indent(flow=True)278 self.state = self.expect_first_flow_sequence_item279 280 def expect_first_flow_sequence_item(self):281 if isinstance(self.event, SequenceEndEvent):282 self.indent = self.indents.pop()283 self.flow_level -= 1284 self.write_indicator(']', False)285 self.state = self.states.pop()286 else:287 if self.canonical or self.column > self.best_width:288 self.write_indent()289 self.states.append(self.expect_flow_sequence_item)290 self.expect_node(sequence=True)291 292 def expect_flow_sequence_item(self):293 if isinstance(self.event, SequenceEndEvent):294 self.indent = self.indents.pop()295 self.flow_level -= 1296 if self.canonical:297 self.write_indicator(',', False)298 self.write_indent()299 self.write_indicator(']', False)300 self.state = self.states.pop()301 else:302 self.write_indicator(',', False)303 if self.canonical or self.column > self.best_width:304 self.write_indent()305 self.states.append(self.expect_flow_sequence_item)306 self.expect_node(sequence=True)307 308 # Flow mapping handlers.309 310 def expect_flow_mapping(self):311 self.write_indicator('{', True, whitespace=True)312 self.flow_level += 1313 self.increase_indent(flow=True)314 self.state = self.expect_first_flow_mapping_key315 316 def expect_first_flow_mapping_key(self):317 if isinstance(self.event, MappingEndEvent):318 self.indent = self.indents.pop()319 self.flow_level -= 1320 self.write_indicator('}', False)321 self.state = self.states.pop()322 else:323 if self.canonical or self.column > self.best_width:324 self.write_indent()325 if not self.canonical and self.check_simple_key():326 self.states.append(self.expect_flow_mapping_simple_value)327 self.expect_node(mapping=True, simple_key=True)328 else:329 self.write_indicator('?', True)330 self.states.append(self.expect_flow_mapping_value)331 self.expect_node(mapping=True)332 333 def expect_flow_mapping_key(self):334 if isinstance(self.event, MappingEndEvent):335 self.indent = self.indents.pop()336 self.flow_level -= 1337 if self.canonical:338 self.write_indicator(',', False)339 self.write_indent()340 self.write_indicator('}', False)341 self.state = self.states.pop()342 else:343 self.write_indicator(',', False)344 if self.canonical or self.column > self.best_width:345 self.write_indent()346 if not self.canonical and self.check_simple_key():347 self.states.append(self.expect_flow_mapping_simple_value)348 self.expect_node(mapping=True, simple_key=True)349 else:350 self.write_indicator('?', True)351 self.states.append(self.expect_flow_mapping_value)352 self.expect_node(mapping=True)353 354 def expect_flow_mapping_simple_value(self):355 self.write_indicator(':', False)356 self.states.append(self.expect_flow_mapping_key)357 self.expect_node(mapping=True)358 359 def expect_flow_mapping_value(self):360 if self.canonical or self.column > self.best_width:361 self.write_indent()362 self.write_indicator(':', True)363 self.states.append(self.expect_flow_mapping_key)364 self.expect_node(mapping=True)365 366 # Block sequence handlers.367 368 def expect_block_sequence(self):369 indentless = (self.mapping_context and not self.indention)370 self.increase_indent(flow=False, indentless=indentless)371 self.state = self.expect_first_block_sequence_item372 373 def expect_first_block_sequence_item(self):374 return self.expect_block_sequence_item(first=True)375 376 def expect_block_sequence_item(self, first=False):377 if not first and isinstance(self.event, SequenceEndEvent):378 self.indent = self.indents.pop()379 self.state = self.states.pop()380 else:381 self.write_indent()382 self.write_indicator('-', True, indention=True)383 self.states.append(self.expect_block_sequence_item)384 self.expect_node(sequence=True)385 386 # Block mapping handlers.387 388 def expect_block_mapping(self):389 self.increase_indent(flow=False)390 self.state = self.expect_first_block_mapping_key391 392 def expect_first_block_mapping_key(self):393 return self.expect_block_mapping_key(first=True)394 395 def expect_block_mapping_key(self, first=False):396 if not first and isinstance(self.event, MappingEndEvent):397 self.indent = self.indents.pop()398 self.state = self.states.pop()399 else:400 self.write_indent()401 if self.check_simple_key():402 self.states.append(self.expect_block_mapping_simple_value)403 self.expect_node(mapping=True, simple_key=True)404 else:405 self.write_indicator('?', True, indention=True)406 self.states.append(self.expect_block_mapping_value)407 self.expect_node(mapping=True)408 409 def expect_block_mapping_simple_value(self):410 self.write_indicator(':', False)411 self.states.append(self.expect_block_mapping_key)412 self.expect_node(mapping=True)413 414 def expect_block_mapping_value(self):415 self.write_indent()416 self.write_indicator(':', True, indention=True)417 self.states.append(self.expect_block_mapping_key)418 self.expect_node(mapping=True)419 420 # Checkers.421 422 def check_empty_sequence(self):423 return (isinstance(self.event, SequenceStartEvent) and self.events424 and isinstance(self.events[0], SequenceEndEvent))425 426 def check_empty_mapping(self):427 return (isinstance(self.event, MappingStartEvent) and self.events428 and isinstance(self.events[0], MappingEndEvent))429 430 def check_empty_document(self):431 if not isinstance(self.event, DocumentStartEvent) or not self.events:432 return False433 event = self.events[0]434 return (isinstance(event, ScalarEvent) and event.anchor is None435 and event.tag is None and event.implicit and event.value == '')436 437 def check_simple_key(self):438 length = 0439 if isinstance(self.event, NodeEvent) and self.event.anchor is not None:440 if self.prepared_anchor is None:441 self.prepared_anchor = self.prepare_anchor(self.event.anchor)442 length += len(self.prepared_anchor)443 if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \444 and self.event.tag is not None:445 if self.prepared_tag is None:446 self.prepared_tag = self.prepare_tag(self.event.tag)447 length += len(self.prepared_tag)448 if isinstance(self.event, ScalarEvent):449 if self.analysis is None:450 self.analysis = self.analyze_scalar(self.event.value)451 length += len(self.analysis.scalar)452 return (length < 128 and (isinstance(self.event, AliasEvent)453 or (isinstance(self.event, ScalarEvent)454 and not self.analysis.empty and not self.analysis.multiline)455 or self.check_empty_sequence() or self.check_empty_mapping()))456 457 # Anchor, Tag, and Scalar processors.458 459 def process_anchor(self, indicator):460 if self.event.anchor is None:461 self.prepared_anchor = None462 return463 if self.prepared_anchor is None:464 self.prepared_anchor = self.prepare_anchor(self.event.anchor)465 if self.prepared_anchor:466 self.write_indicator(indicator+self.prepared_anchor, True)467 self.prepared_anchor = None468 469 def process_tag(self):470 tag = self.event.tag471 if isinstance(self.event, ScalarEvent):472 if self.style is None:473 self.style = self.choose_scalar_style()474 if ((not self.canonical or tag is None) and475 ((self.style == '' and self.event.implicit[0])476 or (self.style != '' and self.event.implicit[1]))):477 self.prepared_tag = None478 return479 if self.event.implicit[0] and tag is None:480 tag = '!'481 self.prepared_tag = None482 else:483 if (not self.canonical or tag is None) and self.event.implicit:484 self.prepared_tag = None485 return486 if tag is None:487 raise EmitterError("tag is not specified")488 if self.prepared_tag is None:489 self.prepared_tag = self.prepare_tag(tag)490 if self.prepared_tag:491 self.write_indicator(self.prepared_tag, True)492 self.prepared_tag = None493 494 def choose_scalar_style(self):495 if self.analysis is None:496 self.analysis = self.analyze_scalar(self.event.value)497 if self.event.style == '"' or self.canonical:498 return '"'499 if not self.event.style and self.event.implicit[0]:500 if (not (self.simple_key_context and501 (self.analysis.empty or self.analysis.multiline))502 and (self.flow_level and self.analysis.allow_flow_plain503 or (not self.flow_level and self.analysis.allow_block_plain))):504 return ''505 if self.event.style and self.event.style in '|>':506 if (not self.flow_level and not self.simple_key_context507 and self.analysis.allow_block):508 return self.event.style509 if not self.event.style or self.event.style == '\'':510 if (self.analysis.allow_single_quoted and511 not (self.simple_key_context and self.analysis.multiline)):512 return '\''513 return '"'514 515 def process_scalar(self):516 if self.analysis is None:517 self.analysis = self.analyze_scalar(self.event.value)518 if self.style is None:519 self.style = self.choose_scalar_style()520 split = (not self.simple_key_context)521 #if self.analysis.multiline and split \522 # and (not self.style or self.style in '\'\"'):523 # self.write_indent()524 if self.style == '"':525 self.write_double_quoted(self.analysis.scalar, split)526 elif self.style == '\'':527 self.write_single_quoted(self.analysis.scalar, split)528 elif self.style == '>':529 self.write_folded(self.analysis.scalar)530 elif self.style == '|':531 self.write_literal(self.analysis.scalar)532 else:533 self.write_plain(self.analysis.scalar, split)534 self.analysis = None535 self.style = None536 537 # Analyzers.538 539 def prepare_version(self, version):540 major, minor = version541 if major != 1:542 raise EmitterError("unsupported YAML version: %d.%d" % (major, minor))543 return '%d.%d' % (major, minor)544 545 def prepare_tag_handle(self, handle):546 if not handle:547 raise EmitterError("tag handle must not be empty")548 if handle[0] != '!' or handle[-1] != '!':549 raise EmitterError("tag handle must start and end with '!': %r" % handle)550 for ch in handle[1:-1]:551 if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \552 or ch in '-_'):553 raise EmitterError("invalid character %r in the tag handle: %r"554 % (ch, handle))555 return handle556 557 def prepare_tag_prefix(self, prefix):558 if not prefix:559 raise EmitterError("tag prefix must not be empty")560 chunks = []561 start = end = 0562 if prefix[0] == '!':563 end = 1564 while end < len(prefix):565 ch = prefix[end]566 if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \567 or ch in '-;/?!:@&=+$,_.~*\'()[]':568 end += 1569 else:570 if start < end:571 chunks.append(prefix[start:end])572 start = end = end+1573 data = ch.encode('utf-8')574 for ch in data:575 chunks.append('%%%02X' % ord(ch))576 if start < end:577 chunks.append(prefix[start:end])578 return ''.join(chunks)579 580 def prepare_tag(self, tag):581 if not tag:582 raise EmitterError("tag must not be empty")583 if tag == '!':584 return tag585 handle = None586 suffix = tag587 prefixes = sorted(self.tag_prefixes.keys())588 for prefix in prefixes:589 if tag.startswith(prefix) \590 and (prefix == '!' or len(prefix) < len(tag)):591 handle = self.tag_prefixes[prefix]592 suffix = tag[len(prefix):]593 chunks = []594 start = end = 0595 while end < len(suffix):596 ch = suffix[end]597 if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \598 or ch in '-;/?:@&=+$,_.~*\'()[]' \599 or (ch == '!' and handle != '!'):600 end += 1601 else:602 if start < end:603 chunks.append(suffix[start:end])604 start = end = end+1605 data = ch.encode('utf-8')606 for ch in data:607 chunks.append('%%%02X' % ch)608 if start < end:609 chunks.append(suffix[start:end])610 suffix_text = ''.join(chunks)611 if handle:612 return '%s%s' % (handle, suffix_text)613 else:614 return '!<%s>' % suffix_text615 616 def prepare_anchor(self, anchor):617 if not anchor:618 raise EmitterError("anchor must not be empty")619 for ch in anchor:620 if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \621 or ch in '-_'):622 raise EmitterError("invalid character %r in the anchor: %r"623 % (ch, anchor))624 return anchor625 626 def analyze_scalar(self, scalar):627 628 # Empty scalar is a special case.629 if not scalar:630 return ScalarAnalysis(scalar=scalar, empty=True, multiline=False,631 allow_flow_plain=False, allow_block_plain=True,632 allow_single_quoted=True, allow_double_quoted=True,633 allow_block=False)634 635 # Indicators and special characters.636 block_indicators = False637 flow_indicators = False638 line_breaks = False639 special_characters = False640 641 # Important whitespace combinations.642 leading_space = False643 leading_break = False644 trailing_space = False645 trailing_break = False646 break_space = False647 space_break = False648 649 # Check document indicators.650 if scalar.startswith('---') or scalar.startswith('...'):651 block_indicators = True652 flow_indicators = True653 654 # First character or preceded by a whitespace.655 preceded_by_whitespace = True656 657 # Last character or followed by a whitespace.658 followed_by_whitespace = (len(scalar) == 1 or659 scalar[1] in '\0 \t\r\n\x85\u2028\u2029')660 661 # The previous character is a space.662 previous_space = False663 664 # The previous character is a break.665 previous_break = False666 667 index = 0668 while index < len(scalar):669 ch = scalar[index]670 671 # Check for indicators.672 if index == 0:673 # Leading indicators are special characters.674 if ch in '#,[]{}&*!|>\'\"%@`':675 flow_indicators = True676 block_indicators = True677 if ch in '?:':678 flow_indicators = True679 if followed_by_whitespace:680 block_indicators = True681 if ch == '-' and followed_by_whitespace:682 flow_indicators = True683 block_indicators = True684 else:685 # Some indicators cannot appear within a scalar as well.686 if ch in ',?[]{}':687 flow_indicators = True688 if ch == ':':689 flow_indicators = True690 if followed_by_whitespace:691 block_indicators = True692 if ch == '#' and preceded_by_whitespace:693 flow_indicators = True694 block_indicators = True695 696 # Check for line breaks, special, and unicode characters.697 if ch in '\n\x85\u2028\u2029':698 line_breaks = True699 if not (ch == '\n' or '\x20' <= ch <= '\x7E'):700 if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF'701 or '\uE000' <= ch <= '\uFFFD'702 or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF':703 unicode_characters = True704 if not self.allow_unicode:705 special_characters = True706 else:707 special_characters = True708 709 # Detect important whitespace combinations.710 if ch == ' ':711 if index == 0:712 leading_space = True713 if index == len(scalar)-1:714 trailing_space = True715 if previous_break:716 break_space = True717 previous_space = True718 previous_break = False719 elif ch in '\n\x85\u2028\u2029':720 if index == 0:721 leading_break = True722 if index == len(scalar)-1:723 trailing_break = True724 if previous_space:725 space_break = True726 previous_space = False727 previous_break = True728 else:729 previous_space = False730 previous_break = False731 732 # Prepare for the next character.733 index += 1734 preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029')735 followed_by_whitespace = (index+1 >= len(scalar) or736 scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029')737 738 # Let's decide what styles are allowed.739 allow_flow_plain = True740 allow_block_plain = True741 allow_single_quoted = True742 allow_double_quoted = True743 allow_block = True744 745 # Leading and trailing whitespaces are bad for plain scalars.746 if (leading_space or leading_break747 or trailing_space or trailing_break):748 allow_flow_plain = allow_block_plain = False749 750 # We do not permit trailing spaces for block scalars.751 if trailing_space:752 allow_block = False753 754 # Spaces at the beginning of a new line are only acceptable for block755 # scalars.756 if break_space:757 allow_flow_plain = allow_block_plain = allow_single_quoted = False758 759 # Spaces followed by breaks, as well as special character are only760 # allowed for double quoted scalars.761 if space_break or special_characters:762 allow_flow_plain = allow_block_plain = \763 allow_single_quoted = allow_block = False764 765 # Although the plain scalar writer supports breaks, we never emit766 # multiline plain scalars.767 if line_breaks:768 allow_flow_plain = allow_block_plain = False769 770 # Flow indicators are forbidden for flow plain scalars.771 if flow_indicators:772 allow_flow_plain = False773 774 # Block indicators are forbidden for block plain scalars.775 if block_indicators:776 allow_block_plain = False777 778 return ScalarAnalysis(scalar=scalar,779 empty=False, multiline=line_breaks,780 allow_flow_plain=allow_flow_plain,781 allow_block_plain=allow_block_plain,782 allow_single_quoted=allow_single_quoted,783 allow_double_quoted=allow_double_quoted,784 allow_block=allow_block)785 786 # Writers.787 788 def flush_stream(self):789 if hasattr(self.stream, 'flush'):790 self.stream.flush()791 792 def write_stream_start(self):793 # Write BOM if needed.794 if self.encoding and self.encoding.startswith('utf-16'):795 self.stream.write('\uFEFF'.encode(self.encoding))796 797 def write_stream_end(self):798 self.flush_stream()799 800 def write_indicator(self, indicator, need_whitespace,801 whitespace=False, indention=False):802 if self.whitespace or not need_whitespace:803 data = indicator804 else:805 data = ' '+indicator806 self.whitespace = whitespace807 self.indention = self.indention and indention808 self.column += len(data)809 self.open_ended = False810 if self.encoding:811 data = data.encode(self.encoding)812 self.stream.write(data)813 814 def write_indent(self):815 indent = self.indent or 0816 if not self.indention or self.column > indent \817 or (self.column == indent and not self.whitespace):818 self.write_line_break()819 if self.column < indent:820 self.whitespace = True821 data = ' '*(indent-self.column)822 self.column = indent823 if self.encoding:824 data = data.encode(self.encoding)825 self.stream.write(data)826 827 def write_line_break(self, data=None):828 if data is None:829 data = self.best_line_break830 self.whitespace = True831 self.indention = True832 self.line += 1833 self.column = 0834 if self.encoding:835 data = data.encode(self.encoding)836 self.stream.write(data)837 838 def write_version_directive(self, version_text):839 data = '%%YAML %s' % version_text840 if self.encoding:841 data = data.encode(self.encoding)842 self.stream.write(data)843 self.write_line_break()844 845 def write_tag_directive(self, handle_text, prefix_text):846 data = '%%TAG %s %s' % (handle_text, prefix_text)847 if self.encoding:848 data = data.encode(self.encoding)849 self.stream.write(data)850 self.write_line_break()851 852 # Scalar streams.853 854 def write_single_quoted(self, text, split=True):855 self.write_indicator('\'', True)856 spaces = False857 breaks = False858 start = end = 0859 while end <= len(text):860 ch = None861 if end < len(text):862 ch = text[end]863 if spaces:864 if ch is None or ch != ' ':865 if start+1 == end and self.column > self.best_width and split \866 and start != 0 and end != len(text):867 self.write_indent()868 else:869 data = text[start:end]870 self.column += len(data)871 if self.encoding:872 data = data.encode(self.encoding)873 self.stream.write(data)874 start = end875 elif breaks:876 if ch is None or ch not in '\n\x85\u2028\u2029':877 if text[start] == '\n':878 self.write_line_break()879 for br in text[start:end]:880 if br == '\n':881 self.write_line_break()882 else:883 self.write_line_break(br)884 self.write_indent()885 start = end886 else:887 if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'':888 if start < end:889 data = text[start:end]890 self.column += len(data)891 if self.encoding:892 data = data.encode(self.encoding)893 self.stream.write(data)894 start = end895 if ch == '\'':896 data = '\'\''897 self.column += 2898 if self.encoding:899 data = data.encode(self.encoding)900 self.stream.write(data)901 start = end + 1902 if ch is not None:903 spaces = (ch == ' ')904 breaks = (ch in '\n\x85\u2028\u2029')905 end += 1906 self.write_indicator('\'', False)907 908 ESCAPE_REPLACEMENTS = {909 '\0': '0',910 '\x07': 'a',911 '\x08': 'b',912 '\x09': 't',913 '\x0A': 'n',914 '\x0B': 'v',915 '\x0C': 'f',916 '\x0D': 'r',917 '\x1B': 'e',918 '\"': '\"',919 '\\': '\\',920 '\x85': 'N',921 '\xA0': '_',922 '\u2028': 'L',923 '\u2029': 'P',924 }925 926 def write_double_quoted(self, text, split=True):927 self.write_indicator('"', True)928 start = end = 0929 while end <= len(text):930 ch = None931 if end < len(text):932 ch = text[end]933 if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \934 or not ('\x20' <= ch <= '\x7E'935 or (self.allow_unicode936 and ('\xA0' <= ch <= '\uD7FF'937 or '\uE000' <= ch <= '\uFFFD'))):938 if start < end:939 data = text[start:end]940 self.column += len(data)941 if self.encoding:942 data = data.encode(self.encoding)943 self.stream.write(data)944 start = end945 if ch is not None:946 if ch in self.ESCAPE_REPLACEMENTS:947 data = '\\'+self.ESCAPE_REPLACEMENTS[ch]948 elif ch <= '\xFF':949 data = '\\x%02X' % ord(ch)950 elif ch <= '\uFFFF':951 data = '\\u%04X' % ord(ch)952 else:953 data = '\\U%08X' % ord(ch)954 self.column += len(data)955 if self.encoding:956 data = data.encode(self.encoding)957 self.stream.write(data)958 start = end+1959 if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \960 and self.column+(end-start) > self.best_width and split:961 data = text[start:end]+'\\'962 if start < end:963 start = end964 self.column += len(data)965 if self.encoding:966 data = data.encode(self.encoding)967 self.stream.write(data)968 self.write_indent()969 self.whitespace = False970 self.indention = False971 if text[start] == ' ':972 data = '\\'973 self.column += len(data)974 if self.encoding:975 data = data.encode(self.encoding)976 self.stream.write(data)977 end += 1978 self.write_indicator('"', False)979 980 def determine_block_hints(self, text):981 hints = ''982 if text:983 if text[0] in ' \n\x85\u2028\u2029':984 hints += str(self.best_indent)985 if text[-1] not in '\n\x85\u2028\u2029':986 hints += '-'987 elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029':988 hints += '+'989 return hints990 991 def write_folded(self, text):992 hints = self.determine_block_hints(text)993 self.write_indicator('>'+hints, True)994 if hints[-1:] == '+':995 self.open_ended = True996 self.write_line_break()997 leading_space = True998 spaces = False999 breaks = True1000 start = end = 01001 while end <= len(text):1002 ch = None1003 if end < len(text):1004 ch = text[end]1005 if breaks:1006 if ch is None or ch not in '\n\x85\u2028\u2029':1007 if not leading_space and ch is not None and ch != ' ' \1008 and text[start] == '\n':1009 self.write_line_break()1010 leading_space = (ch == ' ')1011 for br in text[start:end]:1012 if br == '\n':1013 self.write_line_break()1014 else:1015 self.write_line_break(br)1016 if ch is not None:1017 self.write_indent()1018 start = end1019 elif spaces:1020 if ch != ' ':1021 if start+1 == end and self.column > self.best_width:1022 self.write_indent()1023 else:1024 data = text[start:end]1025 self.column += len(data)1026 if self.encoding:1027 data = data.encode(self.encoding)1028 self.stream.write(data)1029 start = end1030 else:1031 if ch is None or ch in ' \n\x85\u2028\u2029':1032 data = text[start:end]1033 self.column += len(data)1034 if self.encoding:1035 data = data.encode(self.encoding)1036 self.stream.write(data)1037 if ch is None:1038 self.write_line_break()1039 start = end1040 if ch is not None:1041 breaks = (ch in '\n\x85\u2028\u2029')1042 spaces = (ch == ' ')1043 end += 11044 1045 def write_literal(self, text):1046 hints = self.determine_block_hints(text)1047 self.write_indicator('|'+hints, True)1048 if hints[-1:] == '+':1049 self.open_ended = True1050 self.write_line_break()1051 breaks = True1052 start = end = 01053 while end <= len(text):1054 ch = None1055 if end < len(text):1056 ch = text[end]1057 if breaks:1058 if ch is None or ch not in '\n\x85\u2028\u2029':1059 for br in text[start:end]:1060 if br == '\n':1061 self.write_line_break()1062 else:1063 self.write_line_break(br)1064 if ch is not None:1065 self.write_indent()1066 start = end1067 else:1068 if ch is None or ch in '\n\x85\u2028\u2029':1069 data = text[start:end]1070 if self.encoding:1071 data = data.encode(self.encoding)1072 self.stream.write(data)1073 if ch is None:1074 self.write_line_break()1075 start = end1076 if ch is not None:1077 breaks = (ch in '\n\x85\u2028\u2029')1078 end += 11079 1080 def write_plain(self, text, split=True):1081 if self.root_context:1082 self.open_ended = True1083 if not text:1084 return1085 if not self.whitespace:1086 data = ' '1087 self.column += len(data)1088 if self.encoding:1089 data = data.encode(self.encoding)1090 self.stream.write(data)1091 self.whitespace = False1092 self.indention = False1093 spaces = False1094 breaks = False1095 start = end = 01096 while end <= len(text):1097 ch = None1098 if end < len(text):1099 ch = text[end]1100 if spaces:1101 if ch != ' ':1102 if start+1 == end and self.column > self.best_width and split:1103 self.write_indent()1104 self.whitespace = False1105 self.indention = False1106 else:1107 data = text[start:end]1108 self.column += len(data)1109 if self.encoding:1110 data = data.encode(self.encoding)1111 self.stream.write(data)1112 start = end1113 elif breaks:1114 if ch not in '\n\x85\u2028\u2029':1115 if text[start] == '\n':1116 self.write_line_break()1117 for br in text[start:end]:1118 if br == '\n':1119 self.write_line_break()1120 else:1121 self.write_line_break(br)1122 self.write_indent()1123 self.whitespace = False1124 self.indention = False1125 start = end1126 else:1127 if ch is None or ch in ' \n\x85\u2028\u2029':1128 data = text[start:end]1129 self.column += len(data)1130 if self.encoding:1131 data = data.encode(self.encoding)1132 self.stream.write(data)1133 start = end1134 if ch is not None:1135 spaces = (ch == ' ')1136 breaks = (ch in '\n\x85\u2028\u2029')1137 end += 11138 