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