From 06ea5a9886a6abb88b7a81481c6600477088f2c6 Mon Sep 17 00:00:00 2001 From: Anthon van der Neut Date: Sun, 5 Aug 2018 12:27:52 +0200 Subject: leading empty lines and comments on root level literal scalars not preserved added test, fixed both now you can have --- !some_tag | # with a comment this is a multi- line literal scalar --- CHANGES | 6 ++++++ README.rst | 10 ++++++++-- __init__.py | 4 ++-- _doc/_static/pypi.svg | 2 +- _test/test_literal.py | 27 +++++++++++++++++++++++++++ constructor.py | 5 ++++- emitter.py | 12 +++++++++--- representer.py | 7 ++++++- scalarstring.py | 2 +- scanner.py | 11 ++++++++++- setup.py | 2 +- 11 files changed, 75 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 3fc9b84..eadd88a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +[0, 15, 49]: 2018-08-05 + - fix preservation of leading newlines in root level literal style scalar, + and preserve comment after literal style indicator (``| # some comment``) + Both needed for round-tripping multi-doc streams in + `ryd `__. + [0, 15, 48]: 2018-08-03 - housekeeping: ``oitnb`` for formatting, mypy 0.620 upgrade and conformity diff --git a/README.rst b/README.rst index 2f91868..8f540c3 100644 --- a/README.rst +++ b/README.rst @@ -4,8 +4,8 @@ ruamel.yaml ``ruamel.yaml`` is a YAML 1.2 loader/dumper package for Python. -:version: 0.15.48 -:updated: 2018-08-03 +:version: 0.15.49 +:updated: 2018-08-05 :documentation: http://yaml.readthedocs.io :repository: https://bitbucket.org/ruamel/ :pypi: https://pypi.org/project/ruamel.yaml/ @@ -54,6 +54,12 @@ ChangeLog .. should insert NEXT: at the beginning of line for next key (with empty line) +0.15.49 (2018-08-05): + - fix preservation of leading newlines in root level literal style scalar, + and preserve comment after literal style indicator (``| # some comment``) + Both needed for round-tripping multi-doc streams in + `ryd `__. + 0.15.48 (2018-08-03): - housekeeping: ``oitnb`` for formatting, mypy 0.620 upgrade and conformity diff --git a/__init__.py b/__init__.py index c675a8e..2eb42bc 100644 --- a/__init__.py +++ b/__init__.py @@ -7,8 +7,8 @@ if False: # MYPY _package_data = dict( full_package_name='ruamel.yaml', - version_info=(0, 15, 48), - __version__='0.15.48', + version_info=(0, 15, 49), + __version__='0.15.49', author='Anthon van der Neut', author_email='a.van.der.neut@ruamel.eu', description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order', # NOQA diff --git a/_doc/_static/pypi.svg b/_doc/_static/pypi.svg index 45c5650..67703ce 100644 --- a/_doc/_static/pypi.svg +++ b/_doc/_static/pypi.svg @@ -1 +1 @@ - pypipypi0.15.480.15.48 + pypipypi0.15.490.15.49 diff --git a/_test/test_literal.py b/_test/test_literal.py index 74dca3d..5847269 100644 --- a/_test/test_literal.py +++ b/_test/test_literal.py @@ -240,6 +240,33 @@ class Test_RoundTripLiteral: d = yaml.load(ys) yaml.dump(d, compare=ys) + def test_rt_top_literal_scalar_no_indent_comment(self): + yaml = YAML() + yaml.explicit_start = True + s = 'testing123' + ys = """ + --- | # a comment + {} + """.format( + s + ) + d = yaml.load(ys) + yaml.dump(d, compare=ys) + + def test_rt_top_literal_scalar_no_indent_leading_empty_line(self): + yaml = YAML() + yaml.explicit_start = True + s = 'testing123' + ys = """ + --- | + + {} + """.format( + s + ) + d = yaml.load(ys) + yaml.dump(d, compare=ys) + def test_rt_top_literal_scalar_indent(self): yaml = YAML() yaml.explicit_start = True diff --git a/constructor.py b/constructor.py index 6a26da3..c830580 100644 --- a/constructor.py +++ b/constructor.py @@ -979,7 +979,10 @@ class RoundTripConstructor(SafeConstructor): ) if node.style == '|' and isinstance(node.value, text_type): - return PreservedScalarString(node.value) + pss = PreservedScalarString(node.value) + if node.comment and node.comment[1]: + pss.comment = node.comment[1][0] + return pss elif bool(self._preserve_quotes) and isinstance(node.value, text_type): if node.style == "'": return SingleQuotedScalarString(node.value) diff --git a/emitter.py b/emitter.py index 626a69e..a75fa52 100644 --- a/emitter.py +++ b/emitter.py @@ -811,7 +811,7 @@ class Emitter(object): elif self.style == '>': self.write_folded(self.analysis.scalar) elif self.style == '|': - self.write_literal(self.analysis.scalar) + self.write_literal(self.analysis.scalar, self.event.comment) else: self.write_plain(self.analysis.scalar, split) self.analysis = None @@ -1337,7 +1337,7 @@ class Emitter(object): # type: (Any) -> Any hints = "" if text: - if text[0] in u' \n\x85\u2028\u2029': + if not self.root_context and text[0] in u' \n\x85\u2028\u2029': hints += text_type(self.best_sequence_indent) if text[-1] not in u'\n\x85\u2028\u2029': hints += u'-' @@ -1404,10 +1404,16 @@ class Emitter(object): spaces = ch == u' ' end += 1 - def write_literal(self, text): + def write_literal(self, text, comment=None): # type: (Any) -> None hints = self.determine_block_hints(text) self.write_indicator(u'|' + hints, True) + try: + comment = comment[1][0] + if comment: + self.stream.write(comment) + except (TypeError, IndexError): + pass if hints[-1:] == u'+': self.open_ended = True self.write_line_break() diff --git a/representer.py b/representer.py index 7b821fe..07b2535 100644 --- a/representer.py +++ b/representer.py @@ -148,7 +148,12 @@ class BaseRepresenter(object): # type: (Any, Any, Any) -> Any if style is None: style = self.default_style - node = ScalarNode(tag, value, style=style) + comment = None + if style and style[0] == '|': + comment = getattr(value, 'comment', None) + if comment: + comment = [None, [comment]] + node = ScalarNode(tag, value, style=style, comment=comment) if self.alias_key is not None: self.represented_objects[self.alias_key] = node return node diff --git a/scalarstring.py b/scalarstring.py index 4b1a317..ef283d2 100644 --- a/scalarstring.py +++ b/scalarstring.py @@ -28,7 +28,7 @@ class ScalarString(text_type): class PreservedScalarString(ScalarString): - __slots__ = () + __slots__ = ('comment') # the comment after the | on the first line style = '|' diff --git a/scanner.py b/scanner.py index 92fbf52..dd77a12 100644 --- a/scanner.py +++ b/scanner.py @@ -1111,7 +1111,8 @@ class Scanner(object): # Scan the header. self.reader.forward() chomping, increment = self.scan_block_scalar_indicators(start_mark) - self.scan_block_scalar_ignored_line(start_mark) + # block scalar comment e.g. : |+ # comment text + block_scalar_comment = self.scan_block_scalar_ignored_line(start_mark) # Determine the indentation level and go to the first non-empty line. min_indent = self.indent + 1 @@ -1193,6 +1194,8 @@ class Scanner(object): # We are done. token = ScalarToken("".join(chunks), False, start_mark, end_mark, style) + if block_scalar_comment is not None: + token.add_pre_comments([block_scalar_comment]) if len(trailing) > 0: # print('trailing 1', trailing) # XXXXX # Eat whitespaces and comments until we reach the next token. @@ -1261,10 +1264,15 @@ class Scanner(object): def scan_block_scalar_ignored_line(self, start_mark): # type: (Any) -> Any # See the specification for details. + prefix = '' + comment = None while self.reader.peek() == ' ': + prefix += self.reader.peek() self.reader.forward() if self.reader.peek() == '#': + comment = prefix while self.reader.peek() not in _THE_END: + comment += self.reader.peek() self.reader.forward() ch = self.reader.peek() if ch not in _THE_END: @@ -1275,6 +1283,7 @@ class Scanner(object): self.reader.get_mark(), ) self.scan_line_break() + return comment def scan_block_scalar_indentation(self): # type: () -> Any diff --git a/setup.py b/setup.py index 4eb777c..857b5e2 100644 --- a/setup.py +++ b/setup.py @@ -662,7 +662,7 @@ class NameSpacePackager(object): @property def keywords(self): - return self.pn(self._pkg_data.get('keywords')) + return self.pn(self._pkg_data.get('keywords', [])) @property def install_requires(self): -- cgit v1.2.1