|
21 | 21 | import sys |
22 | 22 | from subprocess import PIPE, Popen |
23 | 23 |
|
| 24 | +import ruamel.yaml |
24 | 25 | from botocore.configprovider import BaseProvider |
25 | 26 | from botocore.useragent import UserAgentComponent |
26 | 27 | from botocore.utils import ( |
@@ -488,6 +489,95 @@ def dump_yaml_to_str(yaml, data): |
488 | 489 | return stream.getvalue() |
489 | 490 |
|
490 | 491 |
|
| 492 | +class SafeLineBreakEmitter(ruamel.yaml.emitter.Emitter): |
| 493 | + """Emitter that always uses backslash escapes at line breaks in |
| 494 | + double-quoted scalars. |
| 495 | +
|
| 496 | + Derived from ruamel.yaml's Emitter.write_double_quoted (MIT license). |
| 497 | +
|
| 498 | + ruamel.yaml >= 0.17.23 added a heuristic that sometimes omits the |
| 499 | + trailing backslash when wrapping long double-quoted strings, relying |
| 500 | + on YAML line-folding rules to preserve the value. Consumers that don't |
| 501 | + properly parse the value may experience failures. |
| 502 | +
|
| 503 | + This subclass restores the pre-0.17.23 behaviour of unconditionally |
| 504 | + emitting a backslash at every line break in double-quoted scalars. |
| 505 | + """ |
| 506 | + |
| 507 | + def write_double_quoted(self, text, split=True): |
| 508 | + if self.root_context: |
| 509 | + if self.requested_indent is not None: |
| 510 | + self.write_line_break() |
| 511 | + if self.requested_indent != 0: |
| 512 | + self.write_indent() |
| 513 | + self.write_indicator('"', True) |
| 514 | + start = end = 0 |
| 515 | + while end <= len(text): |
| 516 | + ch = None |
| 517 | + if end < len(text): |
| 518 | + ch = text[end] |
| 519 | + if ( |
| 520 | + ch is None |
| 521 | + or ch in '"\\\x85\u2028\u2029\ufeff' |
| 522 | + or not ( |
| 523 | + '\x20' <= ch <= '\x7e' |
| 524 | + or ( |
| 525 | + self.allow_unicode |
| 526 | + and ( |
| 527 | + ('\xa0' <= ch <= '\ud7ff') |
| 528 | + or ('\ue000' <= ch <= '\ufffd') |
| 529 | + or ('\U00010000' <= ch <= '\U0010ffff') |
| 530 | + ) |
| 531 | + ) |
| 532 | + ) |
| 533 | + ): |
| 534 | + if start < end: |
| 535 | + data = text[start:end] |
| 536 | + self.column += len(data) |
| 537 | + if bool(self.encoding): |
| 538 | + data = data.encode(self.encoding) |
| 539 | + self.stream.write(data) |
| 540 | + start = end |
| 541 | + if ch is not None: |
| 542 | + if ch in self.ESCAPE_REPLACEMENTS: |
| 543 | + data = '\\' + self.ESCAPE_REPLACEMENTS[ch] |
| 544 | + elif ch <= '\xff': |
| 545 | + data = '\\x%02X' % ord(ch) |
| 546 | + elif ch <= '\uffff': |
| 547 | + data = '\\u%04X' % ord(ch) |
| 548 | + else: |
| 549 | + data = '\\U%08X' % ord(ch) |
| 550 | + self.column += len(data) |
| 551 | + if bool(self.encoding): |
| 552 | + data = data.encode(self.encoding) |
| 553 | + self.stream.write(data) |
| 554 | + start = end + 1 |
| 555 | + if ( |
| 556 | + 0 < end < len(text) - 1 |
| 557 | + and (ch == ' ' or start >= end) |
| 558 | + and self.column + (end - start) > self.best_width |
| 559 | + and split |
| 560 | + ): |
| 561 | + data = text[start:end] + '\\' |
| 562 | + if start < end: |
| 563 | + start = end |
| 564 | + self.column += len(data) |
| 565 | + if bool(self.encoding): |
| 566 | + data = data.encode(self.encoding) |
| 567 | + self.stream.write(data) |
| 568 | + self.write_indent() |
| 569 | + self.whitespace = False |
| 570 | + self.indention = False |
| 571 | + if text[start] == ' ': |
| 572 | + data = '\\' |
| 573 | + self.column += len(data) |
| 574 | + if bool(self.encoding): |
| 575 | + data = data.encode(self.encoding) |
| 576 | + self.stream.write(data) |
| 577 | + end += 1 |
| 578 | + self.write_indicator('"', False) |
| 579 | + |
| 580 | + |
491 | 581 | class ShapeWalker: |
492 | 582 | def walk(self, shape, visitor): |
493 | 583 | """Walk through and visit shapes for introspection |
|
0 commit comments