diff options
-rw-r--r-- | CHANGES | 6 | ||||
-rw-r--r-- | README.rst | 6 | ||||
-rw-r--r-- | __init__.py | 4 | ||||
-rw-r--r-- | _doc/api.ryd | 3 | ||||
-rw-r--r-- | _doc/detail.ryd | 24 | ||||
-rw-r--r-- | _doc/example.ryd | 5 | ||||
-rw-r--r-- | _doc/overview.ryd | 10 | ||||
-rw-r--r-- | _test/roundtrip.py | 15 | ||||
-rw-r--r-- | _test/test_indentation.py | 76 | ||||
-rw-r--r-- | emitter.py | 81 | ||||
-rw-r--r-- | main.py | 53 |
11 files changed, 233 insertions, 50 deletions
@@ -1,3 +1,9 @@ +[0, 15, 29]: 2017-08-14 + - fix issue #51: different indents for mappings and sequences (reported by + Alex Harvey) + - fix for flow sequence/mapping as element/value of block sequence with + sequence-indent minus dash-offset not equal two. + [0, 15, 28]: 2017-08-13 - fix issue #61: merge of merge cannot be __repr__-ed (reported by Tal Liron) @@ -35,6 +35,12 @@ ChangeLog .. should insert NEXT: at the beginning of line for next key +0.15.29 (2017-08-14): + - fix issue #51: different indents for mappings and sequences (reported by + Alex Harvey) + - fix for flow sequence/mapping as element/value of block sequence with + sequence-indent minus dash-offset not equal two. + 0.15.28 (2017-08-13): - fix issue #61: merge of merge cannot be __repr__-ed (reported by Tal Liron) diff --git a/__init__.py b/__init__.py index bf5f6ab..88730b4 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, 28), - __version__='0.15.28', + version_info=(0, 15, 29), + __version__='0.15.29', 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/api.ryd b/_doc/api.ryd index c03355a..6a94044 100644 --- a/_doc/api.ryd +++ b/_doc/api.ryd @@ -183,8 +183,7 @@ if ruamel.yaml.version_info < (0, 15): def __init__(self): yaml.YAML.__init__(self) self.preserve_quotes = True - self.indent = 4 - self.block_seq_indent = 2 + self.indent(mapping=4, sequence=4, offset=2) # in your code try: from myyaml import MyYAML diff --git a/_doc/detail.ryd b/_doc/detail.ryd index e3243a2..7867169 100644 --- a/_doc/detail.ryd +++ b/_doc/detail.ryd @@ -55,7 +55,8 @@ back to:: - b: 1 - 2 -if you specify ``yaml.indent = 4``. +if you specify ``yaml.indent(sequence=4)`` (indentation is counted to the +beginning of the sequence element). PyYAML (and older versions of ruamel.yaml) gives you non-indented scalars (when specifying default_flow_style=False):: @@ -64,27 +65,30 @@ scalars (when specifying default_flow_style=False):: - b: 1 - 2 -The dump also observes an additional ``block_seq_indent`` settingr that -can be used to push the dash inwards, *within the space defined by* ``indent``. +You can use ``mapping=4`` to also have the mappings values indented. +The dump also observes an additional ``offset=2`` setting that +can be used to push the dash inwards, *within the space defined by* ``sequence``. -The above example with the often seen ``yaml.indent = 4; yaml.block_seq_indent = 2`` +The above example with the often seen ``yaml.indent(mapping=2, sequence=4, offset=2)`` indentation:: x: - - b: 1 - - 2 + y: + - b: 1 + - 2 +The defaults are as if you specified ``yaml.indent(mapping=2, sequence=2, offset=0)``. -If the ``block_seq_indent`` equals ``indent``, there is not enough -room for the dash and the space that has to follow. In that case the +If the ``offset`` equals ``sequence``, there is not enough +room for the dash and the space that has to follow it. In that case the element itself would normally be pushed to the next line (and older versions of ruamel.yaml did so). But this is prevented from happening. However the ``indent`` level is what is used for calculating the cumulative indent for deeper levels and specifying -``yaml.indent = 3`` resp. ``yaml.block_seq_indent = 2``, migth give correct, but counter +``sequence=3`` resp. ``offset=2``, might give correct, but counter intuitive results. -**It is best to always have** ``indent >= block_seq_indent + 2`` +**It is best to always have** ``sequence >= offset + 2`` **but this is not enforced**. Depending on your structure, not following this advice **might lead to invalid output**. diff --git a/_doc/example.ryd b/_doc/example.ryd index 83681e3..58a5a1e 100644 --- a/_doc/example.ryd +++ b/_doc/example.ryd @@ -136,8 +136,7 @@ The following program with three dumps:: yaml = YAML() yaml.explicit_start = True yaml.dump(data, sys.stdout) - yaml.indent = 4 - yaml.block_seq_indent = 2 + yaml.indent(sequence=4, offset=2) yaml.dump(data, sys.stdout) @@ -204,7 +203,7 @@ you really need to have it (or think you do):: --- !python | import sys -from ruamel.yaml import YAML +from ruamel.yaml import YAML from ruamel.yaml.compat import StringIO class MyYAML(YAML): diff --git a/_doc/overview.ryd b/_doc/overview.ryd index 914d909..de51ddd 100644 --- a/_doc/overview.ryd +++ b/_doc/overview.ryd @@ -28,12 +28,10 @@ Reassigning values or replacing list items, etc., is fine. For the specific 1.2 differences see :ref:`yaml-1-2-support` -Although individual indentation of lines is not preserved, you can -specify both indentation (counting for this does **not** include the -dash for a sequence element) and specific offset of block sequence -dashes within that indentation. There is a utility function that tries -to determine the correct values for ``indent`` and -``block_seq_indent`` that can then be passed to the dumper. +Although individual indentation of lines is not preserved, you can specify +separate indentation levels for mappings and sequences (counting for sequences +does **not** include the dash for a sequence element) and specific offset of +block sequence dashes within that indentation. Although ``ruamel.yaml`` still allows most of the PyYAML way of doing diff --git a/_test/roundtrip.py b/_test/roundtrip.py index a00c4d4..0037437 100644 --- a/_test/roundtrip.py +++ b/_test/roundtrip.py @@ -145,3 +145,18 @@ class YAML(ruamel.yaml.YAML): res = sorted(res.splitlines()) expected = sorted(expected.splitlines()) assert res == expected + + def round_trip(self, stream, **kw): + assert isinstance(stream, ruamel.yaml.compat.text_type) + lkw = kw.copy() + if stream and stream[0] == '\n': + stream = stream[1:] + stream = textwrap.dedent(stream) + data = ruamel.yaml.YAML.load(self, stream) + outp = lkw.pop('outp', stream) + lkw['stream'] = st = StringIO() + ruamel.yaml.YAML.dump(self, data, **lkw) + res = st.getvalue() + if res != outp: + diff(outp, res, "input string") + assert res == outp diff --git a/_test/test_indentation.py b/_test/test_indentation.py index 4391013..4e991fd 100644 --- a/_test/test_indentation.py +++ b/_test/test_indentation.py @@ -10,7 +10,7 @@ import pytest # NOQA import ruamel.yaml from ruamel.yaml.util import load_yaml_guess_indent -from roundtrip import round_trip, round_trip_load, round_trip_dump, dedent +from roundtrip import round_trip, round_trip_load, round_trip_dump, dedent, YAML def rt(s): @@ -240,4 +240,78 @@ class TestGuessIndent: a: 1 """) == (3, None) + +class TestSeparateMapSeqIndents: + # using uncommon 6 indent with 3 push in as 2 push in automatically + # gets you 4 indent even if not set + def test_00(self): + # old style + yaml = YAML() + yaml.indent = 6 + yaml.block_seq_indent = 3 + yaml.round_trip(""" + a: + - 1 + - [1, 2] + """) + + def test_01(self): + yaml = YAML() + yaml.indent(sequence=6) + yaml.indent(offset=3) + yaml.round_trip(""" + a: + - 1 + - {b: 3} + """) + + def test_02(self): + yaml = YAML() + yaml.indent(mapping=5, sequence=6, offset=3) + yaml.round_trip(""" + a: + b: + - 1 + - [1, 2] + """) + + def test_03(self): + round_trip(""" + a: + b: + c: + - 1 + - [1, 2] + """, indent=4) + + def test_04(self): + yaml = YAML() + yaml.indent(mapping=5, sequence=6) + yaml.round_trip(""" + a: + b: + - 1 + - [1, 2] + - {d: 3.14} + """) + + def test_issue_51(self): + yaml = YAML() + # yaml.map_indent = 2 # the default + yaml.sequence_indent = 4 + yaml.sequence_dash_offset = 2 + yaml.preserve_quotes = True + yaml.round_trip(""" + role::startup::author::rsyslog_inputs: + imfile: + - ruleset: 'AEM-slinglog' + File: '/opt/aem/author/crx-quickstart/logs/error.log' + startmsg.regex: '^[-+T.:[:digit:]]*' + tag: 'error' + - ruleset: 'AEM-slinglog' + File: '/opt/aem/author/crx-quickstart/logs/stdout.log' + startmsg.regex: '^[-+T.:[:digit:]]*' + tag: 'stdout' + """) + # ############ indentation @@ -17,7 +17,7 @@ from ruamel.yaml.compat import utf8, text_type, PY2, nprint, dbg, DBG_EVENT, \ check_anchorname_char if False: # MYPY - from typing import Any, Dict, List, Union, Text # NOQA + from typing import Any, Dict, List, Union, Text, Tuple # NOQA from ruamel.yaml.compat import StreamType # NOQA __all__ = ['Emitter', 'EmitterError'] @@ -43,6 +43,38 @@ class ScalarAnalysis(object): self.allow_block = allow_block +class Indents(object): + # replacement for the list based stack of None/int + def __init__(self): + # type: () -> None + self.values = [] # type: List[Tuple[int, bool]] + + def append(self, val, seq): + # type: (Any, Any) -> None + self.values.append((val, seq)) + + def pop(self): + # type: () -> Any + return self.values.pop()[0] + + def last_seq(self): + # type: () -> bool + # return the seq(uence) value for the element added before the last one + # in increase_indent() + try: + return self.values[-2][1] + except IndexError: + return False + + def seq_flow_align(self, seq_indent, column): + # type: (int, int) -> int + # extra spaces because of dash + if len(self.values) < 2 or not self.values[-1][1]: + return 0 + # -1 for the dash + return self.values[-1][0] + seq_indent - column - 1 + + class Emitter(object): DEFAULT_TAG_PREFIXES = { u'!': u'!', @@ -74,7 +106,7 @@ class Emitter(object): self.event = None # type: Any # The current indentation level and the stack of previous indents. - self.indents = [] # type: List[Union[None, int]] + self.indents = Indents() self.indent = None # type: Union[None, int] # Flow level. @@ -109,16 +141,17 @@ class Emitter(object): self.allow_unicode = allow_unicode # set to False to get "\Uxxxxxxxx" for non-basic unicode like emojis self.unicode_supplementary = sys.maxunicode > 0xffff - self.block_seq_indent = block_seq_indent if block_seq_indent else 0 + self.sequence_dash_offset = block_seq_indent if block_seq_indent else 0 self.top_level_colon_align = top_level_colon_align - self.best_indent = 2 + self.best_sequence_indent = 2 self.requested_indent = indent # specific for literal zero indent if indent and 1 < indent < 10: - self.best_indent = indent - # if self.best_indent < self.block_seq_indent + 1: - # self.best_indent = self.block_seq_indent + 1 + self.best_sequence_indent = indent + self.best_map_indent = self.best_sequence_indent + # if self.best_sequence_indent < self.sequence_dash_offset + 1: + # self.best_sequence_indent = self.sequence_dash_offset + 1 self.best_width = 80 - if width and width > self.best_indent * 2: + if width and width > self.best_sequence_indent * 2: self.best_width = width self.best_line_break = u'\n' # type: Any if line_break in [u'\r', u'\n', u'\r\n']: @@ -210,16 +243,21 @@ class Emitter(object): def increase_indent(self, flow=False, sequence=None, indentless=False): # type: (bool, bool, bool) -> None - self.indents.append(self.indent) - if self.indent is None: + self.indents.append(self.indent, sequence) + if self.indent is None: # top level if flow: - self.indent = self.best_indent + # self.indent = self.best_sequence_indent if self.indents.last_seq() else \ + # self.best_map_indent + # self.indent = self.best_sequence_indent + self.indent = self.requested_indent else: self.indent = 0 elif not indentless: - self.indent += self.best_indent - # if self.sequence_context and (self.block_seq_indent + 2) > self.best_indent: - # self.indent = self.block_seq_indent + 2 + self.indent += (self.best_sequence_indent if self.indents.last_seq() else + self.best_map_indent) + # if ()self.sequence_context and (self.sequence_dash_offset + 2) > + # self.best_sequence_indent): + # self.indent = self.sequence_dash_offset + 2 # States. @@ -371,9 +409,10 @@ class Emitter(object): def expect_flow_sequence(self): # type: () -> None - self.write_indicator(u'[', True, whitespace=True) - self.flow_level += 1 + ind = self.indents.seq_flow_align(self.best_sequence_indent, self.column) + self.write_indicator(u' ' * ind + u'[', True, whitespace=True) self.increase_indent(flow=True, sequence=True) + self.flow_level += 1 self.state = self.expect_first_flow_sequence_item def expect_first_flow_sequence_item(self): @@ -420,7 +459,8 @@ class Emitter(object): def expect_flow_mapping(self): # type: () -> None - self.write_indicator(u'{', True, whitespace=True) + ind = self.indents.seq_flow_align(self.best_sequence_indent, self.column) + self.write_indicator(u' ' * ind + u'{', True, whitespace=True) self.flow_level += 1 self.increase_indent(flow=True, sequence=False) self.state = self.expect_first_flow_mapping_key @@ -512,8 +552,9 @@ class Emitter(object): self.write_pre_comment(self.event) nonl = self.no_newline if self.column == 0 else False self.write_indent() - self.write_indicator((u' ' * self.block_seq_indent) + u'-', True, indention=True) - if nonl or self.block_seq_indent + 2 > self.best_indent: + self.write_indicator((u' ' * self.sequence_dash_offset) + u'-', True, + indention=True) + if nonl or self.sequence_dash_offset + 2 > self.best_sequence_indent: self.no_newline = True self.states.append(self.expect_block_sequence_item) self.expect_node(sequence=True) @@ -1197,7 +1238,7 @@ class Emitter(object): hints = u'' if text: if text[0] in u' \n\x85\u2028\u2029': - hints += text_type(self.best_indent) + hints += text_type(self.best_sequence_indent) if text[-1] not in u'\n\x85\u2028\u2029': hints += u'-' elif len(text) == 1 or text[-2] in u'\n\x85\u2028\u2029': @@ -110,10 +110,14 @@ class YAML(object): 'typ "{}"not recognised (need to install plug-in?)'.format(self.typ)) self.stream = None self.canonical = None - self.indent = None + self.old_indent = None self.width = None self.line_break = None - self.block_seq_indent = None + + self.map_indent = None + self.sequence_indent = None + self.sequence_dash_offset = 0 + self.top_level_colon_align = None self.prefix_colon = None self.version = None @@ -198,12 +202,19 @@ class YAML(object): attr = '_' + sys._getframe().f_code.co_name if not hasattr(self, attr): if self.Emitter is not CEmitter: - setattr(self, attr, self.Emitter( + _emitter = self.Emitter( None, canonical=self.canonical, - indent=self.indent, width=self.width, + indent=self.old_indent, width=self.width, allow_unicode=self.allow_unicode, line_break=self.line_break, - block_seq_indent=self.block_seq_indent, - dumper=self)) + dumper=self) + setattr(self, attr, _emitter) + if self.map_indent is not None: + _emitter.best_map_indent = self.map_indent + if self.sequence_indent is not None: + _emitter.best_sequence_indent = self.sequence_indent + if self.sequence_dash_offset is not None: + _emitter.sequence_dash_offset = self.sequence_dash_offset + # _emitter.block_seq_indent = self.sequence_dash_offset else: if getattr(self, '_stream', None) is None: # wait for the stream @@ -490,6 +501,36 @@ class YAML(object): self.constructor.add_constructor(tag, f_y) + # ### backwards compatibility + def _indent(self, mapping=None, sequence=None, offset=None): + # type: (Any, Any, Any) -> None + if mapping is not None: + self.map_indent = mapping + if sequence is not None: + self.sequence_indent = sequence + if offset is not None: + self.sequence_dash_offset = offset + + @property + def indent(self): + # type: () -> Any + return self._indent + + @indent.setter + def indent(self, val): + # type: (Any) -> None + self.old_indent = val + + @property + def block_seq_indent(self): + # type: () -> Any + return self.sequence_dash_offset + + @block_seq_indent.setter + def block_seq_indent(self, val): + # type: (Any) -> None + self.sequence_dash_offset = val + def yaml_object(yml): # type: (Any) -> Any |