Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

aroundthecode / ruamel.yaml   python

Repository URL to install this package:

/ yaml / comments.py

# 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, MutableMapping, Mapping
else:
    from collections.abc import MutableSet, Sized, Set, MutableMapping, 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):
Loading ...