summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthon van der Neut <anthon@mnt.org>2017-08-14 20:44:09 +0200
committerAnthon van der Neut <anthon@mnt.org>2017-08-14 20:44:09 +0200
commit4c7259963b3f1bd92e7f5a863a737ef008e0e6dd (patch)
tree8b163f23fd56eac04246417cef96ca927a561ca2
parent21c1662a4c8367e1ce48811f78bcfe2830d06d58 (diff)
downloadruamel.yaml-4c7259963b3f1bd92e7f5a863a737ef008e0e6dd.tar.gz
fix issue #51: separate indent levels for mappings and sequences0.15.29
**When this resolves the reported problem, please close this issue**
-rw-r--r--CHANGES6
-rw-r--r--README.rst6
-rw-r--r--__init__.py4
-rw-r--r--_doc/api.ryd3
-rw-r--r--_doc/detail.ryd24
-rw-r--r--_doc/example.ryd5
-rw-r--r--_doc/overview.ryd10
-rw-r--r--_test/roundtrip.py15
-rw-r--r--_test/test_indentation.py76
-rw-r--r--emitter.py81
-rw-r--r--main.py53
11 files changed, 233 insertions, 50 deletions
diff --git a/CHANGES b/CHANGES
index 393a5b8..983623a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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)
diff --git a/README.rst b/README.rst
index b6271e2..09d7b32 100644
--- a/README.rst
+++ b/README.rst
@@ -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
diff --git a/emitter.py b/emitter.py
index 09d3419..6f75f41 100644
--- a/emitter.py
+++ b/emitter.py
@@ -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':
diff --git a/main.py b/main.py
index 949191d..74d2507 100644
--- a/main.py
+++ b/main.py
@@ -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