diff env/lib/python3.7/site-packages/ruamel/yaml/comments.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.7/site-packages/ruamel/yaml/comments.py	Sat May 02 07:14:21 2020 -0400
@@ -0,0 +1,1146 @@
+# coding: utf-8
+
+from __future__ import absolute_import, print_function
+
+"""
+stuff to deal with comments and formatting on dict/list/ordereddict/set
+these are not really related, formatting could be factored out as
+a separate base
+"""
+
+import sys
+import copy
+
+
+from ruamel.yaml.compat import ordereddict, PY2, string_types, MutableSliceableSequence
+from ruamel.yaml.scalarstring import ScalarString
+from ruamel.yaml.anchor import Anchor
+
+if PY2:
+    from collections import MutableSet, Sized, Set, Mapping
+else:
+    from collections.abc import MutableSet, Sized, Set, Mapping
+
+if False:  # MYPY
+    from typing import Any, Dict, Optional, List, Union, Optional, Iterator  # NOQA
+
+# fmt: off
+__all__ = ['CommentedSeq', 'CommentedKeySeq',
+           'CommentedMap', 'CommentedOrderedMap',
+           'CommentedSet', 'comment_attrib', 'merge_attrib']
+# fmt: on
+
+comment_attrib = '_yaml_comment'
+format_attrib = '_yaml_format'
+line_col_attrib = '_yaml_line_col'
+merge_attrib = '_yaml_merge'
+tag_attrib = '_yaml_tag'
+
+
+class Comment(object):
+    # sys.getsize tested the Comment objects, __slots__ makes them bigger
+    # and adding self.end did not matter
+    __slots__ = 'comment', '_items', '_end', '_start'
+    attrib = comment_attrib
+
+    def __init__(self):
+        # type: () -> None
+        self.comment = None  # [post, [pre]]
+        # map key (mapping/omap/dict) or index (sequence/list) to a  list of
+        # dict: post_key, pre_key, post_value, pre_value
+        # list: pre item, post item
+        self._items = {}  # type: Dict[Any, Any]
+        # self._start = [] # should not put these on first item
+        self._end = []  # type: List[Any] # end of document comments
+
+    def __str__(self):
+        # type: () -> str
+        if bool(self._end):
+            end = ',\n  end=' + str(self._end)
+        else:
+            end = ""
+        return 'Comment(comment={0},\n  items={1}{2})'.format(self.comment, self._items, end)
+
+    @property
+    def items(self):
+        # type: () -> Any
+        return self._items
+
+    @property
+    def end(self):
+        # type: () -> Any
+        return self._end
+
+    @end.setter
+    def end(self, value):
+        # type: (Any) -> None
+        self._end = value
+
+    @property
+    def start(self):
+        # type: () -> Any
+        return self._start
+
+    @start.setter
+    def start(self, value):
+        # type: (Any) -> None
+        self._start = value
+
+
+# to distinguish key from None
+def NoComment():
+    # type: () -> None
+    pass
+
+
+class Format(object):
+    __slots__ = ('_flow_style',)
+    attrib = format_attrib
+
+    def __init__(self):
+        # type: () -> None
+        self._flow_style = None  # type: Any
+
+    def set_flow_style(self):
+        # type: () -> None
+        self._flow_style = True
+
+    def set_block_style(self):
+        # type: () -> None
+        self._flow_style = False
+
+    def flow_style(self, default=None):
+        # type: (Optional[Any]) -> Any
+        """if default (the flow_style) is None, the flow style tacked on to
+        the object explicitly will be taken. If that is None as well the
+        default flow style rules the format down the line, or the type
+        of the constituent values (simple -> flow, map/list -> block)"""
+        if self._flow_style is None:
+            return default
+        return self._flow_style
+
+
+class LineCol(object):
+    attrib = line_col_attrib
+
+    def __init__(self):
+        # type: () -> None
+        self.line = None
+        self.col = None
+        self.data = None  # type: Optional[Dict[Any, Any]]
+
+    def add_kv_line_col(self, key, data):
+        # type: (Any, Any) -> None
+        if self.data is None:
+            self.data = {}
+        self.data[key] = data
+
+    def key(self, k):
+        # type: (Any) -> Any
+        return self._kv(k, 0, 1)
+
+    def value(self, k):
+        # type: (Any) -> Any
+        return self._kv(k, 2, 3)
+
+    def _kv(self, k, x0, x1):
+        # type: (Any, Any, Any) -> Any
+        if self.data is None:
+            return None
+        data = self.data[k]
+        return data[x0], data[x1]
+
+    def item(self, idx):
+        # type: (Any) -> Any
+        if self.data is None:
+            return None
+        return self.data[idx][0], self.data[idx][1]
+
+    def add_idx_line_col(self, key, data):
+        # type: (Any, Any) -> None
+        if self.data is None:
+            self.data = {}  # type: Dict[Any, Any]
+        self.data[key] = data
+
+
+class Tag(object):
+    """store tag information for roundtripping"""
+
+    __slots__ = ('value',)
+    attrib = tag_attrib
+
+    def __init__(self):
+        # type: () -> None
+        self.value = None
+
+    def __repr__(self):
+        # type: () -> Any
+        return '{0.__class__.__name__}({0.value!r})'.format(self)
+
+
+class CommentedBase(object):
+    @property
+    def ca(self):
+        # type: () -> Any
+        if not hasattr(self, Comment.attrib):
+            setattr(self, Comment.attrib, Comment())
+        return getattr(self, Comment.attrib)
+
+    def yaml_end_comment_extend(self, comment, clear=False):
+        # type: (Any, bool) -> None
+        if comment is None:
+            return
+        if clear or self.ca.end is None:
+            self.ca.end = []
+        self.ca.end.extend(comment)
+
+    def yaml_key_comment_extend(self, key, comment, clear=False):
+        # type: (Any, Any, bool) -> None
+        r = self.ca._items.setdefault(key, [None, None, None, None])
+        if clear or r[1] is None:
+            if comment[1] is not None:
+                assert isinstance(comment[1], list)
+            r[1] = comment[1]
+        else:
+            r[1].extend(comment[0])
+        r[0] = comment[0]
+
+    def yaml_value_comment_extend(self, key, comment, clear=False):
+        # type: (Any, Any, bool) -> None
+        r = self.ca._items.setdefault(key, [None, None, None, None])
+        if clear or r[3] is None:
+            if comment[1] is not None:
+                assert isinstance(comment[1], list)
+            r[3] = comment[1]
+        else:
+            r[3].extend(comment[0])
+        r[2] = comment[0]
+
+    def yaml_set_start_comment(self, comment, indent=0):
+        # type: (Any, Any) -> None
+        """overwrites any preceding comment lines on an object
+        expects comment to be without `#` and possible have multiple lines
+        """
+        from .error import CommentMark
+        from .tokens import CommentToken
+
+        pre_comments = self._yaml_get_pre_comment()
+        if comment[-1] == '\n':
+            comment = comment[:-1]  # strip final newline if there
+        start_mark = CommentMark(indent)
+        for com in comment.split('\n'):
+            pre_comments.append(CommentToken('# ' + com + '\n', start_mark, None))
+
+    def yaml_set_comment_before_after_key(
+        self, key, before=None, indent=0, after=None, after_indent=None
+    ):
+        # type: (Any, Any, Any, Any, Any) -> None
+        """
+        expects comment (before/after) to be without `#` and possible have multiple lines
+        """
+        from ruamel.yaml.error import CommentMark
+        from ruamel.yaml.tokens import CommentToken
+
+        def comment_token(s, mark):
+            # type: (Any, Any) -> Any
+            # handle empty lines as having no comment
+            return CommentToken(('# ' if s else "") + s + '\n', mark, None)
+
+        if after_indent is None:
+            after_indent = indent + 2
+        if before and (len(before) > 1) and before[-1] == '\n':
+            before = before[:-1]  # strip final newline if there
+        if after and after[-1] == '\n':
+            after = after[:-1]  # strip final newline if there
+        start_mark = CommentMark(indent)
+        c = self.ca.items.setdefault(key, [None, [], None, None])
+        if before == '\n':
+            c[1].append(comment_token("", start_mark))
+        elif before:
+            for com in before.split('\n'):
+                c[1].append(comment_token(com, start_mark))
+        if after:
+            start_mark = CommentMark(after_indent)
+            if c[3] is None:
+                c[3] = []
+            for com in after.split('\n'):
+                c[3].append(comment_token(com, start_mark))  # type: ignore
+
+    @property
+    def fa(self):
+        # type: () -> Any
+        """format attribute
+
+        set_flow_style()/set_block_style()"""
+        if not hasattr(self, Format.attrib):
+            setattr(self, Format.attrib, Format())
+        return getattr(self, Format.attrib)
+
+    def yaml_add_eol_comment(self, comment, key=NoComment, column=None):
+        # type: (Any, Optional[Any], Optional[Any]) -> None
+        """
+        there is a problem as eol comments should start with ' #'
+        (but at the beginning of the line the space doesn't have to be before
+        the #. The column index is for the # mark
+        """
+        from .tokens import CommentToken
+        from .error import CommentMark
+
+        if column is None:
+            try:
+                column = self._yaml_get_column(key)
+            except AttributeError:
+                column = 0
+        if comment[0] != '#':
+            comment = '# ' + comment
+        if column is None:
+            if comment[0] == '#':
+                comment = ' ' + comment
+                column = 0
+        start_mark = CommentMark(column)
+        ct = [CommentToken(comment, start_mark, None), None]
+        self._yaml_add_eol_comment(ct, key=key)
+
+    @property
+    def lc(self):
+        # type: () -> Any
+        if not hasattr(self, LineCol.attrib):
+            setattr(self, LineCol.attrib, LineCol())
+        return getattr(self, LineCol.attrib)
+
+    def _yaml_set_line_col(self, line, col):
+        # type: (Any, Any) -> None
+        self.lc.line = line
+        self.lc.col = col
+
+    def _yaml_set_kv_line_col(self, key, data):
+        # type: (Any, Any) -> None
+        self.lc.add_kv_line_col(key, data)
+
+    def _yaml_set_idx_line_col(self, key, data):
+        # type: (Any, Any) -> None
+        self.lc.add_idx_line_col(key, data)
+
+    @property
+    def anchor(self):
+        # type: () -> Any
+        if not hasattr(self, Anchor.attrib):
+            setattr(self, Anchor.attrib, Anchor())
+        return getattr(self, Anchor.attrib)
+
+    def yaml_anchor(self):
+        # type: () -> Any
+        if not hasattr(self, Anchor.attrib):
+            return None
+        return self.anchor
+
+    def yaml_set_anchor(self, value, always_dump=False):
+        # type: (Any, bool) -> None
+        self.anchor.value = value
+        self.anchor.always_dump = always_dump
+
+    @property
+    def tag(self):
+        # type: () -> Any
+        if not hasattr(self, Tag.attrib):
+            setattr(self, Tag.attrib, Tag())
+        return getattr(self, Tag.attrib)
+
+    def yaml_set_tag(self, value):
+        # type: (Any) -> None
+        self.tag.value = value
+
+    def copy_attributes(self, t, memo=None):
+        # type: (Any, bool) -> None
+        # fmt: off
+        for a in [Comment.attrib, Format.attrib, LineCol.attrib, Anchor.attrib,
+                  Tag.attrib, merge_attrib]:
+            if hasattr(self, a):
+                if memo is not None:
+                    setattr(t, a, copy.deepcopy(getattr(self, a, memo)))
+                else:
+                    setattr(t, a, getattr(self, a))
+        # fmt: on
+
+    def _yaml_add_eol_comment(self, comment, key):
+        # type: (Any, Any) -> None
+        raise NotImplementedError
+
+    def _yaml_get_pre_comment(self):
+        # type: () -> Any
+        raise NotImplementedError
+
+    def _yaml_get_column(self, key):
+        # type: (Any) -> Any
+        raise NotImplementedError
+
+
+class CommentedSeq(MutableSliceableSequence, list, CommentedBase):  # type: ignore
+    __slots__ = (Comment.attrib, '_lst')
+
+    def __init__(self, *args, **kw):
+        # type: (Any, Any) -> None
+        list.__init__(self, *args, **kw)
+
+    def __getsingleitem__(self, idx):
+        # type: (Any) -> Any
+        return list.__getitem__(self, idx)
+
+    def __setsingleitem__(self, idx, value):
+        # type: (Any, Any) -> None
+        # try to preserve the scalarstring type if setting an existing key to a new value
+        if idx < len(self):
+            if (
+                isinstance(value, string_types)
+                and not isinstance(value, ScalarString)
+                and isinstance(self[idx], ScalarString)
+            ):
+                value = type(self[idx])(value)
+        list.__setitem__(self, idx, value)
+
+    def __delsingleitem__(self, idx=None):
+        # type: (Any) -> Any
+        list.__delitem__(self, idx)
+        self.ca.items.pop(idx, None)  # might not be there -> default value
+        for list_index in sorted(self.ca.items):
+            if list_index < idx:
+                continue
+            self.ca.items[list_index - 1] = self.ca.items.pop(list_index)
+
+    def __len__(self):
+        # type: () -> int
+        return list.__len__(self)
+
+    def insert(self, idx, val):
+        # type: (Any, Any) -> None
+        """the comments after the insertion have to move forward"""
+        list.insert(self, idx, val)
+        for list_index in sorted(self.ca.items, reverse=True):
+            if list_index < idx:
+                break
+            self.ca.items[list_index + 1] = self.ca.items.pop(list_index)
+
+    def extend(self, val):
+        # type: (Any) -> None
+        list.extend(self, val)
+
+    def __eq__(self, other):
+        # type: (Any) -> bool
+        return list.__eq__(self, other)
+
+    def _yaml_add_comment(self, comment, key=NoComment):
+        # type: (Any, Optional[Any]) -> None
+        if key is not NoComment:
+            self.yaml_key_comment_extend(key, comment)
+        else:
+            self.ca.comment = comment
+
+    def _yaml_add_eol_comment(self, comment, key):
+        # type: (Any, Any) -> None
+        self._yaml_add_comment(comment, key=key)
+
+    def _yaml_get_columnX(self, key):
+        # type: (Any) -> Any
+        return self.ca.items[key][0].start_mark.column
+
+    def _yaml_get_column(self, key):
+        # type: (Any) -> Any
+        column = None
+        sel_idx = None
+        pre, post = key - 1, key + 1
+        if pre in self.ca.items:
+            sel_idx = pre
+        elif post in self.ca.items:
+            sel_idx = post
+        else:
+            # self.ca.items is not ordered
+            for row_idx, _k1 in enumerate(self):
+                if row_idx >= key:
+                    break
+                if row_idx not in self.ca.items:
+                    continue
+                sel_idx = row_idx
+        if sel_idx is not None:
+            column = self._yaml_get_columnX(sel_idx)
+        return column
+
+    def _yaml_get_pre_comment(self):
+        # type: () -> Any
+        pre_comments = []  # type: List[Any]
+        if self.ca.comment is None:
+            self.ca.comment = [None, pre_comments]
+        else:
+            self.ca.comment[1] = pre_comments
+        return pre_comments
+
+    def __deepcopy__(self, memo):
+        # type: (Any) -> Any
+        res = self.__class__()
+        memo[id(self)] = res
+        for k in self:
+            res.append(copy.deepcopy(k, memo))
+            self.copy_attributes(res, memo=memo)
+        return res
+
+    def __add__(self, other):
+        # type: (Any) -> Any
+        return list.__add__(self, other)
+
+    def sort(self, key=None, reverse=False):  # type: ignore
+        # type: (Any, bool) -> None
+        if key is None:
+            tmp_lst = sorted(zip(self, range(len(self))), reverse=reverse)
+            list.__init__(self, [x[0] for x in tmp_lst])
+        else:
+            tmp_lst = sorted(
+                zip(map(key, list.__iter__(self)), range(len(self))), reverse=reverse
+            )
+            list.__init__(self, [list.__getitem__(self, x[1]) for x in tmp_lst])
+        itm = self.ca.items
+        self.ca._items = {}
+        for idx, x in enumerate(tmp_lst):
+            old_index = x[1]
+            if old_index in itm:
+                self.ca.items[idx] = itm[old_index]
+
+    def __repr__(self):
+        # type: () -> Any
+        return list.__repr__(self)
+
+
+class CommentedKeySeq(tuple, CommentedBase):  # type: ignore
+    """This primarily exists to be able to roundtrip keys that are sequences"""
+
+    def _yaml_add_comment(self, comment, key=NoComment):
+        # type: (Any, Optional[Any]) -> None
+        if key is not NoComment:
+            self.yaml_key_comment_extend(key, comment)
+        else:
+            self.ca.comment = comment
+
+    def _yaml_add_eol_comment(self, comment, key):
+        # type: (Any, Any) -> None
+        self._yaml_add_comment(comment, key=key)
+
+    def _yaml_get_columnX(self, key):
+        # type: (Any) -> Any
+        return self.ca.items[key][0].start_mark.column
+
+    def _yaml_get_column(self, key):
+        # type: (Any) -> Any
+        column = None
+        sel_idx = None
+        pre, post = key - 1, key + 1
+        if pre in self.ca.items:
+            sel_idx = pre
+        elif post in self.ca.items:
+            sel_idx = post
+        else:
+            # self.ca.items is not ordered
+            for row_idx, _k1 in enumerate(self):
+                if row_idx >= key:
+                    break
+                if row_idx not in self.ca.items:
+                    continue
+                sel_idx = row_idx
+        if sel_idx is not None:
+            column = self._yaml_get_columnX(sel_idx)
+        return column
+
+    def _yaml_get_pre_comment(self):
+        # type: () -> Any
+        pre_comments = []  # type: List[Any]
+        if self.ca.comment is None:
+            self.ca.comment = [None, pre_comments]
+        else:
+            self.ca.comment[1] = pre_comments
+        return pre_comments
+
+
+class CommentedMapView(Sized):
+    __slots__ = ('_mapping',)
+
+    def __init__(self, mapping):
+        # type: (Any) -> None
+        self._mapping = mapping
+
+    def __len__(self):
+        # type: () -> int
+        count = len(self._mapping)
+        return count
+
+
+class CommentedMapKeysView(CommentedMapView, Set):  # type: ignore
+    __slots__ = ()
+
+    @classmethod
+    def _from_iterable(self, it):
+        # type: (Any) -> Any
+        return set(it)
+
+    def __contains__(self, key):
+        # type: (Any) -> Any
+        return key in self._mapping
+
+    def __iter__(self):
+        # type: () -> Any  # yield from self._mapping  # not in py27, pypy
+        # for x in self._mapping._keys():
+        for x in self._mapping:
+            yield x
+
+
+class CommentedMapItemsView(CommentedMapView, Set):  # type: ignore
+    __slots__ = ()
+
+    @classmethod
+    def _from_iterable(self, it):
+        # type: (Any) -> Any
+        return set(it)
+
+    def __contains__(self, item):
+        # type: (Any) -> Any
+        key, value = item
+        try:
+            v = self._mapping[key]
+        except KeyError:
+            return False
+        else:
+            return v == value
+
+    def __iter__(self):
+        # type: () -> Any
+        for key in self._mapping._keys():
+            yield (key, self._mapping[key])
+
+
+class CommentedMapValuesView(CommentedMapView):
+    __slots__ = ()
+
+    def __contains__(self, value):
+        # type: (Any) -> Any
+        for key in self._mapping:
+            if value == self._mapping[key]:
+                return True
+        return False
+
+    def __iter__(self):
+        # type: () -> Any
+        for key in self._mapping._keys():
+            yield self._mapping[key]
+
+
+class CommentedMap(ordereddict, CommentedBase):
+    __slots__ = (Comment.attrib, '_ok', '_ref')
+
+    def __init__(self, *args, **kw):
+        # type: (Any, Any) -> None
+        self._ok = set()  # type: MutableSet[Any]  #  own keys
+        self._ref = []  # type: List[CommentedMap]
+        ordereddict.__init__(self, *args, **kw)
+
+    def _yaml_add_comment(self, comment, key=NoComment, value=NoComment):
+        # type: (Any, Optional[Any], Optional[Any]) -> None
+        """values is set to key to indicate a value attachment of comment"""
+        if key is not NoComment:
+            self.yaml_key_comment_extend(key, comment)
+            return
+        if value is not NoComment:
+            self.yaml_value_comment_extend(value, comment)
+        else:
+            self.ca.comment = comment
+
+    def _yaml_add_eol_comment(self, comment, key):
+        # type: (Any, Any) -> None
+        """add on the value line, with value specified by the key"""
+        self._yaml_add_comment(comment, value=key)
+
+    def _yaml_get_columnX(self, key):
+        # type: (Any) -> Any
+        return self.ca.items[key][2].start_mark.column
+
+    def _yaml_get_column(self, key):
+        # type: (Any) -> Any
+        column = None
+        sel_idx = None
+        pre, post, last = None, None, None
+        for x in self:
+            if pre is not None and x != key:
+                post = x
+                break
+            if x == key:
+                pre = last
+            last = x
+        if pre in self.ca.items:
+            sel_idx = pre
+        elif post in self.ca.items:
+            sel_idx = post
+        else:
+            # self.ca.items is not ordered
+            for k1 in self:
+                if k1 >= key:
+                    break
+                if k1 not in self.ca.items:
+                    continue
+                sel_idx = k1
+        if sel_idx is not None:
+            column = self._yaml_get_columnX(sel_idx)
+        return column
+
+    def _yaml_get_pre_comment(self):
+        # type: () -> Any
+        pre_comments = []  # type: List[Any]
+        if self.ca.comment is None:
+            self.ca.comment = [None, pre_comments]
+        else:
+            self.ca.comment[1] = pre_comments
+        return pre_comments
+
+    def update(self, vals):  # type: ignore
+        # type: (Any) -> None
+        try:
+            ordereddict.update(self, vals)
+        except TypeError:
+            # probably a dict that is used
+            for x in vals:
+                self[x] = vals[x]
+        try:
+            self._ok.update(vals.keys())  # type: ignore
+        except AttributeError:
+            # assume a list/tuple of two element lists/tuples
+            for x in vals:
+                self._ok.add(x[0])
+
+    def insert(self, pos, key, value, comment=None):
+        # type: (Any, Any, Any, Optional[Any]) -> None
+        """insert key value into given position
+        attach comment if provided
+        """
+        ordereddict.insert(self, pos, key, value)
+        self._ok.add(key)
+        if comment is not None:
+            self.yaml_add_eol_comment(comment, key=key)
+
+    def mlget(self, key, default=None, list_ok=False):
+        # type: (Any, Any, Any) -> Any
+        """multi-level get that expects dicts within dicts"""
+        if not isinstance(key, list):
+            return self.get(key, default)
+        # assume that the key is a list of recursively accessible dicts
+
+        def get_one_level(key_list, level, d):
+            # type: (Any, Any, Any) -> Any
+            if not list_ok:
+                assert isinstance(d, dict)
+            if level >= len(key_list):
+                if level > len(key_list):
+                    raise IndexError
+                return d[key_list[level - 1]]
+            return get_one_level(key_list, level + 1, d[key_list[level - 1]])
+
+        try:
+            return get_one_level(key, 1, self)
+        except KeyError:
+            return default
+        except (TypeError, IndexError):
+            if not list_ok:
+                raise
+            return default
+
+    def __getitem__(self, key):
+        # type: (Any) -> Any
+        try:
+            return ordereddict.__getitem__(self, key)
+        except KeyError:
+            for merged in getattr(self, merge_attrib, []):
+                if key in merged[1]:
+                    return merged[1][key]
+            raise
+
+    def __setitem__(self, key, value):
+        # type: (Any, Any) -> None
+        # try to preserve the scalarstring type if setting an existing key to a new value
+        if key in self:
+            if (
+                isinstance(value, string_types)
+                and not isinstance(value, ScalarString)
+                and isinstance(self[key], ScalarString)
+            ):
+                value = type(self[key])(value)
+        ordereddict.__setitem__(self, key, value)
+        self._ok.add(key)
+
+    def _unmerged_contains(self, key):
+        # type: (Any) -> Any
+        if key in self._ok:
+            return True
+        return None
+
+    def __contains__(self, key):
+        # type: (Any) -> bool
+        return bool(ordereddict.__contains__(self, key))
+
+    def get(self, key, default=None):
+        # type: (Any, Any) -> Any
+        try:
+            return self.__getitem__(key)
+        except:  # NOQA
+            return default
+
+    def __repr__(self):
+        # type: () -> Any
+        return ordereddict.__repr__(self).replace('CommentedMap', 'ordereddict')
+
+    def non_merged_items(self):
+        # type: () -> Any
+        for x in ordereddict.__iter__(self):
+            if x in self._ok:
+                yield x, ordereddict.__getitem__(self, x)
+
+    def __delitem__(self, key):
+        # type: (Any) -> None
+        # for merged in getattr(self, merge_attrib, []):
+        #     if key in merged[1]:
+        #         value = merged[1][key]
+        #         break
+        # else:
+        #     # not found in merged in stuff
+        #     ordereddict.__delitem__(self, key)
+        #    for referer in self._ref:
+        #        referer.update_key_value(key)
+        #    return
+        #
+        # ordereddict.__setitem__(self, key, value)  # merge might have different value
+        # self._ok.discard(key)
+        self._ok.discard(key)
+        ordereddict.__delitem__(self, key)
+        for referer in self._ref:
+            referer.update_key_value(key)
+
+    def __iter__(self):
+        # type: () -> Any
+        for x in ordereddict.__iter__(self):
+            yield x
+
+    def _keys(self):
+        # type: () -> Any
+        for x in ordereddict.__iter__(self):
+            yield x
+
+    def __len__(self):
+        # type: () -> int
+        return ordereddict.__len__(self)
+
+    def __eq__(self, other):
+        # type: (Any) -> bool
+        return bool(dict(self) == other)
+
+    if PY2:
+
+        def keys(self):
+            # type: () -> Any
+            return list(self._keys())
+
+        def iterkeys(self):
+            # type: () -> Any
+            return self._keys()
+
+        def viewkeys(self):
+            # type: () -> Any
+            return CommentedMapKeysView(self)
+
+    else:
+
+        def keys(self):
+            # type: () -> Any
+            return CommentedMapKeysView(self)
+
+    if PY2:
+
+        def _values(self):
+            # type: () -> Any
+            for x in ordereddict.__iter__(self):
+                yield ordereddict.__getitem__(self, x)
+
+        def values(self):
+            # type: () -> Any
+            return list(self._values())
+
+        def itervalues(self):
+            # type: () -> Any
+            return self._values()
+
+        def viewvalues(self):
+            # type: () -> Any
+            return CommentedMapValuesView(self)
+
+    else:
+
+        def values(self):
+            # type: () -> Any
+            return CommentedMapValuesView(self)
+
+    def _items(self):
+        # type: () -> Any
+        for x in ordereddict.__iter__(self):
+            yield x, ordereddict.__getitem__(self, x)
+
+    if PY2:
+
+        def items(self):
+            # type: () -> Any
+            return list(self._items())
+
+        def iteritems(self):
+            # type: () -> Any
+            return self._items()
+
+        def viewitems(self):
+            # type: () -> Any
+            return CommentedMapItemsView(self)
+
+    else:
+
+        def items(self):
+            # type: () -> Any
+            return CommentedMapItemsView(self)
+
+    @property
+    def merge(self):
+        # type: () -> Any
+        if not hasattr(self, merge_attrib):
+            setattr(self, merge_attrib, [])
+        return getattr(self, merge_attrib)
+
+    def copy(self):
+        # type: () -> Any
+        x = type(self)()  # update doesn't work
+        for k, v in self._items():
+            x[k] = v
+        self.copy_attributes(x)
+        return x
+
+    def add_referent(self, cm):
+        # type: (Any) -> None
+        if cm not in self._ref:
+            self._ref.append(cm)
+
+    def add_yaml_merge(self, value):
+        # type: (Any) -> None
+        for v in value:
+            v[1].add_referent(self)
+            for k, v in v[1].items():
+                if ordereddict.__contains__(self, k):
+                    continue
+                ordereddict.__setitem__(self, k, v)
+        self.merge.extend(value)
+
+    def update_key_value(self, key):
+        # type: (Any) -> None
+        if key in self._ok:
+            return
+        for v in self.merge:
+            if key in v[1]:
+                ordereddict.__setitem__(self, key, v[1][key])
+                return
+        ordereddict.__delitem__(self, key)
+
+    def __deepcopy__(self, memo):
+        # type: (Any) -> Any
+        res = self.__class__()
+        memo[id(self)] = res
+        for k in self:
+            res[k] = copy.deepcopy(self[k], memo)
+        self.copy_attributes(res, memo=memo)
+        return res
+
+
+# based on brownie mappings
+@classmethod  # type: ignore
+def raise_immutable(cls, *args, **kwargs):
+    # type: (Any, *Any, **Any) -> None
+    raise TypeError('{} objects are immutable'.format(cls.__name__))
+
+
+class CommentedKeyMap(CommentedBase, Mapping):  # type: ignore
+    __slots__ = Comment.attrib, '_od'
+    """This primarily exists to be able to roundtrip keys that are mappings"""
+
+    def __init__(self, *args, **kw):
+        # type: (Any, Any) -> None
+        if hasattr(self, '_od'):
+            raise_immutable(self)
+        try:
+            self._od = ordereddict(*args, **kw)
+        except TypeError:
+            if PY2:
+                self._od = ordereddict(args[0].items())
+            else:
+                raise
+
+    __delitem__ = __setitem__ = clear = pop = popitem = setdefault = update = raise_immutable
+
+    # need to implement __getitem__, __iter__ and __len__
+    def __getitem__(self, index):
+        # type: (Any) -> Any
+        return self._od[index]
+
+    def __iter__(self):
+        # type: () -> Iterator[Any]
+        for x in self._od.__iter__():
+            yield x
+
+    def __len__(self):
+        # type: () -> int
+        return len(self._od)
+
+    def __hash__(self):
+        # type: () -> Any
+        return hash(tuple(self.items()))
+
+    def __repr__(self):
+        # type: () -> Any
+        if not hasattr(self, merge_attrib):
+            return self._od.__repr__()
+        return 'ordereddict(' + repr(list(self._od.items())) + ')'
+
+    @classmethod
+    def fromkeys(keys, v=None):
+        # type: (Any, Any) -> Any
+        return CommentedKeyMap(dict.fromkeys(keys, v))
+
+    def _yaml_add_comment(self, comment, key=NoComment):
+        # type: (Any, Optional[Any]) -> None
+        if key is not NoComment:
+            self.yaml_key_comment_extend(key, comment)
+        else:
+            self.ca.comment = comment
+
+    def _yaml_add_eol_comment(self, comment, key):
+        # type: (Any, Any) -> None
+        self._yaml_add_comment(comment, key=key)
+
+    def _yaml_get_columnX(self, key):
+        # type: (Any) -> Any
+        return self.ca.items[key][0].start_mark.column
+
+    def _yaml_get_column(self, key):
+        # type: (Any) -> Any
+        column = None
+        sel_idx = None
+        pre, post = key - 1, key + 1
+        if pre in self.ca.items:
+            sel_idx = pre
+        elif post in self.ca.items:
+            sel_idx = post
+        else:
+            # self.ca.items is not ordered
+            for row_idx, _k1 in enumerate(self):
+                if row_idx >= key:
+                    break
+                if row_idx not in self.ca.items:
+                    continue
+                sel_idx = row_idx
+        if sel_idx is not None:
+            column = self._yaml_get_columnX(sel_idx)
+        return column
+
+    def _yaml_get_pre_comment(self):
+        # type: () -> Any
+        pre_comments = []  # type: List[Any]
+        if self.ca.comment is None:
+            self.ca.comment = [None, pre_comments]
+        else:
+            self.ca.comment[1] = pre_comments
+        return pre_comments
+
+
+class CommentedOrderedMap(CommentedMap):
+    __slots__ = (Comment.attrib,)
+
+
+class CommentedSet(MutableSet, CommentedBase):  # type: ignore  # NOQA
+    __slots__ = Comment.attrib, 'odict'
+
+    def __init__(self, values=None):
+        # type: (Any) -> None
+        self.odict = ordereddict()
+        MutableSet.__init__(self)
+        if values is not None:
+            self |= values  # type: ignore
+
+    def _yaml_add_comment(self, comment, key=NoComment, value=NoComment):
+        # type: (Any, Optional[Any], Optional[Any]) -> None
+        """values is set to key to indicate a value attachment of comment"""
+        if key is not NoComment:
+            self.yaml_key_comment_extend(key, comment)
+            return
+        if value is not NoComment:
+            self.yaml_value_comment_extend(value, comment)
+        else:
+            self.ca.comment = comment
+
+    def _yaml_add_eol_comment(self, comment, key):
+        # type: (Any, Any) -> None
+        """add on the value line, with value specified by the key"""
+        self._yaml_add_comment(comment, value=key)
+
+    def add(self, value):
+        # type: (Any) -> None
+        """Add an element."""
+        self.odict[value] = None
+
+    def discard(self, value):
+        # type: (Any) -> None
+        """Remove an element.  Do not raise an exception if absent."""
+        del self.odict[value]
+
+    def __contains__(self, x):
+        # type: (Any) -> Any
+        return x in self.odict
+
+    def __iter__(self):
+        # type: () -> Any
+        for x in self.odict:
+            yield x
+
+    def __len__(self):
+        # type: () -> int
+        return len(self.odict)
+
+    def __repr__(self):
+        # type: () -> str
+        return 'set({0!r})'.format(self.odict.keys())
+
+
+class TaggedScalar(CommentedBase):
+    # the value and style attributes are set during roundtrip construction
+    def __init__(self):
+        # type: () -> None
+        self.value = None
+        self.style = None
+
+    def __str__(self):
+        # type: () -> Any
+        return self.value
+
+
+def dump_comments(d, name="", sep='.', out=sys.stdout):
+    # type: (Any, str, str, Any) -> None
+    """
+    recursively dump comments, all but the toplevel preceded by the path
+    in dotted form x.0.a
+    """
+    if isinstance(d, dict) and hasattr(d, 'ca'):
+        if name:
+            sys.stdout.write('{}\n'.format(name))
+        out.write('{}\n'.format(d.ca))  # type: ignore
+        for k in d:
+            dump_comments(d[k], name=(name + sep + k) if name else k, sep=sep, out=out)
+    elif isinstance(d, list) and hasattr(d, 'ca'):
+        if name:
+            sys.stdout.write('{}\n'.format(name))
+        out.write('{}\n'.format(d.ca))  # type: ignore
+        for idx, k in enumerate(d):
+            dump_comments(
+                k, name=(name + sep + str(idx)) if name else str(idx), sep=sep, out=out
+            )