Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/ruamel/yaml/emitter.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
| author | shellac |
|---|---|
| date | Sat, 02 May 2020 07:14:21 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:26e78fe6e8c4 |
|---|---|
| 1 # coding: utf-8 | |
| 2 | |
| 3 from __future__ import absolute_import | |
| 4 from __future__ import print_function | |
| 5 | |
| 6 # Emitter expects events obeying the following grammar: | |
| 7 # stream ::= STREAM-START document* STREAM-END | |
| 8 # document ::= DOCUMENT-START node DOCUMENT-END | |
| 9 # node ::= SCALAR | sequence | mapping | |
| 10 # sequence ::= SEQUENCE-START node* SEQUENCE-END | |
| 11 # mapping ::= MAPPING-START (node node)* MAPPING-END | |
| 12 | |
| 13 import sys | |
| 14 from ruamel.yaml.error import YAMLError, YAMLStreamError | |
| 15 from ruamel.yaml.events import * # NOQA | |
| 16 | |
| 17 # fmt: off | |
| 18 from ruamel.yaml.compat import utf8, text_type, PY2, nprint, dbg, DBG_EVENT, \ | |
| 19 check_anchorname_char | |
| 20 # fmt: on | |
| 21 | |
| 22 if False: # MYPY | |
| 23 from typing import Any, Dict, List, Union, Text, Tuple, Optional # NOQA | |
| 24 from ruamel.yaml.compat import StreamType # NOQA | |
| 25 | |
| 26 __all__ = ['Emitter', 'EmitterError'] | |
| 27 | |
| 28 | |
| 29 class EmitterError(YAMLError): | |
| 30 pass | |
| 31 | |
| 32 | |
| 33 class ScalarAnalysis(object): | |
| 34 def __init__( | |
| 35 self, | |
| 36 scalar, | |
| 37 empty, | |
| 38 multiline, | |
| 39 allow_flow_plain, | |
| 40 allow_block_plain, | |
| 41 allow_single_quoted, | |
| 42 allow_double_quoted, | |
| 43 allow_block, | |
| 44 ): | |
| 45 # type: (Any, Any, Any, bool, bool, bool, bool, bool) -> None | |
| 46 self.scalar = scalar | |
| 47 self.empty = empty | |
| 48 self.multiline = multiline | |
| 49 self.allow_flow_plain = allow_flow_plain | |
| 50 self.allow_block_plain = allow_block_plain | |
| 51 self.allow_single_quoted = allow_single_quoted | |
| 52 self.allow_double_quoted = allow_double_quoted | |
| 53 self.allow_block = allow_block | |
| 54 | |
| 55 | |
| 56 class Indents(object): | |
| 57 # replacement for the list based stack of None/int | |
| 58 def __init__(self): | |
| 59 # type: () -> None | |
| 60 self.values = [] # type: List[Tuple[int, bool]] | |
| 61 | |
| 62 def append(self, val, seq): | |
| 63 # type: (Any, Any) -> None | |
| 64 self.values.append((val, seq)) | |
| 65 | |
| 66 def pop(self): | |
| 67 # type: () -> Any | |
| 68 return self.values.pop()[0] | |
| 69 | |
| 70 def last_seq(self): | |
| 71 # type: () -> bool | |
| 72 # return the seq(uence) value for the element added before the last one | |
| 73 # in increase_indent() | |
| 74 try: | |
| 75 return self.values[-2][1] | |
| 76 except IndexError: | |
| 77 return False | |
| 78 | |
| 79 def seq_flow_align(self, seq_indent, column): | |
| 80 # type: (int, int) -> int | |
| 81 # extra spaces because of dash | |
| 82 if len(self.values) < 2 or not self.values[-1][1]: | |
| 83 return 0 | |
| 84 # -1 for the dash | |
| 85 base = self.values[-1][0] if self.values[-1][0] is not None else 0 | |
| 86 return base + seq_indent - column - 1 | |
| 87 | |
| 88 def __len__(self): | |
| 89 # type: () -> int | |
| 90 return len(self.values) | |
| 91 | |
| 92 | |
| 93 class Emitter(object): | |
| 94 # fmt: off | |
| 95 DEFAULT_TAG_PREFIXES = { | |
| 96 u'!': u'!', | |
| 97 u'tag:yaml.org,2002:': u'!!', | |
| 98 } | |
| 99 # fmt: on | |
| 100 | |
| 101 MAX_SIMPLE_KEY_LENGTH = 128 | |
| 102 | |
| 103 def __init__( | |
| 104 self, | |
| 105 stream, | |
| 106 canonical=None, | |
| 107 indent=None, | |
| 108 width=None, | |
| 109 allow_unicode=None, | |
| 110 line_break=None, | |
| 111 block_seq_indent=None, | |
| 112 top_level_colon_align=None, | |
| 113 prefix_colon=None, | |
| 114 brace_single_entry_mapping_in_flow_sequence=None, | |
| 115 dumper=None, | |
| 116 ): | |
| 117 # type: (StreamType, Any, Optional[int], Optional[int], Optional[bool], Any, Optional[int], Optional[bool], Any, Optional[bool], Any) -> None # NOQA | |
| 118 self.dumper = dumper | |
| 119 if self.dumper is not None and getattr(self.dumper, '_emitter', None) is None: | |
| 120 self.dumper._emitter = self | |
| 121 self.stream = stream | |
| 122 | |
| 123 # Encoding can be overriden by STREAM-START. | |
| 124 self.encoding = None # type: Optional[Text] | |
| 125 self.allow_space_break = None | |
| 126 | |
| 127 # Emitter is a state machine with a stack of states to handle nested | |
| 128 # structures. | |
| 129 self.states = [] # type: List[Any] | |
| 130 self.state = self.expect_stream_start # type: Any | |
| 131 | |
| 132 # Current event and the event queue. | |
| 133 self.events = [] # type: List[Any] | |
| 134 self.event = None # type: Any | |
| 135 | |
| 136 # The current indentation level and the stack of previous indents. | |
| 137 self.indents = Indents() | |
| 138 self.indent = None # type: Optional[int] | |
| 139 | |
| 140 # flow_context is an expanding/shrinking list consisting of '{' and '[' | |
| 141 # for each unclosed flow context. If empty list that means block context | |
| 142 self.flow_context = [] # type: List[Text] | |
| 143 | |
| 144 # Contexts. | |
| 145 self.root_context = False | |
| 146 self.sequence_context = False | |
| 147 self.mapping_context = False | |
| 148 self.simple_key_context = False | |
| 149 | |
| 150 # Characteristics of the last emitted character: | |
| 151 # - current position. | |
| 152 # - is it a whitespace? | |
| 153 # - is it an indention character | |
| 154 # (indentation space, '-', '?', or ':')? | |
| 155 self.line = 0 | |
| 156 self.column = 0 | |
| 157 self.whitespace = True | |
| 158 self.indention = True | |
| 159 self.compact_seq_seq = True # dash after dash | |
| 160 self.compact_seq_map = True # key after dash | |
| 161 # self.compact_ms = False # dash after key, only when excplicit key with ? | |
| 162 self.no_newline = None # type: Optional[bool] # set if directly after `- ` | |
| 163 | |
| 164 # Whether the document requires an explicit document end indicator | |
| 165 self.open_ended = False | |
| 166 | |
| 167 # colon handling | |
| 168 self.colon = u':' | |
| 169 self.prefixed_colon = self.colon if prefix_colon is None else prefix_colon + self.colon | |
| 170 # single entry mappings in flow sequence | |
| 171 self.brace_single_entry_mapping_in_flow_sequence = ( | |
| 172 brace_single_entry_mapping_in_flow_sequence | |
| 173 ) # NOQA | |
| 174 | |
| 175 # Formatting details. | |
| 176 self.canonical = canonical | |
| 177 self.allow_unicode = allow_unicode | |
| 178 # set to False to get "\Uxxxxxxxx" for non-basic unicode like emojis | |
| 179 self.unicode_supplementary = sys.maxunicode > 0xffff | |
| 180 self.sequence_dash_offset = block_seq_indent if block_seq_indent else 0 | |
| 181 self.top_level_colon_align = top_level_colon_align | |
| 182 self.best_sequence_indent = 2 | |
| 183 self.requested_indent = indent # specific for literal zero indent | |
| 184 if indent and 1 < indent < 10: | |
| 185 self.best_sequence_indent = indent | |
| 186 self.best_map_indent = self.best_sequence_indent | |
| 187 # if self.best_sequence_indent < self.sequence_dash_offset + 1: | |
| 188 # self.best_sequence_indent = self.sequence_dash_offset + 1 | |
| 189 self.best_width = 80 | |
| 190 if width and width > self.best_sequence_indent * 2: | |
| 191 self.best_width = width | |
| 192 self.best_line_break = u'\n' # type: Any | |
| 193 if line_break in [u'\r', u'\n', u'\r\n']: | |
| 194 self.best_line_break = line_break | |
| 195 | |
| 196 # Tag prefixes. | |
| 197 self.tag_prefixes = None # type: Any | |
| 198 | |
| 199 # Prepared anchor and tag. | |
| 200 self.prepared_anchor = None # type: Any | |
| 201 self.prepared_tag = None # type: Any | |
| 202 | |
| 203 # Scalar analysis and style. | |
| 204 self.analysis = None # type: Any | |
| 205 self.style = None # type: Any | |
| 206 | |
| 207 @property | |
| 208 def stream(self): | |
| 209 # type: () -> Any | |
| 210 try: | |
| 211 return self._stream | |
| 212 except AttributeError: | |
| 213 raise YAMLStreamError('output stream needs to specified') | |
| 214 | |
| 215 @stream.setter | |
| 216 def stream(self, val): | |
| 217 # type: (Any) -> None | |
| 218 if val is None: | |
| 219 return | |
| 220 if not hasattr(val, 'write'): | |
| 221 raise YAMLStreamError('stream argument needs to have a write() method') | |
| 222 self._stream = val | |
| 223 | |
| 224 @property | |
| 225 def serializer(self): | |
| 226 # type: () -> Any | |
| 227 try: | |
| 228 if hasattr(self.dumper, 'typ'): | |
| 229 return self.dumper.serializer | |
| 230 return self.dumper._serializer | |
| 231 except AttributeError: | |
| 232 return self # cyaml | |
| 233 | |
| 234 @property | |
| 235 def flow_level(self): | |
| 236 # type: () -> int | |
| 237 return len(self.flow_context) | |
| 238 | |
| 239 def dispose(self): | |
| 240 # type: () -> None | |
| 241 # Reset the state attributes (to clear self-references) | |
| 242 self.states = [] # type: List[Any] | |
| 243 self.state = None | |
| 244 | |
| 245 def emit(self, event): | |
| 246 # type: (Any) -> None | |
| 247 if dbg(DBG_EVENT): | |
| 248 nprint(event) | |
| 249 self.events.append(event) | |
| 250 while not self.need_more_events(): | |
| 251 self.event = self.events.pop(0) | |
| 252 self.state() | |
| 253 self.event = None | |
| 254 | |
| 255 # In some cases, we wait for a few next events before emitting. | |
| 256 | |
| 257 def need_more_events(self): | |
| 258 # type: () -> bool | |
| 259 if not self.events: | |
| 260 return True | |
| 261 event = self.events[0] | |
| 262 if isinstance(event, DocumentStartEvent): | |
| 263 return self.need_events(1) | |
| 264 elif isinstance(event, SequenceStartEvent): | |
| 265 return self.need_events(2) | |
| 266 elif isinstance(event, MappingStartEvent): | |
| 267 return self.need_events(3) | |
| 268 else: | |
| 269 return False | |
| 270 | |
| 271 def need_events(self, count): | |
| 272 # type: (int) -> bool | |
| 273 level = 0 | |
| 274 for event in self.events[1:]: | |
| 275 if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): | |
| 276 level += 1 | |
| 277 elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): | |
| 278 level -= 1 | |
| 279 elif isinstance(event, StreamEndEvent): | |
| 280 level = -1 | |
| 281 if level < 0: | |
| 282 return False | |
| 283 return len(self.events) < count + 1 | |
| 284 | |
| 285 def increase_indent(self, flow=False, sequence=None, indentless=False): | |
| 286 # type: (bool, Optional[bool], bool) -> None | |
| 287 self.indents.append(self.indent, sequence) | |
| 288 if self.indent is None: # top level | |
| 289 if flow: | |
| 290 # self.indent = self.best_sequence_indent if self.indents.last_seq() else \ | |
| 291 # self.best_map_indent | |
| 292 # self.indent = self.best_sequence_indent | |
| 293 self.indent = self.requested_indent | |
| 294 else: | |
| 295 self.indent = 0 | |
| 296 elif not indentless: | |
| 297 self.indent += ( | |
| 298 self.best_sequence_indent if self.indents.last_seq() else self.best_map_indent | |
| 299 ) | |
| 300 # if self.indents.last_seq(): | |
| 301 # if self.indent == 0: # top level block sequence | |
| 302 # self.indent = self.best_sequence_indent - self.sequence_dash_offset | |
| 303 # else: | |
| 304 # self.indent += self.best_sequence_indent | |
| 305 # else: | |
| 306 # self.indent += self.best_map_indent | |
| 307 | |
| 308 # States. | |
| 309 | |
| 310 # Stream handlers. | |
| 311 | |
| 312 def expect_stream_start(self): | |
| 313 # type: () -> None | |
| 314 if isinstance(self.event, StreamStartEvent): | |
| 315 if PY2: | |
| 316 if self.event.encoding and not getattr(self.stream, 'encoding', None): | |
| 317 self.encoding = self.event.encoding | |
| 318 else: | |
| 319 if self.event.encoding and not hasattr(self.stream, 'encoding'): | |
| 320 self.encoding = self.event.encoding | |
| 321 self.write_stream_start() | |
| 322 self.state = self.expect_first_document_start | |
| 323 else: | |
| 324 raise EmitterError('expected StreamStartEvent, but got %s' % (self.event,)) | |
| 325 | |
| 326 def expect_nothing(self): | |
| 327 # type: () -> None | |
| 328 raise EmitterError('expected nothing, but got %s' % (self.event,)) | |
| 329 | |
| 330 # Document handlers. | |
| 331 | |
| 332 def expect_first_document_start(self): | |
| 333 # type: () -> Any | |
| 334 return self.expect_document_start(first=True) | |
| 335 | |
| 336 def expect_document_start(self, first=False): | |
| 337 # type: (bool) -> None | |
| 338 if isinstance(self.event, DocumentStartEvent): | |
| 339 if (self.event.version or self.event.tags) and self.open_ended: | |
| 340 self.write_indicator(u'...', True) | |
| 341 self.write_indent() | |
| 342 if self.event.version: | |
| 343 version_text = self.prepare_version(self.event.version) | |
| 344 self.write_version_directive(version_text) | |
| 345 self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() | |
| 346 if self.event.tags: | |
| 347 handles = sorted(self.event.tags.keys()) | |
| 348 for handle in handles: | |
| 349 prefix = self.event.tags[handle] | |
| 350 self.tag_prefixes[prefix] = handle | |
| 351 handle_text = self.prepare_tag_handle(handle) | |
| 352 prefix_text = self.prepare_tag_prefix(prefix) | |
| 353 self.write_tag_directive(handle_text, prefix_text) | |
| 354 implicit = ( | |
| 355 first | |
| 356 and not self.event.explicit | |
| 357 and not self.canonical | |
| 358 and not self.event.version | |
| 359 and not self.event.tags | |
| 360 and not self.check_empty_document() | |
| 361 ) | |
| 362 if not implicit: | |
| 363 self.write_indent() | |
| 364 self.write_indicator(u'---', True) | |
| 365 if self.canonical: | |
| 366 self.write_indent() | |
| 367 self.state = self.expect_document_root | |
| 368 elif isinstance(self.event, StreamEndEvent): | |
| 369 if self.open_ended: | |
| 370 self.write_indicator(u'...', True) | |
| 371 self.write_indent() | |
| 372 self.write_stream_end() | |
| 373 self.state = self.expect_nothing | |
| 374 else: | |
| 375 raise EmitterError('expected DocumentStartEvent, but got %s' % (self.event,)) | |
| 376 | |
| 377 def expect_document_end(self): | |
| 378 # type: () -> None | |
| 379 if isinstance(self.event, DocumentEndEvent): | |
| 380 self.write_indent() | |
| 381 if self.event.explicit: | |
| 382 self.write_indicator(u'...', True) | |
| 383 self.write_indent() | |
| 384 self.flush_stream() | |
| 385 self.state = self.expect_document_start | |
| 386 else: | |
| 387 raise EmitterError('expected DocumentEndEvent, but got %s' % (self.event,)) | |
| 388 | |
| 389 def expect_document_root(self): | |
| 390 # type: () -> None | |
| 391 self.states.append(self.expect_document_end) | |
| 392 self.expect_node(root=True) | |
| 393 | |
| 394 # Node handlers. | |
| 395 | |
| 396 def expect_node(self, root=False, sequence=False, mapping=False, simple_key=False): | |
| 397 # type: (bool, bool, bool, bool) -> None | |
| 398 self.root_context = root | |
| 399 self.sequence_context = sequence # not used in PyYAML | |
| 400 self.mapping_context = mapping | |
| 401 self.simple_key_context = simple_key | |
| 402 if isinstance(self.event, AliasEvent): | |
| 403 self.expect_alias() | |
| 404 elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): | |
| 405 if ( | |
| 406 self.process_anchor(u'&') | |
| 407 and isinstance(self.event, ScalarEvent) | |
| 408 and self.sequence_context | |
| 409 ): | |
| 410 self.sequence_context = False | |
| 411 self.process_tag() | |
| 412 if isinstance(self.event, ScalarEvent): | |
| 413 # nprint('@', self.indention, self.no_newline, self.column) | |
| 414 self.expect_scalar() | |
| 415 elif isinstance(self.event, SequenceStartEvent): | |
| 416 # nprint('@', self.indention, self.no_newline, self.column) | |
| 417 i2, n2 = self.indention, self.no_newline # NOQA | |
| 418 if self.event.comment: | |
| 419 if self.event.flow_style is False and self.event.comment: | |
| 420 if self.write_post_comment(self.event): | |
| 421 self.indention = False | |
| 422 self.no_newline = True | |
| 423 if self.write_pre_comment(self.event): | |
| 424 self.indention = i2 | |
| 425 self.no_newline = not self.indention | |
| 426 if ( | |
| 427 self.flow_level | |
| 428 or self.canonical | |
| 429 or self.event.flow_style | |
| 430 or self.check_empty_sequence() | |
| 431 ): | |
| 432 self.expect_flow_sequence() | |
| 433 else: | |
| 434 self.expect_block_sequence() | |
| 435 elif isinstance(self.event, MappingStartEvent): | |
| 436 if self.event.flow_style is False and self.event.comment: | |
| 437 self.write_post_comment(self.event) | |
| 438 if self.event.comment and self.event.comment[1]: | |
| 439 self.write_pre_comment(self.event) | |
| 440 if ( | |
| 441 self.flow_level | |
| 442 or self.canonical | |
| 443 or self.event.flow_style | |
| 444 or self.check_empty_mapping() | |
| 445 ): | |
| 446 self.expect_flow_mapping(single=self.event.nr_items == 1) | |
| 447 else: | |
| 448 self.expect_block_mapping() | |
| 449 else: | |
| 450 raise EmitterError('expected NodeEvent, but got %s' % (self.event,)) | |
| 451 | |
| 452 def expect_alias(self): | |
| 453 # type: () -> None | |
| 454 if self.event.anchor is None: | |
| 455 raise EmitterError('anchor is not specified for alias') | |
| 456 self.process_anchor(u'*') | |
| 457 self.state = self.states.pop() | |
| 458 | |
| 459 def expect_scalar(self): | |
| 460 # type: () -> None | |
| 461 self.increase_indent(flow=True) | |
| 462 self.process_scalar() | |
| 463 self.indent = self.indents.pop() | |
| 464 self.state = self.states.pop() | |
| 465 | |
| 466 # Flow sequence handlers. | |
| 467 | |
| 468 def expect_flow_sequence(self): | |
| 469 # type: () -> None | |
| 470 ind = self.indents.seq_flow_align(self.best_sequence_indent, self.column) | |
| 471 self.write_indicator(u' ' * ind + u'[', True, whitespace=True) | |
| 472 self.increase_indent(flow=True, sequence=True) | |
| 473 self.flow_context.append('[') | |
| 474 self.state = self.expect_first_flow_sequence_item | |
| 475 | |
| 476 def expect_first_flow_sequence_item(self): | |
| 477 # type: () -> None | |
| 478 if isinstance(self.event, SequenceEndEvent): | |
| 479 self.indent = self.indents.pop() | |
| 480 popped = self.flow_context.pop() | |
| 481 assert popped == '[' | |
| 482 self.write_indicator(u']', False) | |
| 483 if self.event.comment and self.event.comment[0]: | |
| 484 # eol comment on empty flow sequence | |
| 485 self.write_post_comment(self.event) | |
| 486 elif self.flow_level == 0: | |
| 487 self.write_line_break() | |
| 488 self.state = self.states.pop() | |
| 489 else: | |
| 490 if self.canonical or self.column > self.best_width: | |
| 491 self.write_indent() | |
| 492 self.states.append(self.expect_flow_sequence_item) | |
| 493 self.expect_node(sequence=True) | |
| 494 | |
| 495 def expect_flow_sequence_item(self): | |
| 496 # type: () -> None | |
| 497 if isinstance(self.event, SequenceEndEvent): | |
| 498 self.indent = self.indents.pop() | |
| 499 popped = self.flow_context.pop() | |
| 500 assert popped == '[' | |
| 501 if self.canonical: | |
| 502 self.write_indicator(u',', False) | |
| 503 self.write_indent() | |
| 504 self.write_indicator(u']', False) | |
| 505 if self.event.comment and self.event.comment[0]: | |
| 506 # eol comment on flow sequence | |
| 507 self.write_post_comment(self.event) | |
| 508 else: | |
| 509 self.no_newline = False | |
| 510 self.state = self.states.pop() | |
| 511 else: | |
| 512 self.write_indicator(u',', False) | |
| 513 if self.canonical or self.column > self.best_width: | |
| 514 self.write_indent() | |
| 515 self.states.append(self.expect_flow_sequence_item) | |
| 516 self.expect_node(sequence=True) | |
| 517 | |
| 518 # Flow mapping handlers. | |
| 519 | |
| 520 def expect_flow_mapping(self, single=False): | |
| 521 # type: (Optional[bool]) -> None | |
| 522 ind = self.indents.seq_flow_align(self.best_sequence_indent, self.column) | |
| 523 map_init = u'{' | |
| 524 if ( | |
| 525 single | |
| 526 and self.flow_level | |
| 527 and self.flow_context[-1] == '[' | |
| 528 and not self.canonical | |
| 529 and not self.brace_single_entry_mapping_in_flow_sequence | |
| 530 ): | |
| 531 # single map item with flow context, no curly braces necessary | |
| 532 map_init = u'' | |
| 533 self.write_indicator(u' ' * ind + map_init, True, whitespace=True) | |
| 534 self.flow_context.append(map_init) | |
| 535 self.increase_indent(flow=True, sequence=False) | |
| 536 self.state = self.expect_first_flow_mapping_key | |
| 537 | |
| 538 def expect_first_flow_mapping_key(self): | |
| 539 # type: () -> None | |
| 540 if isinstance(self.event, MappingEndEvent): | |
| 541 self.indent = self.indents.pop() | |
| 542 popped = self.flow_context.pop() | |
| 543 assert popped == '{' # empty flow mapping | |
| 544 self.write_indicator(u'}', False) | |
| 545 if self.event.comment and self.event.comment[0]: | |
| 546 # eol comment on empty mapping | |
| 547 self.write_post_comment(self.event) | |
| 548 elif self.flow_level == 0: | |
| 549 self.write_line_break() | |
| 550 self.state = self.states.pop() | |
| 551 else: | |
| 552 if self.canonical or self.column > self.best_width: | |
| 553 self.write_indent() | |
| 554 if not self.canonical and self.check_simple_key(): | |
| 555 self.states.append(self.expect_flow_mapping_simple_value) | |
| 556 self.expect_node(mapping=True, simple_key=True) | |
| 557 else: | |
| 558 self.write_indicator(u'?', True) | |
| 559 self.states.append(self.expect_flow_mapping_value) | |
| 560 self.expect_node(mapping=True) | |
| 561 | |
| 562 def expect_flow_mapping_key(self): | |
| 563 # type: () -> None | |
| 564 if isinstance(self.event, MappingEndEvent): | |
| 565 # if self.event.comment and self.event.comment[1]: | |
| 566 # self.write_pre_comment(self.event) | |
| 567 self.indent = self.indents.pop() | |
| 568 popped = self.flow_context.pop() | |
| 569 assert popped in [u'{', u''] | |
| 570 if self.canonical: | |
| 571 self.write_indicator(u',', False) | |
| 572 self.write_indent() | |
| 573 if popped != u'': | |
| 574 self.write_indicator(u'}', False) | |
| 575 if self.event.comment and self.event.comment[0]: | |
| 576 # eol comment on flow mapping, never reached on empty mappings | |
| 577 self.write_post_comment(self.event) | |
| 578 else: | |
| 579 self.no_newline = False | |
| 580 self.state = self.states.pop() | |
| 581 else: | |
| 582 self.write_indicator(u',', False) | |
| 583 if self.canonical or self.column > self.best_width: | |
| 584 self.write_indent() | |
| 585 if not self.canonical and self.check_simple_key(): | |
| 586 self.states.append(self.expect_flow_mapping_simple_value) | |
| 587 self.expect_node(mapping=True, simple_key=True) | |
| 588 else: | |
| 589 self.write_indicator(u'?', True) | |
| 590 self.states.append(self.expect_flow_mapping_value) | |
| 591 self.expect_node(mapping=True) | |
| 592 | |
| 593 def expect_flow_mapping_simple_value(self): | |
| 594 # type: () -> None | |
| 595 self.write_indicator(self.prefixed_colon, False) | |
| 596 self.states.append(self.expect_flow_mapping_key) | |
| 597 self.expect_node(mapping=True) | |
| 598 | |
| 599 def expect_flow_mapping_value(self): | |
| 600 # type: () -> None | |
| 601 if self.canonical or self.column > self.best_width: | |
| 602 self.write_indent() | |
| 603 self.write_indicator(self.prefixed_colon, True) | |
| 604 self.states.append(self.expect_flow_mapping_key) | |
| 605 self.expect_node(mapping=True) | |
| 606 | |
| 607 # Block sequence handlers. | |
| 608 | |
| 609 def expect_block_sequence(self): | |
| 610 # type: () -> None | |
| 611 if self.mapping_context: | |
| 612 indentless = not self.indention | |
| 613 else: | |
| 614 indentless = False | |
| 615 if not self.compact_seq_seq and self.column != 0: | |
| 616 self.write_line_break() | |
| 617 self.increase_indent(flow=False, sequence=True, indentless=indentless) | |
| 618 self.state = self.expect_first_block_sequence_item | |
| 619 | |
| 620 def expect_first_block_sequence_item(self): | |
| 621 # type: () -> Any | |
| 622 return self.expect_block_sequence_item(first=True) | |
| 623 | |
| 624 def expect_block_sequence_item(self, first=False): | |
| 625 # type: (bool) -> None | |
| 626 if not first and isinstance(self.event, SequenceEndEvent): | |
| 627 if self.event.comment and self.event.comment[1]: | |
| 628 # final comments on a block list e.g. empty line | |
| 629 self.write_pre_comment(self.event) | |
| 630 self.indent = self.indents.pop() | |
| 631 self.state = self.states.pop() | |
| 632 self.no_newline = False | |
| 633 else: | |
| 634 if self.event.comment and self.event.comment[1]: | |
| 635 self.write_pre_comment(self.event) | |
| 636 nonl = self.no_newline if self.column == 0 else False | |
| 637 self.write_indent() | |
| 638 ind = self.sequence_dash_offset # if len(self.indents) > 1 else 0 | |
| 639 self.write_indicator(u' ' * ind + u'-', True, indention=True) | |
| 640 if nonl or self.sequence_dash_offset + 2 > self.best_sequence_indent: | |
| 641 self.no_newline = True | |
| 642 self.states.append(self.expect_block_sequence_item) | |
| 643 self.expect_node(sequence=True) | |
| 644 | |
| 645 # Block mapping handlers. | |
| 646 | |
| 647 def expect_block_mapping(self): | |
| 648 # type: () -> None | |
| 649 if not self.mapping_context and not (self.compact_seq_map or self.column == 0): | |
| 650 self.write_line_break() | |
| 651 self.increase_indent(flow=False, sequence=False) | |
| 652 self.state = self.expect_first_block_mapping_key | |
| 653 | |
| 654 def expect_first_block_mapping_key(self): | |
| 655 # type: () -> None | |
| 656 return self.expect_block_mapping_key(first=True) | |
| 657 | |
| 658 def expect_block_mapping_key(self, first=False): | |
| 659 # type: (Any) -> None | |
| 660 if not first and isinstance(self.event, MappingEndEvent): | |
| 661 if self.event.comment and self.event.comment[1]: | |
| 662 # final comments from a doc | |
| 663 self.write_pre_comment(self.event) | |
| 664 self.indent = self.indents.pop() | |
| 665 self.state = self.states.pop() | |
| 666 else: | |
| 667 if self.event.comment and self.event.comment[1]: | |
| 668 # final comments from a doc | |
| 669 self.write_pre_comment(self.event) | |
| 670 self.write_indent() | |
| 671 if self.check_simple_key(): | |
| 672 if not isinstance( | |
| 673 self.event, (SequenceStartEvent, MappingStartEvent) | |
| 674 ): # sequence keys | |
| 675 try: | |
| 676 if self.event.style == '?': | |
| 677 self.write_indicator(u'?', True, indention=True) | |
| 678 except AttributeError: # aliases have no style | |
| 679 pass | |
| 680 self.states.append(self.expect_block_mapping_simple_value) | |
| 681 self.expect_node(mapping=True, simple_key=True) | |
| 682 if isinstance(self.event, AliasEvent): | |
| 683 self.stream.write(u' ') | |
| 684 else: | |
| 685 self.write_indicator(u'?', True, indention=True) | |
| 686 self.states.append(self.expect_block_mapping_value) | |
| 687 self.expect_node(mapping=True) | |
| 688 | |
| 689 def expect_block_mapping_simple_value(self): | |
| 690 # type: () -> None | |
| 691 if getattr(self.event, 'style', None) != '?': | |
| 692 # prefix = u'' | |
| 693 if self.indent == 0 and self.top_level_colon_align is not None: | |
| 694 # write non-prefixed colon | |
| 695 c = u' ' * (self.top_level_colon_align - self.column) + self.colon | |
| 696 else: | |
| 697 c = self.prefixed_colon | |
| 698 self.write_indicator(c, False) | |
| 699 self.states.append(self.expect_block_mapping_key) | |
| 700 self.expect_node(mapping=True) | |
| 701 | |
| 702 def expect_block_mapping_value(self): | |
| 703 # type: () -> None | |
| 704 self.write_indent() | |
| 705 self.write_indicator(self.prefixed_colon, True, indention=True) | |
| 706 self.states.append(self.expect_block_mapping_key) | |
| 707 self.expect_node(mapping=True) | |
| 708 | |
| 709 # Checkers. | |
| 710 | |
| 711 def check_empty_sequence(self): | |
| 712 # type: () -> bool | |
| 713 return ( | |
| 714 isinstance(self.event, SequenceStartEvent) | |
| 715 and bool(self.events) | |
| 716 and isinstance(self.events[0], SequenceEndEvent) | |
| 717 ) | |
| 718 | |
| 719 def check_empty_mapping(self): | |
| 720 # type: () -> bool | |
| 721 return ( | |
| 722 isinstance(self.event, MappingStartEvent) | |
| 723 and bool(self.events) | |
| 724 and isinstance(self.events[0], MappingEndEvent) | |
| 725 ) | |
| 726 | |
| 727 def check_empty_document(self): | |
| 728 # type: () -> bool | |
| 729 if not isinstance(self.event, DocumentStartEvent) or not self.events: | |
| 730 return False | |
| 731 event = self.events[0] | |
| 732 return ( | |
| 733 isinstance(event, ScalarEvent) | |
| 734 and event.anchor is None | |
| 735 and event.tag is None | |
| 736 and event.implicit | |
| 737 and event.value == "" | |
| 738 ) | |
| 739 | |
| 740 def check_simple_key(self): | |
| 741 # type: () -> bool | |
| 742 length = 0 | |
| 743 if isinstance(self.event, NodeEvent) and self.event.anchor is not None: | |
| 744 if self.prepared_anchor is None: | |
| 745 self.prepared_anchor = self.prepare_anchor(self.event.anchor) | |
| 746 length += len(self.prepared_anchor) | |
| 747 if ( | |
| 748 isinstance(self.event, (ScalarEvent, CollectionStartEvent)) | |
| 749 and self.event.tag is not None | |
| 750 ): | |
| 751 if self.prepared_tag is None: | |
| 752 self.prepared_tag = self.prepare_tag(self.event.tag) | |
| 753 length += len(self.prepared_tag) | |
| 754 if isinstance(self.event, ScalarEvent): | |
| 755 if self.analysis is None: | |
| 756 self.analysis = self.analyze_scalar(self.event.value) | |
| 757 length += len(self.analysis.scalar) | |
| 758 return length < self.MAX_SIMPLE_KEY_LENGTH and ( | |
| 759 isinstance(self.event, AliasEvent) | |
| 760 or (isinstance(self.event, SequenceStartEvent) and self.event.flow_style is True) | |
| 761 or (isinstance(self.event, MappingStartEvent) and self.event.flow_style is True) | |
| 762 or ( | |
| 763 isinstance(self.event, ScalarEvent) | |
| 764 and not self.analysis.empty | |
| 765 and not self.analysis.multiline | |
| 766 ) | |
| 767 or self.check_empty_sequence() | |
| 768 or self.check_empty_mapping() | |
| 769 ) | |
| 770 | |
| 771 # Anchor, Tag, and Scalar processors. | |
| 772 | |
| 773 def process_anchor(self, indicator): | |
| 774 # type: (Any) -> bool | |
| 775 if self.event.anchor is None: | |
| 776 self.prepared_anchor = None | |
| 777 return False | |
| 778 if self.prepared_anchor is None: | |
| 779 self.prepared_anchor = self.prepare_anchor(self.event.anchor) | |
| 780 if self.prepared_anchor: | |
| 781 self.write_indicator(indicator + self.prepared_anchor, True) | |
| 782 # issue 288 | |
| 783 self.no_newline = False | |
| 784 self.prepared_anchor = None | |
| 785 return True | |
| 786 | |
| 787 def process_tag(self): | |
| 788 # type: () -> None | |
| 789 tag = self.event.tag | |
| 790 if isinstance(self.event, ScalarEvent): | |
| 791 if self.style is None: | |
| 792 self.style = self.choose_scalar_style() | |
| 793 if (not self.canonical or tag is None) and ( | |
| 794 (self.style == "" and self.event.implicit[0]) | |
| 795 or (self.style != "" and self.event.implicit[1]) | |
| 796 ): | |
| 797 self.prepared_tag = None | |
| 798 return | |
| 799 if self.event.implicit[0] and tag is None: | |
| 800 tag = u'!' | |
| 801 self.prepared_tag = None | |
| 802 else: | |
| 803 if (not self.canonical or tag is None) and self.event.implicit: | |
| 804 self.prepared_tag = None | |
| 805 return | |
| 806 if tag is None: | |
| 807 raise EmitterError('tag is not specified') | |
| 808 if self.prepared_tag is None: | |
| 809 self.prepared_tag = self.prepare_tag(tag) | |
| 810 if self.prepared_tag: | |
| 811 self.write_indicator(self.prepared_tag, True) | |
| 812 if ( | |
| 813 self.sequence_context | |
| 814 and not self.flow_level | |
| 815 and isinstance(self.event, ScalarEvent) | |
| 816 ): | |
| 817 self.no_newline = True | |
| 818 self.prepared_tag = None | |
| 819 | |
| 820 def choose_scalar_style(self): | |
| 821 # type: () -> Any | |
| 822 if self.analysis is None: | |
| 823 self.analysis = self.analyze_scalar(self.event.value) | |
| 824 if self.event.style == '"' or self.canonical: | |
| 825 return '"' | |
| 826 if (not self.event.style or self.event.style == '?') and ( | |
| 827 self.event.implicit[0] or not self.event.implicit[2] | |
| 828 ): | |
| 829 if not ( | |
| 830 self.simple_key_context and (self.analysis.empty or self.analysis.multiline) | |
| 831 ) and ( | |
| 832 self.flow_level | |
| 833 and self.analysis.allow_flow_plain | |
| 834 or (not self.flow_level and self.analysis.allow_block_plain) | |
| 835 ): | |
| 836 return "" | |
| 837 self.analysis.allow_block = True | |
| 838 if self.event.style and self.event.style in '|>': | |
| 839 if ( | |
| 840 not self.flow_level | |
| 841 and not self.simple_key_context | |
| 842 and self.analysis.allow_block | |
| 843 ): | |
| 844 return self.event.style | |
| 845 if not self.event.style and self.analysis.allow_double_quoted: | |
| 846 if "'" in self.event.value or '\n' in self.event.value: | |
| 847 return '"' | |
| 848 if not self.event.style or self.event.style == "'": | |
| 849 if self.analysis.allow_single_quoted and not ( | |
| 850 self.simple_key_context and self.analysis.multiline | |
| 851 ): | |
| 852 return "'" | |
| 853 return '"' | |
| 854 | |
| 855 def process_scalar(self): | |
| 856 # type: () -> None | |
| 857 if self.analysis is None: | |
| 858 self.analysis = self.analyze_scalar(self.event.value) | |
| 859 if self.style is None: | |
| 860 self.style = self.choose_scalar_style() | |
| 861 split = not self.simple_key_context | |
| 862 # if self.analysis.multiline and split \ | |
| 863 # and (not self.style or self.style in '\'\"'): | |
| 864 # self.write_indent() | |
| 865 # nprint('xx', self.sequence_context, self.flow_level) | |
| 866 if self.sequence_context and not self.flow_level: | |
| 867 self.write_indent() | |
| 868 if self.style == '"': | |
| 869 self.write_double_quoted(self.analysis.scalar, split) | |
| 870 elif self.style == "'": | |
| 871 self.write_single_quoted(self.analysis.scalar, split) | |
| 872 elif self.style == '>': | |
| 873 self.write_folded(self.analysis.scalar) | |
| 874 elif self.style == '|': | |
| 875 self.write_literal(self.analysis.scalar, self.event.comment) | |
| 876 else: | |
| 877 self.write_plain(self.analysis.scalar, split) | |
| 878 self.analysis = None | |
| 879 self.style = None | |
| 880 if self.event.comment: | |
| 881 self.write_post_comment(self.event) | |
| 882 | |
| 883 # Analyzers. | |
| 884 | |
| 885 def prepare_version(self, version): | |
| 886 # type: (Any) -> Any | |
| 887 major, minor = version | |
| 888 if major != 1: | |
| 889 raise EmitterError('unsupported YAML version: %d.%d' % (major, minor)) | |
| 890 return u'%d.%d' % (major, minor) | |
| 891 | |
| 892 def prepare_tag_handle(self, handle): | |
| 893 # type: (Any) -> Any | |
| 894 if not handle: | |
| 895 raise EmitterError('tag handle must not be empty') | |
| 896 if handle[0] != u'!' or handle[-1] != u'!': | |
| 897 raise EmitterError("tag handle must start and end with '!': %r" % (utf8(handle))) | |
| 898 for ch in handle[1:-1]: | |
| 899 if not ( | |
| 900 u'0' <= ch <= u'9' or u'A' <= ch <= u'Z' or u'a' <= ch <= u'z' or ch in u'-_' | |
| 901 ): | |
| 902 raise EmitterError( | |
| 903 'invalid character %r in the tag handle: %r' % (utf8(ch), utf8(handle)) | |
| 904 ) | |
| 905 return handle | |
| 906 | |
| 907 def prepare_tag_prefix(self, prefix): | |
| 908 # type: (Any) -> Any | |
| 909 if not prefix: | |
| 910 raise EmitterError('tag prefix must not be empty') | |
| 911 chunks = [] # type: List[Any] | |
| 912 start = end = 0 | |
| 913 if prefix[0] == u'!': | |
| 914 end = 1 | |
| 915 while end < len(prefix): | |
| 916 ch = prefix[end] | |
| 917 if ( | |
| 918 u'0' <= ch <= u'9' | |
| 919 or u'A' <= ch <= u'Z' | |
| 920 or u'a' <= ch <= u'z' | |
| 921 or ch in u"-;/?!:@&=+$,_.~*'()[]" | |
| 922 ): | |
| 923 end += 1 | |
| 924 else: | |
| 925 if start < end: | |
| 926 chunks.append(prefix[start:end]) | |
| 927 start = end = end + 1 | |
| 928 data = utf8(ch) | |
| 929 for ch in data: | |
| 930 chunks.append(u'%%%02X' % ord(ch)) | |
| 931 if start < end: | |
| 932 chunks.append(prefix[start:end]) | |
| 933 return "".join(chunks) | |
| 934 | |
| 935 def prepare_tag(self, tag): | |
| 936 # type: (Any) -> Any | |
| 937 if not tag: | |
| 938 raise EmitterError('tag must not be empty') | |
| 939 if tag == u'!': | |
| 940 return tag | |
| 941 handle = None | |
| 942 suffix = tag | |
| 943 prefixes = sorted(self.tag_prefixes.keys()) | |
| 944 for prefix in prefixes: | |
| 945 if tag.startswith(prefix) and (prefix == u'!' or len(prefix) < len(tag)): | |
| 946 handle = self.tag_prefixes[prefix] | |
| 947 suffix = tag[len(prefix) :] | |
| 948 chunks = [] # type: List[Any] | |
| 949 start = end = 0 | |
| 950 while end < len(suffix): | |
| 951 ch = suffix[end] | |
| 952 if ( | |
| 953 u'0' <= ch <= u'9' | |
| 954 or u'A' <= ch <= u'Z' | |
| 955 or u'a' <= ch <= u'z' | |
| 956 or ch in u"-;/?:@&=+$,_.~*'()[]" | |
| 957 or (ch == u'!' and handle != u'!') | |
| 958 ): | |
| 959 end += 1 | |
| 960 else: | |
| 961 if start < end: | |
| 962 chunks.append(suffix[start:end]) | |
| 963 start = end = end + 1 | |
| 964 data = utf8(ch) | |
| 965 for ch in data: | |
| 966 chunks.append(u'%%%02X' % ord(ch)) | |
| 967 if start < end: | |
| 968 chunks.append(suffix[start:end]) | |
| 969 suffix_text = "".join(chunks) | |
| 970 if handle: | |
| 971 return u'%s%s' % (handle, suffix_text) | |
| 972 else: | |
| 973 return u'!<%s>' % suffix_text | |
| 974 | |
| 975 def prepare_anchor(self, anchor): | |
| 976 # type: (Any) -> Any | |
| 977 if not anchor: | |
| 978 raise EmitterError('anchor must not be empty') | |
| 979 for ch in anchor: | |
| 980 if not check_anchorname_char(ch): | |
| 981 raise EmitterError( | |
| 982 'invalid character %r in the anchor: %r' % (utf8(ch), utf8(anchor)) | |
| 983 ) | |
| 984 return anchor | |
| 985 | |
| 986 def analyze_scalar(self, scalar): | |
| 987 # type: (Any) -> Any | |
| 988 # Empty scalar is a special case. | |
| 989 if not scalar: | |
| 990 return ScalarAnalysis( | |
| 991 scalar=scalar, | |
| 992 empty=True, | |
| 993 multiline=False, | |
| 994 allow_flow_plain=False, | |
| 995 allow_block_plain=True, | |
| 996 allow_single_quoted=True, | |
| 997 allow_double_quoted=True, | |
| 998 allow_block=False, | |
| 999 ) | |
| 1000 | |
| 1001 # Indicators and special characters. | |
| 1002 block_indicators = False | |
| 1003 flow_indicators = False | |
| 1004 line_breaks = False | |
| 1005 special_characters = False | |
| 1006 | |
| 1007 # Important whitespace combinations. | |
| 1008 leading_space = False | |
| 1009 leading_break = False | |
| 1010 trailing_space = False | |
| 1011 trailing_break = False | |
| 1012 break_space = False | |
| 1013 space_break = False | |
| 1014 | |
| 1015 # Check document indicators. | |
| 1016 if scalar.startswith(u'---') or scalar.startswith(u'...'): | |
| 1017 block_indicators = True | |
| 1018 flow_indicators = True | |
| 1019 | |
| 1020 # First character or preceded by a whitespace. | |
| 1021 preceeded_by_whitespace = True | |
| 1022 | |
| 1023 # Last character or followed by a whitespace. | |
| 1024 followed_by_whitespace = len(scalar) == 1 or scalar[1] in u'\0 \t\r\n\x85\u2028\u2029' | |
| 1025 | |
| 1026 # The previous character is a space. | |
| 1027 previous_space = False | |
| 1028 | |
| 1029 # The previous character is a break. | |
| 1030 previous_break = False | |
| 1031 | |
| 1032 index = 0 | |
| 1033 while index < len(scalar): | |
| 1034 ch = scalar[index] | |
| 1035 | |
| 1036 # Check for indicators. | |
| 1037 if index == 0: | |
| 1038 # Leading indicators are special characters. | |
| 1039 if ch in u'#,[]{}&*!|>\'"%@`': | |
| 1040 flow_indicators = True | |
| 1041 block_indicators = True | |
| 1042 if ch in u'?:': # ToDo | |
| 1043 if self.serializer.use_version == (1, 1): | |
| 1044 flow_indicators = True | |
| 1045 elif len(scalar) == 1: # single character | |
| 1046 flow_indicators = True | |
| 1047 if followed_by_whitespace: | |
| 1048 block_indicators = True | |
| 1049 if ch == u'-' and followed_by_whitespace: | |
| 1050 flow_indicators = True | |
| 1051 block_indicators = True | |
| 1052 else: | |
| 1053 # Some indicators cannot appear within a scalar as well. | |
| 1054 if ch in u',[]{}': # http://yaml.org/spec/1.2/spec.html#id2788859 | |
| 1055 flow_indicators = True | |
| 1056 if ch == u'?' and self.serializer.use_version == (1, 1): | |
| 1057 flow_indicators = True | |
| 1058 if ch == u':': | |
| 1059 if followed_by_whitespace: | |
| 1060 flow_indicators = True | |
| 1061 block_indicators = True | |
| 1062 if ch == u'#' and preceeded_by_whitespace: | |
| 1063 flow_indicators = True | |
| 1064 block_indicators = True | |
| 1065 | |
| 1066 # Check for line breaks, special, and unicode characters. | |
| 1067 if ch in u'\n\x85\u2028\u2029': | |
| 1068 line_breaks = True | |
| 1069 if not (ch == u'\n' or u'\x20' <= ch <= u'\x7E'): | |
| 1070 if ( | |
| 1071 ch == u'\x85' | |
| 1072 or u'\xA0' <= ch <= u'\uD7FF' | |
| 1073 or u'\uE000' <= ch <= u'\uFFFD' | |
| 1074 or (self.unicode_supplementary and (u'\U00010000' <= ch <= u'\U0010FFFF')) | |
| 1075 ) and ch != u'\uFEFF': | |
| 1076 # unicode_characters = True | |
| 1077 if not self.allow_unicode: | |
| 1078 special_characters = True | |
| 1079 else: | |
| 1080 special_characters = True | |
| 1081 | |
| 1082 # Detect important whitespace combinations. | |
| 1083 if ch == u' ': | |
| 1084 if index == 0: | |
| 1085 leading_space = True | |
| 1086 if index == len(scalar) - 1: | |
| 1087 trailing_space = True | |
| 1088 if previous_break: | |
| 1089 break_space = True | |
| 1090 previous_space = True | |
| 1091 previous_break = False | |
| 1092 elif ch in u'\n\x85\u2028\u2029': | |
| 1093 if index == 0: | |
| 1094 leading_break = True | |
| 1095 if index == len(scalar) - 1: | |
| 1096 trailing_break = True | |
| 1097 if previous_space: | |
| 1098 space_break = True | |
| 1099 previous_space = False | |
| 1100 previous_break = True | |
| 1101 else: | |
| 1102 previous_space = False | |
| 1103 previous_break = False | |
| 1104 | |
| 1105 # Prepare for the next character. | |
| 1106 index += 1 | |
| 1107 preceeded_by_whitespace = ch in u'\0 \t\r\n\x85\u2028\u2029' | |
| 1108 followed_by_whitespace = ( | |
| 1109 index + 1 >= len(scalar) or scalar[index + 1] in u'\0 \t\r\n\x85\u2028\u2029' | |
| 1110 ) | |
| 1111 | |
| 1112 # Let's decide what styles are allowed. | |
| 1113 allow_flow_plain = True | |
| 1114 allow_block_plain = True | |
| 1115 allow_single_quoted = True | |
| 1116 allow_double_quoted = True | |
| 1117 allow_block = True | |
| 1118 | |
| 1119 # Leading and trailing whitespaces are bad for plain scalars. | |
| 1120 if leading_space or leading_break or trailing_space or trailing_break: | |
| 1121 allow_flow_plain = allow_block_plain = False | |
| 1122 | |
| 1123 # We do not permit trailing spaces for block scalars. | |
| 1124 if trailing_space: | |
| 1125 allow_block = False | |
| 1126 | |
| 1127 # Spaces at the beginning of a new line are only acceptable for block | |
| 1128 # scalars. | |
| 1129 if break_space: | |
| 1130 allow_flow_plain = allow_block_plain = allow_single_quoted = False | |
| 1131 | |
| 1132 # Spaces followed by breaks, as well as special character are only | |
| 1133 # allowed for double quoted scalars. | |
| 1134 if special_characters: | |
| 1135 allow_flow_plain = allow_block_plain = allow_single_quoted = allow_block = False | |
| 1136 elif space_break: | |
| 1137 allow_flow_plain = allow_block_plain = allow_single_quoted = False | |
| 1138 if not self.allow_space_break: | |
| 1139 allow_block = False | |
| 1140 | |
| 1141 # Although the plain scalar writer supports breaks, we never emit | |
| 1142 # multiline plain scalars. | |
| 1143 if line_breaks: | |
| 1144 allow_flow_plain = allow_block_plain = False | |
| 1145 | |
| 1146 # Flow indicators are forbidden for flow plain scalars. | |
| 1147 if flow_indicators: | |
| 1148 allow_flow_plain = False | |
| 1149 | |
| 1150 # Block indicators are forbidden for block plain scalars. | |
| 1151 if block_indicators: | |
| 1152 allow_block_plain = False | |
| 1153 | |
| 1154 return ScalarAnalysis( | |
| 1155 scalar=scalar, | |
| 1156 empty=False, | |
| 1157 multiline=line_breaks, | |
| 1158 allow_flow_plain=allow_flow_plain, | |
| 1159 allow_block_plain=allow_block_plain, | |
| 1160 allow_single_quoted=allow_single_quoted, | |
| 1161 allow_double_quoted=allow_double_quoted, | |
| 1162 allow_block=allow_block, | |
| 1163 ) | |
| 1164 | |
| 1165 # Writers. | |
| 1166 | |
| 1167 def flush_stream(self): | |
| 1168 # type: () -> None | |
| 1169 if hasattr(self.stream, 'flush'): | |
| 1170 self.stream.flush() | |
| 1171 | |
| 1172 def write_stream_start(self): | |
| 1173 # type: () -> None | |
| 1174 # Write BOM if needed. | |
| 1175 if self.encoding and self.encoding.startswith('utf-16'): | |
| 1176 self.stream.write(u'\uFEFF'.encode(self.encoding)) | |
| 1177 | |
| 1178 def write_stream_end(self): | |
| 1179 # type: () -> None | |
| 1180 self.flush_stream() | |
| 1181 | |
| 1182 def write_indicator(self, indicator, need_whitespace, whitespace=False, indention=False): | |
| 1183 # type: (Any, Any, bool, bool) -> None | |
| 1184 if self.whitespace or not need_whitespace: | |
| 1185 data = indicator | |
| 1186 else: | |
| 1187 data = u' ' + indicator | |
| 1188 self.whitespace = whitespace | |
| 1189 self.indention = self.indention and indention | |
| 1190 self.column += len(data) | |
| 1191 self.open_ended = False | |
| 1192 if bool(self.encoding): | |
| 1193 data = data.encode(self.encoding) | |
| 1194 self.stream.write(data) | |
| 1195 | |
| 1196 def write_indent(self): | |
| 1197 # type: () -> None | |
| 1198 indent = self.indent or 0 | |
| 1199 if ( | |
| 1200 not self.indention | |
| 1201 or self.column > indent | |
| 1202 or (self.column == indent and not self.whitespace) | |
| 1203 ): | |
| 1204 if bool(self.no_newline): | |
| 1205 self.no_newline = False | |
| 1206 else: | |
| 1207 self.write_line_break() | |
| 1208 if self.column < indent: | |
| 1209 self.whitespace = True | |
| 1210 data = u' ' * (indent - self.column) | |
| 1211 self.column = indent | |
| 1212 if self.encoding: | |
| 1213 data = data.encode(self.encoding) | |
| 1214 self.stream.write(data) | |
| 1215 | |
| 1216 def write_line_break(self, data=None): | |
| 1217 # type: (Any) -> None | |
| 1218 if data is None: | |
| 1219 data = self.best_line_break | |
| 1220 self.whitespace = True | |
| 1221 self.indention = True | |
| 1222 self.line += 1 | |
| 1223 self.column = 0 | |
| 1224 if bool(self.encoding): | |
| 1225 data = data.encode(self.encoding) | |
| 1226 self.stream.write(data) | |
| 1227 | |
| 1228 def write_version_directive(self, version_text): | |
| 1229 # type: (Any) -> None | |
| 1230 data = u'%%YAML %s' % version_text | |
| 1231 if self.encoding: | |
| 1232 data = data.encode(self.encoding) | |
| 1233 self.stream.write(data) | |
| 1234 self.write_line_break() | |
| 1235 | |
| 1236 def write_tag_directive(self, handle_text, prefix_text): | |
| 1237 # type: (Any, Any) -> None | |
| 1238 data = u'%%TAG %s %s' % (handle_text, prefix_text) | |
| 1239 if self.encoding: | |
| 1240 data = data.encode(self.encoding) | |
| 1241 self.stream.write(data) | |
| 1242 self.write_line_break() | |
| 1243 | |
| 1244 # Scalar streams. | |
| 1245 | |
| 1246 def write_single_quoted(self, text, split=True): | |
| 1247 # type: (Any, Any) -> None | |
| 1248 if self.root_context: | |
| 1249 if self.requested_indent is not None: | |
| 1250 self.write_line_break() | |
| 1251 if self.requested_indent != 0: | |
| 1252 self.write_indent() | |
| 1253 self.write_indicator(u"'", True) | |
| 1254 spaces = False | |
| 1255 breaks = False | |
| 1256 start = end = 0 | |
| 1257 while end <= len(text): | |
| 1258 ch = None | |
| 1259 if end < len(text): | |
| 1260 ch = text[end] | |
| 1261 if spaces: | |
| 1262 if ch is None or ch != u' ': | |
| 1263 if ( | |
| 1264 start + 1 == end | |
| 1265 and self.column > self.best_width | |
| 1266 and split | |
| 1267 and start != 0 | |
| 1268 and end != len(text) | |
| 1269 ): | |
| 1270 self.write_indent() | |
| 1271 else: | |
| 1272 data = text[start:end] | |
| 1273 self.column += len(data) | |
| 1274 if bool(self.encoding): | |
| 1275 data = data.encode(self.encoding) | |
| 1276 self.stream.write(data) | |
| 1277 start = end | |
| 1278 elif breaks: | |
| 1279 if ch is None or ch not in u'\n\x85\u2028\u2029': | |
| 1280 if text[start] == u'\n': | |
| 1281 self.write_line_break() | |
| 1282 for br in text[start:end]: | |
| 1283 if br == u'\n': | |
| 1284 self.write_line_break() | |
| 1285 else: | |
| 1286 self.write_line_break(br) | |
| 1287 self.write_indent() | |
| 1288 start = end | |
| 1289 else: | |
| 1290 if ch is None or ch in u' \n\x85\u2028\u2029' or ch == u"'": | |
| 1291 if start < end: | |
| 1292 data = text[start:end] | |
| 1293 self.column += len(data) | |
| 1294 if bool(self.encoding): | |
| 1295 data = data.encode(self.encoding) | |
| 1296 self.stream.write(data) | |
| 1297 start = end | |
| 1298 if ch == u"'": | |
| 1299 data = u"''" | |
| 1300 self.column += 2 | |
| 1301 if bool(self.encoding): | |
| 1302 data = data.encode(self.encoding) | |
| 1303 self.stream.write(data) | |
| 1304 start = end + 1 | |
| 1305 if ch is not None: | |
| 1306 spaces = ch == u' ' | |
| 1307 breaks = ch in u'\n\x85\u2028\u2029' | |
| 1308 end += 1 | |
| 1309 self.write_indicator(u"'", False) | |
| 1310 | |
| 1311 ESCAPE_REPLACEMENTS = { | |
| 1312 u'\0': u'0', | |
| 1313 u'\x07': u'a', | |
| 1314 u'\x08': u'b', | |
| 1315 u'\x09': u't', | |
| 1316 u'\x0A': u'n', | |
| 1317 u'\x0B': u'v', | |
| 1318 u'\x0C': u'f', | |
| 1319 u'\x0D': u'r', | |
| 1320 u'\x1B': u'e', | |
| 1321 u'"': u'"', | |
| 1322 u'\\': u'\\', | |
| 1323 u'\x85': u'N', | |
| 1324 u'\xA0': u'_', | |
| 1325 u'\u2028': u'L', | |
| 1326 u'\u2029': u'P', | |
| 1327 } | |
| 1328 | |
| 1329 def write_double_quoted(self, text, split=True): | |
| 1330 # type: (Any, Any) -> None | |
| 1331 if self.root_context: | |
| 1332 if self.requested_indent is not None: | |
| 1333 self.write_line_break() | |
| 1334 if self.requested_indent != 0: | |
| 1335 self.write_indent() | |
| 1336 self.write_indicator(u'"', True) | |
| 1337 start = end = 0 | |
| 1338 while end <= len(text): | |
| 1339 ch = None | |
| 1340 if end < len(text): | |
| 1341 ch = text[end] | |
| 1342 if ( | |
| 1343 ch is None | |
| 1344 or ch in u'"\\\x85\u2028\u2029\uFEFF' | |
| 1345 or not ( | |
| 1346 u'\x20' <= ch <= u'\x7E' | |
| 1347 or ( | |
| 1348 self.allow_unicode | |
| 1349 and (u'\xA0' <= ch <= u'\uD7FF' or u'\uE000' <= ch <= u'\uFFFD') | |
| 1350 ) | |
| 1351 ) | |
| 1352 ): | |
| 1353 if start < end: | |
| 1354 data = text[start:end] | |
| 1355 self.column += len(data) | |
| 1356 if bool(self.encoding): | |
| 1357 data = data.encode(self.encoding) | |
| 1358 self.stream.write(data) | |
| 1359 start = end | |
| 1360 if ch is not None: | |
| 1361 if ch in self.ESCAPE_REPLACEMENTS: | |
| 1362 data = u'\\' + self.ESCAPE_REPLACEMENTS[ch] | |
| 1363 elif ch <= u'\xFF': | |
| 1364 data = u'\\x%02X' % ord(ch) | |
| 1365 elif ch <= u'\uFFFF': | |
| 1366 data = u'\\u%04X' % ord(ch) | |
| 1367 else: | |
| 1368 data = u'\\U%08X' % ord(ch) | |
| 1369 self.column += len(data) | |
| 1370 if bool(self.encoding): | |
| 1371 data = data.encode(self.encoding) | |
| 1372 self.stream.write(data) | |
| 1373 start = end + 1 | |
| 1374 if ( | |
| 1375 0 < end < len(text) - 1 | |
| 1376 and (ch == u' ' or start >= end) | |
| 1377 and self.column + (end - start) > self.best_width | |
| 1378 and split | |
| 1379 ): | |
| 1380 data = text[start:end] + u'\\' | |
| 1381 if start < end: | |
| 1382 start = end | |
| 1383 self.column += len(data) | |
| 1384 if bool(self.encoding): | |
| 1385 data = data.encode(self.encoding) | |
| 1386 self.stream.write(data) | |
| 1387 self.write_indent() | |
| 1388 self.whitespace = False | |
| 1389 self.indention = False | |
| 1390 if text[start] == u' ': | |
| 1391 data = u'\\' | |
| 1392 self.column += len(data) | |
| 1393 if bool(self.encoding): | |
| 1394 data = data.encode(self.encoding) | |
| 1395 self.stream.write(data) | |
| 1396 end += 1 | |
| 1397 self.write_indicator(u'"', False) | |
| 1398 | |
| 1399 def determine_block_hints(self, text): | |
| 1400 # type: (Any) -> Any | |
| 1401 indent = 0 | |
| 1402 indicator = u'' | |
| 1403 hints = u'' | |
| 1404 if text: | |
| 1405 if text[0] in u' \n\x85\u2028\u2029': | |
| 1406 indent = self.best_sequence_indent | |
| 1407 hints += text_type(indent) | |
| 1408 elif self.root_context: | |
| 1409 for end in ['\n---', '\n...']: | |
| 1410 pos = 0 | |
| 1411 while True: | |
| 1412 pos = text.find(end, pos) | |
| 1413 if pos == -1: | |
| 1414 break | |
| 1415 try: | |
| 1416 if text[pos + 4] in ' \r\n': | |
| 1417 break | |
| 1418 except IndexError: | |
| 1419 pass | |
| 1420 pos += 1 | |
| 1421 if pos > -1: | |
| 1422 break | |
| 1423 if pos > 0: | |
| 1424 indent = self.best_sequence_indent | |
| 1425 if text[-1] not in u'\n\x85\u2028\u2029': | |
| 1426 indicator = u'-' | |
| 1427 elif len(text) == 1 or text[-2] in u'\n\x85\u2028\u2029': | |
| 1428 indicator = u'+' | |
| 1429 hints += indicator | |
| 1430 return hints, indent, indicator | |
| 1431 | |
| 1432 def write_folded(self, text): | |
| 1433 # type: (Any) -> None | |
| 1434 hints, _indent, _indicator = self.determine_block_hints(text) | |
| 1435 self.write_indicator(u'>' + hints, True) | |
| 1436 if _indicator == u'+': | |
| 1437 self.open_ended = True | |
| 1438 self.write_line_break() | |
| 1439 leading_space = True | |
| 1440 spaces = False | |
| 1441 breaks = True | |
| 1442 start = end = 0 | |
| 1443 while end <= len(text): | |
| 1444 ch = None | |
| 1445 if end < len(text): | |
| 1446 ch = text[end] | |
| 1447 if breaks: | |
| 1448 if ch is None or ch not in u'\n\x85\u2028\u2029\a': | |
| 1449 if ( | |
| 1450 not leading_space | |
| 1451 and ch is not None | |
| 1452 and ch != u' ' | |
| 1453 and text[start] == u'\n' | |
| 1454 ): | |
| 1455 self.write_line_break() | |
| 1456 leading_space = ch == u' ' | |
| 1457 for br in text[start:end]: | |
| 1458 if br == u'\n': | |
| 1459 self.write_line_break() | |
| 1460 else: | |
| 1461 self.write_line_break(br) | |
| 1462 if ch is not None: | |
| 1463 self.write_indent() | |
| 1464 start = end | |
| 1465 elif spaces: | |
| 1466 if ch != u' ': | |
| 1467 if start + 1 == end and self.column > self.best_width: | |
| 1468 self.write_indent() | |
| 1469 else: | |
| 1470 data = text[start:end] | |
| 1471 self.column += len(data) | |
| 1472 if bool(self.encoding): | |
| 1473 data = data.encode(self.encoding) | |
| 1474 self.stream.write(data) | |
| 1475 start = end | |
| 1476 else: | |
| 1477 if ch is None or ch in u' \n\x85\u2028\u2029\a': | |
| 1478 data = text[start:end] | |
| 1479 self.column += len(data) | |
| 1480 if bool(self.encoding): | |
| 1481 data = data.encode(self.encoding) | |
| 1482 self.stream.write(data) | |
| 1483 if ch == u'\a': | |
| 1484 if end < (len(text) - 1) and not text[end + 2].isspace(): | |
| 1485 self.write_line_break() | |
| 1486 self.write_indent() | |
| 1487 end += 2 # \a and the space that is inserted on the fold | |
| 1488 else: | |
| 1489 raise EmitterError('unexcpected fold indicator \\a before space') | |
| 1490 if ch is None: | |
| 1491 self.write_line_break() | |
| 1492 start = end | |
| 1493 if ch is not None: | |
| 1494 breaks = ch in u'\n\x85\u2028\u2029' | |
| 1495 spaces = ch == u' ' | |
| 1496 end += 1 | |
| 1497 | |
| 1498 def write_literal(self, text, comment=None): | |
| 1499 # type: (Any, Any) -> None | |
| 1500 hints, _indent, _indicator = self.determine_block_hints(text) | |
| 1501 self.write_indicator(u'|' + hints, True) | |
| 1502 try: | |
| 1503 comment = comment[1][0] | |
| 1504 if comment: | |
| 1505 self.stream.write(comment) | |
| 1506 except (TypeError, IndexError): | |
| 1507 pass | |
| 1508 if _indicator == u'+': | |
| 1509 self.open_ended = True | |
| 1510 self.write_line_break() | |
| 1511 breaks = True | |
| 1512 start = end = 0 | |
| 1513 while end <= len(text): | |
| 1514 ch = None | |
| 1515 if end < len(text): | |
| 1516 ch = text[end] | |
| 1517 if breaks: | |
| 1518 if ch is None or ch not in u'\n\x85\u2028\u2029': | |
| 1519 for br in text[start:end]: | |
| 1520 if br == u'\n': | |
| 1521 self.write_line_break() | |
| 1522 else: | |
| 1523 self.write_line_break(br) | |
| 1524 if ch is not None: | |
| 1525 if self.root_context: | |
| 1526 idnx = self.indent if self.indent is not None else 0 | |
| 1527 self.stream.write(u' ' * (_indent + idnx)) | |
| 1528 else: | |
| 1529 self.write_indent() | |
| 1530 start = end | |
| 1531 else: | |
| 1532 if ch is None or ch in u'\n\x85\u2028\u2029': | |
| 1533 data = text[start:end] | |
| 1534 if bool(self.encoding): | |
| 1535 data = data.encode(self.encoding) | |
| 1536 self.stream.write(data) | |
| 1537 if ch is None: | |
| 1538 self.write_line_break() | |
| 1539 start = end | |
| 1540 if ch is not None: | |
| 1541 breaks = ch in u'\n\x85\u2028\u2029' | |
| 1542 end += 1 | |
| 1543 | |
| 1544 def write_plain(self, text, split=True): | |
| 1545 # type: (Any, Any) -> None | |
| 1546 if self.root_context: | |
| 1547 if self.requested_indent is not None: | |
| 1548 self.write_line_break() | |
| 1549 if self.requested_indent != 0: | |
| 1550 self.write_indent() | |
| 1551 else: | |
| 1552 self.open_ended = True | |
| 1553 if not text: | |
| 1554 return | |
| 1555 if not self.whitespace: | |
| 1556 data = u' ' | |
| 1557 self.column += len(data) | |
| 1558 if self.encoding: | |
| 1559 data = data.encode(self.encoding) | |
| 1560 self.stream.write(data) | |
| 1561 self.whitespace = False | |
| 1562 self.indention = False | |
| 1563 spaces = False | |
| 1564 breaks = False | |
| 1565 start = end = 0 | |
| 1566 while end <= len(text): | |
| 1567 ch = None | |
| 1568 if end < len(text): | |
| 1569 ch = text[end] | |
| 1570 if spaces: | |
| 1571 if ch != u' ': | |
| 1572 if start + 1 == end and self.column > self.best_width and split: | |
| 1573 self.write_indent() | |
| 1574 self.whitespace = False | |
| 1575 self.indention = False | |
| 1576 else: | |
| 1577 data = text[start:end] | |
| 1578 self.column += len(data) | |
| 1579 if self.encoding: | |
| 1580 data = data.encode(self.encoding) | |
| 1581 self.stream.write(data) | |
| 1582 start = end | |
| 1583 elif breaks: | |
| 1584 if ch not in u'\n\x85\u2028\u2029': | |
| 1585 if text[start] == u'\n': | |
| 1586 self.write_line_break() | |
| 1587 for br in text[start:end]: | |
| 1588 if br == u'\n': | |
| 1589 self.write_line_break() | |
| 1590 else: | |
| 1591 self.write_line_break(br) | |
| 1592 self.write_indent() | |
| 1593 self.whitespace = False | |
| 1594 self.indention = False | |
| 1595 start = end | |
| 1596 else: | |
| 1597 if ch is None or ch in u' \n\x85\u2028\u2029': | |
| 1598 data = text[start:end] | |
| 1599 self.column += len(data) | |
| 1600 if self.encoding: | |
| 1601 data = data.encode(self.encoding) | |
| 1602 try: | |
| 1603 self.stream.write(data) | |
| 1604 except: # NOQA | |
| 1605 sys.stdout.write(repr(data) + '\n') | |
| 1606 raise | |
| 1607 start = end | |
| 1608 if ch is not None: | |
| 1609 spaces = ch == u' ' | |
| 1610 breaks = ch in u'\n\x85\u2028\u2029' | |
| 1611 end += 1 | |
| 1612 | |
| 1613 def write_comment(self, comment, pre=False): | |
| 1614 # type: (Any, bool) -> None | |
| 1615 value = comment.value | |
| 1616 # nprintf('{:02d} {:02d} {!r}'.format(self.column, comment.start_mark.column, value)) | |
| 1617 if not pre and value[-1] == '\n': | |
| 1618 value = value[:-1] | |
| 1619 try: | |
| 1620 # get original column position | |
| 1621 col = comment.start_mark.column | |
| 1622 if comment.value and comment.value.startswith('\n'): | |
| 1623 # never inject extra spaces if the comment starts with a newline | |
| 1624 # and not a real comment (e.g. if you have an empty line following a key-value | |
| 1625 col = self.column | |
| 1626 elif col < self.column + 1: | |
| 1627 ValueError | |
| 1628 except ValueError: | |
| 1629 col = self.column + 1 | |
| 1630 # nprint('post_comment', self.line, self.column, value) | |
| 1631 try: | |
| 1632 # at least one space if the current column >= the start column of the comment | |
| 1633 # but not at the start of a line | |
| 1634 nr_spaces = col - self.column | |
| 1635 if self.column and value.strip() and nr_spaces < 1 and value[0] != '\n': | |
| 1636 nr_spaces = 1 | |
| 1637 value = ' ' * nr_spaces + value | |
| 1638 try: | |
| 1639 if bool(self.encoding): | |
| 1640 value = value.encode(self.encoding) | |
| 1641 except UnicodeDecodeError: | |
| 1642 pass | |
| 1643 self.stream.write(value) | |
| 1644 except TypeError: | |
| 1645 raise | |
| 1646 if not pre: | |
| 1647 self.write_line_break() | |
| 1648 | |
| 1649 def write_pre_comment(self, event): | |
| 1650 # type: (Any) -> bool | |
| 1651 comments = event.comment[1] | |
| 1652 if comments is None: | |
| 1653 return False | |
| 1654 try: | |
| 1655 start_events = (MappingStartEvent, SequenceStartEvent) | |
| 1656 for comment in comments: | |
| 1657 if isinstance(event, start_events) and getattr(comment, 'pre_done', None): | |
| 1658 continue | |
| 1659 if self.column != 0: | |
| 1660 self.write_line_break() | |
| 1661 self.write_comment(comment, pre=True) | |
| 1662 if isinstance(event, start_events): | |
| 1663 comment.pre_done = True | |
| 1664 except TypeError: | |
| 1665 sys.stdout.write('eventtt {} {}'.format(type(event), event)) | |
| 1666 raise | |
| 1667 return True | |
| 1668 | |
| 1669 def write_post_comment(self, event): | |
| 1670 # type: (Any) -> bool | |
| 1671 if self.event.comment[0] is None: | |
| 1672 return False | |
| 1673 comment = event.comment[0] | |
| 1674 self.write_comment(comment) | |
| 1675 return True |
