# 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 ...