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 / emitter.py

# coding: utf-8

from __future__ import absolute_import
from __future__ import print_function

# Emitter expects events obeying the following grammar:
# stream ::= STREAM-START document* STREAM-END
# document ::= DOCUMENT-START node DOCUMENT-END
# node ::= SCALAR | sequence | mapping
# sequence ::= SEQUENCE-START node* SEQUENCE-END
# mapping ::= MAPPING-START (node node)* MAPPING-END

import sys
from ruamel.yaml.error import YAMLError, YAMLStreamError
from ruamel.yaml.events import *  # NOQA

# fmt: off
from ruamel.yaml.compat import utf8, text_type, PY2, nprint, dbg, DBG_EVENT, \
    check_anchorname_char
# fmt: on

if False:  # MYPY
    from typing import Any, Dict, List, Union, Text, Tuple, Optional  # NOQA
    from ruamel.yaml.compat import StreamType  # NOQA

__all__ = ['Emitter', 'EmitterError']


class EmitterError(YAMLError):
    pass


class ScalarAnalysis(object):
    def __init__(
        self,
        scalar,
        empty,
        multiline,
        allow_flow_plain,
        allow_block_plain,
        allow_single_quoted,
        allow_double_quoted,
        allow_block,
    ):
        # type: (Any, Any, Any, bool, bool, bool, bool, bool) -> None
        self.scalar = scalar
        self.empty = empty
        self.multiline = multiline
        self.allow_flow_plain = allow_flow_plain
        self.allow_block_plain = allow_block_plain
        self.allow_single_quoted = allow_single_quoted
        self.allow_double_quoted = allow_double_quoted
        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
        base = self.values[-1][0] if self.values[-1][0] is not None else 0
        return base + seq_indent - column - 1

    def __len__(self):
        # type: () -> int
        return len(self.values)


class Emitter(object):
    # fmt: off
    DEFAULT_TAG_PREFIXES = {
        u'!': u'!',
        u'tag:yaml.org,2002:': u'!!',
    }
    # fmt: on

    MAX_SIMPLE_KEY_LENGTH = 128

    def __init__(
        self,
        stream,
        canonical=None,
        indent=None,
        width=None,
        allow_unicode=None,
        line_break=None,
        block_seq_indent=None,
        top_level_colon_align=None,
        prefix_colon=None,
        brace_single_entry_mapping_in_flow_sequence=None,
        dumper=None,
    ):
        # type: (StreamType, Any, Optional[int], Optional[int], Optional[bool], Any, Optional[int], Optional[bool], Any, Optional[bool], Any) -> None  # NOQA
        self.dumper = dumper
        if self.dumper is not None and getattr(self.dumper, '_emitter', None) is None:
            self.dumper._emitter = self
        self.stream = stream

        # Encoding can be overriden by STREAM-START.
        self.encoding = None  # type: Optional[Text]
        self.allow_space_break = None

        # Emitter is a state machine with a stack of states to handle nested
        # structures.
        self.states = []  # type: List[Any]
        self.state = self.expect_stream_start  # type: Any

        # Current event and the event queue.
        self.events = []  # type: List[Any]
        self.event = None  # type: Any

        # The current indentation level and the stack of previous indents.
        self.indents = Indents()
        self.indent = None  # type: Optional[int]

        # flow_context is an expanding/shrinking list consisting of '{' and '['
        # for each unclosed flow context. If empty list that means block context
        self.flow_context = []  # type: List[Text]

        # Contexts.
        self.root_context = False
        self.sequence_context = False
        self.mapping_context = False
        self.simple_key_context = False

        # Characteristics of the last emitted character:
        #  - current position.
        #  - is it a whitespace?
        #  - is it an indention character
        #    (indentation space, '-', '?', or ':')?
        self.line = 0
        self.column = 0
        self.whitespace = True
        self.indention = True
        self.compact_seq_seq = True  # dash after dash
        self.compact_seq_map = True  # key after dash
        # self.compact_ms = False   # dash after key, only when excplicit key with ?
        self.no_newline = None  # type: Optional[bool]  # set if directly after `- `

        # Whether the document requires an explicit document indicator
        self.open_ended = False

        # colon handling
        self.colon = u':'
        self.prefixed_colon = self.colon if prefix_colon is None else prefix_colon + self.colon
        # single entry mappings in flow sequence
        self.brace_single_entry_mapping_in_flow_sequence = (
            brace_single_entry_mapping_in_flow_sequence
        )  # NOQA

        # Formatting details.
        self.canonical = canonical
        self.allow_unicode = allow_unicode
        # set to False to get "\Uxxxxxxxx" for non-basic unicode like emojis
        self.unicode_supplementary = sys.maxunicode > 0xffff
        self.sequence_dash_offset = block_seq_indent if block_seq_indent else 0
        self.top_level_colon_align = top_level_colon_align
        self.best_sequence_indent = 2
        self.requested_indent = indent  # specific for literal zero indent
        if indent and 1 < indent < 10:
            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_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']:
            self.best_line_break = line_break

        # Tag prefixes.
        self.tag_prefixes = None  # type: Any

        # Prepared anchor and tag.
        self.prepared_anchor = None  # type: Any
        self.prepared_tag = None  # type: Any

        # Scalar analysis and style.
        self.analysis = None  # type: Any
        self.style = None  # type: Any

    @property
    def stream(self):
        # type: () -> Any
        try:
            return self._stream
        except AttributeError:
            raise YAMLStreamError('output stream needs to specified')

    @stream.setter
    def stream(self, val):
        # type: (Any) -> None
        if val is None:
            return
        if not hasattr(val, 'write'):
            raise YAMLStreamError('stream argument needs to have a write() method')
        self._stream = val

    @property
    def serializer(self):
        # type: () -> Any
        try:
            if hasattr(self.dumper, 'typ'):
                return self.dumper.serializer
            return self.dumper._serializer
        except AttributeError:
            return self  # cyaml

    @property
    def flow_level(self):
        # type: () -> int
        return len(self.flow_context)

    def dispose(self):
        # type: () -> None
        # Reset the state attributes (to clear self-references)
        self.states = []  # type: List[Any]
        self.state = None

    def emit(self, event):
        # type: (Any) -> None
        if dbg(DBG_EVENT):
            nprint(event)
        self.events.append(event)
        while not self.need_more_events():
            self.event = self.events.pop(0)
            self.state()
            self.event = None

    # In some cases, we wait for a few next events before emitting.

    def need_more_events(self):
        # type: () -> bool
        if not self.events:
            return True
        event = self.events[0]
        if isinstance(event, DocumentStartEvent):
            return self.need_events(1)
        elif isinstance(event, SequenceStartEvent):
            return self.need_events(2)
        elif isinstance(event, MappingStartEvent):
            return self.need_events(3)
        else:
            return False

    def need_events(self, count):
        # type: (int) -> bool
        level = 0
        for event in self.events[1:]:
            if isinstance(event, (DocumentStartEvent, CollectionStartEvent)):
                level += 1
            elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)):
                level -= 1
            elif isinstance(event, StreamEndEvent):
                level = -1
            if level < 0:
                return False
        return len(self.events) < count + 1

    def increase_indent(self, flow=False, sequence=None, indentless=False):
        # type: (bool, Optional[bool], bool) -> None
        self.indents.append(self.indent, sequence)
        if self.indent is None:  # top level
            if flow:
                # 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_sequence_indent if self.indents.last_seq() else self.best_map_indent
            )
            # if self.indents.last_seq():
            #     if self.indent == 0: # top level block sequence
            #         self.indent = self.best_sequence_indent - self.sequence_dash_offset
            #     else:
            #         self.indent += self.best_sequence_indent
            # else:
            #     self.indent += self.best_map_indent

    # States.

    # Stream handlers.

    def expect_stream_start(self):
        # type: () -> None
        if isinstance(self.event, StreamStartEvent):
            if PY2:
                if self.event.encoding and not getattr(self.stream, 'encoding', None):
                    self.encoding = self.event.encoding
            else:
                if self.event.encoding and not hasattr(self.stream, 'encoding'):
                    self.encoding = self.event.encoding
            self.write_stream_start()
            self.state = self.expect_first_document_start
        else:
            raise EmitterError('expected StreamStartEvent, but got %s' % self.event)

    def expect_nothing(self):
        # type: () -> None
        raise EmitterError('expected nothing, but got %s' % self.event)

    # Document handlers.

    def expect_first_document_start(self):
        # type: () -> Any
        return self.expect_document_start(first=True)

    def expect_document_start(self, first=False):
        # type: (bool) -> None
        if isinstance(self.event, DocumentStartEvent):
            if (self.event.version or self.event.tags) and self.open_ended:
                self.write_indicator(u'...', True)
                self.write_indent()
            if self.event.version:
                version_text = self.prepare_version(self.event.version)
                self.write_version_directive(version_text)
            self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy()
Loading ...