This commit is contained in:
Iliyan Angelov
2025-12-01 06:50:10 +02:00
parent 91f51bc6fe
commit 62c1fe5951
4682 changed files with 544807 additions and 31208 deletions

View File

@@ -0,0 +1,49 @@
from __future__ import annotations
if False: # MYPY
from typing import Dict, Any # NOQA
_package_data = dict(
full_package_name='ruamel.yaml',
version_info=(0, 18, 16),
__version__='0.18.16',
version_timestamp='2025-10-22 19:50:12',
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
entry_points=None,
since=2014,
extras_require={
':platform_python_implementation=="CPython" and python_version<"3.14"': ['ruamel.yaml.clib>=0.2.7'], # NOQA
'jinja2': ['ruamel.yaml.jinja2>=0.2'],
'docs': ['ryd', 'mercurial>5.7'],
},
classifiers=[
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Processing :: Markup',
'Typing :: Typed',
],
keywords='yaml 1.2 parser round-trip preserve quotes order config',
url_doc='https://yaml.dev/doc/{full_package_name}',
tox=dict(
env='*',
fl8excl='_test/lib,branch_default',
),
# universal=True,
supported=[(3, 8)], # minimum
) # type: Dict[Any, Any]
version_info = _package_data['version_info']
__version__ = _package_data['__version__']
try:
from .cyaml import * # NOQA
__with_libyaml__ = True
except (ImportError, ValueError): # for Jython
__with_libyaml__ = False
from ruamel.yaml.main import * # NOQA

View File

@@ -0,0 +1,20 @@
from __future__ import annotations
if False: # MYPY
from typing import Any, Dict, Optional, List, Union, Iterator # NOQA
anchor_attrib = '_yaml_anchor'
class Anchor:
__slots__ = 'value', 'always_dump'
attrib = anchor_attrib
def __init__(self) -> None:
self.value = None
self.always_dump = False
def __repr__(self) -> Any:
ad = ', (always dump)' if self.always_dump else ""
return f'Anchor({self.value!r}{ad})'

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,236 @@
from __future__ import annotations
# partially from package six by Benjamin Peterson
import sys
import os
import io
from abc import abstractmethod
import collections.abc
from ruamel.yaml.docinfo import Version # NOQA
# fmt: off
if False: # MYPY
from typing import Any, Dict, Optional, List, Union, BinaryIO, IO, Text, Tuple # NOQA
from typing import Optional # NOQA
try:
from typing import SupportsIndex as SupportsIndex # in order to reexport for mypy
except ImportError:
SupportsIndex = int # type: ignore
StreamType = Any
StreamTextType = StreamType
VersionType = Union[str , Tuple[int, int] , List[int] , Version , None]
# fmt: on
_DEFAULT_YAML_VERSION = (1, 2)
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict # type: ignore
# to get the right name import ... as ordereddict doesn't do that
class ordereddict(OrderedDict): # type: ignore
if not hasattr(OrderedDict, 'insert'):
def insert(self, pos: int, key: Any, value: Any) -> None:
if pos >= len(self):
self[key] = value
return
od = ordereddict()
od.update(self)
for k in od:
del self[k]
for index, old_key in enumerate(od):
if pos == index:
self[key] = value
self[old_key] = od[old_key]
StringIO = io.StringIO
BytesIO = io.BytesIO
builtins_module = 'builtins'
def with_metaclass(meta: Any, *bases: Any) -> Any:
"""Create a base class with a metaclass."""
return meta('NewBase', bases, {})
DBG_TOKEN = 1
DBG_EVENT = 2
DBG_NODE = 4
_debug: Optional[int] = None
if 'RUAMEL_DEBUG' in os.environ:
_debugx = os.environ.get('RUAMEL_DEBUG')
if _debugx is None:
_debug = 0
else:
_debug = int(_debugx)
if bool(_debug):
class ObjectCounter:
def __init__(self) -> None:
self.map: Dict[Any, Any] = {}
def __call__(self, k: Any) -> None:
self.map[k] = self.map.get(k, 0) + 1
def dump(self) -> None:
for k in sorted(self.map):
sys.stdout.write(f'{k} -> {self.map[k]}')
object_counter = ObjectCounter()
# used from yaml util when testing
def dbg(val: Any = None) -> Any:
debug = _debug
if debug is None:
# set to true or false
_debugx = os.environ.get('YAMLDEBUG')
if _debugx is None:
debug = 0
else:
debug = int(_debugx)
if val is None:
return debug
return debug & val
class Nprint:
def __init__(self, file_name: Any = None) -> None:
self._max_print: Any = None
self._count: Any = None
self._file_name = file_name
def __call__(self, *args: Any, **kw: Any) -> None:
if not bool(_debug):
return
import traceback
out = sys.stdout if self._file_name is None else open(self._file_name, 'a')
dbgprint = print # to fool checking for print statements by dv utility
kw1 = kw.copy()
kw1['file'] = out
dbgprint(*args, **kw1)
out.flush()
if self._max_print is not None:
if self._count is None:
self._count = self._max_print
self._count -= 1
if self._count == 0:
dbgprint('forced exit\n')
traceback.print_stack()
out.flush()
sys.exit(0)
if self._file_name:
out.close()
def set_max_print(self, i: int) -> None:
self._max_print = i
self._count = None
def fp(self, mode: str = 'a') -> Any:
out = sys.stdout if self._file_name is None else open(self._file_name, mode)
return out
nprint = Nprint()
nprintf = Nprint('/var/tmp/ruamel.yaml.log')
# char checkers following production rules
def check_namespace_char(ch: Any) -> bool:
if '\x21' <= ch <= '\x7E': # ! to ~
return True
if '\xA0' <= ch <= '\uD7FF':
return True
if ('\uE000' <= ch <= '\uFFFD') and ch != '\uFEFF': # excl. byte order mark
return True
if '\U00010000' <= ch <= '\U0010FFFF':
return True
return False
def check_anchorname_char(ch: Any) -> bool:
if ch in ',[]{}':
return False
return check_namespace_char(ch)
def version_tnf(t1: Any, t2: Any = None) -> Any:
"""
return True if ruamel.yaml version_info < t1, None if t2 is specified and bigger else False
"""
from ruamel.yaml import version_info # NOQA
if version_info < t1:
return True
if t2 is not None and version_info < t2:
return None
return False
class MutableSliceableSequence(collections.abc.MutableSequence): # type: ignore
__slots__ = ()
def __getitem__(self, index: Any) -> Any:
if not isinstance(index, slice):
return self.__getsingleitem__(index)
return type(self)([self[i] for i in range(*index.indices(len(self)))]) # type: ignore
def __setitem__(self, index: Any, value: Any) -> None:
if not isinstance(index, slice):
return self.__setsingleitem__(index, value)
assert iter(value)
# nprint(index.start, index.stop, index.step, index.indices(len(self)))
if index.step is None:
del self[index.start : index.stop]
for elem in reversed(value):
self.insert(0 if index.start is None else index.start, elem)
else:
range_parms = index.indices(len(self))
nr_assigned_items = (range_parms[1] - range_parms[0] - 1) // range_parms[2] + 1
# need to test before changing, in case TypeError is caught
if nr_assigned_items < len(value):
raise TypeError(
f'too many elements in value {nr_assigned_items} < {len(value)}',
)
elif nr_assigned_items > len(value):
raise TypeError(
f'not enough elements in value {nr_assigned_items} > {len(value)}',
)
for idx, i in enumerate(range(*range_parms)):
self[i] = value[idx]
def __delitem__(self, index: Any) -> None:
if not isinstance(index, slice):
return self.__delsingleitem__(index)
# nprint(index.start, index.stop, index.step, index.indices(len(self)))
for i in reversed(range(*index.indices(len(self)))):
del self[i]
@abstractmethod
def __getsingleitem__(self, index: Any) -> Any:
raise IndexError
@abstractmethod
def __setsingleitem__(self, index: Any, value: Any) -> None:
raise IndexError
@abstractmethod
def __delsingleitem__(self, index: Any) -> None:
raise IndexError

View File

@@ -0,0 +1,231 @@
from __future__ import annotations
import warnings
from ruamel.yaml.error import MarkedYAMLError, ReusedAnchorWarning
from ruamel.yaml.compat import nprint, nprintf # NOQA
from ruamel.yaml.events import (
StreamStartEvent,
StreamEndEvent,
MappingStartEvent,
MappingEndEvent,
SequenceStartEvent,
SequenceEndEvent,
AliasEvent,
ScalarEvent,
)
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode
if False: # MYPY
from typing import Any, Dict, Optional, List # NOQA
__all__ = ['Composer', 'ComposerError']
class ComposerError(MarkedYAMLError):
pass
class Composer:
def __init__(self, loader: Any = None) -> None:
self.loader = loader
if self.loader is not None and getattr(self.loader, '_composer', None) is None:
self.loader._composer = self
self.anchors: Dict[Any, Any] = {}
self.warn_double_anchors = True
@property
def parser(self) -> Any:
if hasattr(self.loader, 'typ'):
self.loader.parser
return self.loader._parser
@property
def resolver(self) -> Any:
# assert self.loader._resolver is not None
if hasattr(self.loader, 'typ'):
self.loader.resolver
return self.loader._resolver
def check_node(self) -> Any:
# Drop the STREAM-START event.
if self.parser.check_event(StreamStartEvent):
self.parser.get_event()
# If there are more documents available?
return not self.parser.check_event(StreamEndEvent)
def get_node(self) -> Any:
# Get the root node of the next document.
if not self.parser.check_event(StreamEndEvent):
return self.compose_document()
def get_single_node(self) -> Any:
# Drop the STREAM-START event.
self.parser.get_event()
# Compose a document if the stream is not empty.
document: Any = None
if not self.parser.check_event(StreamEndEvent):
document = self.compose_document()
# Ensure that the stream contains no more documents.
if not self.parser.check_event(StreamEndEvent):
event = self.parser.get_event()
raise ComposerError(
'expected a single document in the stream',
document.start_mark,
'but found another document',
event.start_mark,
)
# Drop the STREAM-END event.
self.parser.get_event()
return document
def compose_document(self: Any) -> Any:
self.anchors = {}
# Drop the DOCUMENT-START event.
self.parser.get_event()
# Compose the root node.
node = self.compose_node(None, None)
# Drop the DOCUMENT-END event.
self.parser.get_event()
return node
def return_alias(self, a: Any) -> Any:
return a
def compose_node(self, parent: Any, index: Any) -> Any:
if self.parser.check_event(AliasEvent):
event = self.parser.get_event()
alias = event.anchor
if alias not in self.anchors:
raise ComposerError(
None, None, f'found undefined alias {alias!r}', event.start_mark,
)
return self.return_alias(self.anchors[alias])
event = self.parser.peek_event()
anchor = event.anchor
if anchor is not None: # have an anchor
if self.warn_double_anchors and anchor in self.anchors:
ws = (
f'\nfound duplicate anchor {anchor!r}\n'
f'first occurrence {self.anchors[anchor].start_mark}\n'
f'second occurrence {event.start_mark}'
)
warnings.warn(ws, ReusedAnchorWarning, stacklevel=2)
self.resolver.descend_resolver(parent, index)
if self.parser.check_event(ScalarEvent):
node = self.compose_scalar_node(anchor)
elif self.parser.check_event(SequenceStartEvent):
node = self.compose_sequence_node(anchor)
elif self.parser.check_event(MappingStartEvent):
node = self.compose_mapping_node(anchor)
self.resolver.ascend_resolver()
return node
def compose_scalar_node(self, anchor: Any) -> Any:
event = self.parser.get_event()
tag = event.ctag
if tag is None or str(tag) == '!':
tag = self.resolver.resolve(ScalarNode, event.value, event.implicit)
assert not isinstance(tag, str)
# e.g tag.yaml.org,2002:str
node = ScalarNode(
tag,
event.value,
event.start_mark,
event.end_mark,
style=event.style,
comment=event.comment,
anchor=anchor,
)
if anchor is not None:
self.anchors[anchor] = node
return node
def compose_sequence_node(self, anchor: Any) -> Any:
start_event = self.parser.get_event()
tag = start_event.ctag
if tag is None or str(tag) == '!':
tag = self.resolver.resolve(SequenceNode, None, start_event.implicit)
assert not isinstance(tag, str)
node = SequenceNode(
tag,
[],
start_event.start_mark,
None,
flow_style=start_event.flow_style,
comment=start_event.comment,
anchor=anchor,
)
if anchor is not None:
self.anchors[anchor] = node
index = 0
while not self.parser.check_event(SequenceEndEvent):
node.value.append(self.compose_node(node, index))
index += 1
end_event = self.parser.get_event()
if node.flow_style is True and end_event.comment is not None:
if node.comment is not None:
x = node.flow_style
nprint(
f'Warning: unexpected end_event commment in sequence node {x}\n',
' if possible, please report an issue with reproducable data/code',
)
node.comment = end_event.comment
node.end_mark = end_event.end_mark
self.check_end_doc_comment(end_event, node)
return node
def compose_mapping_node(self, anchor: Any) -> Any:
start_event = self.parser.get_event()
tag = start_event.ctag
if tag is None or str(tag) == '!':
tag = self.resolver.resolve(MappingNode, None, start_event.implicit)
assert not isinstance(tag, str)
node = MappingNode(
tag,
[],
start_event.start_mark,
None,
flow_style=start_event.flow_style,
comment=start_event.comment,
anchor=anchor,
)
if anchor is not None:
self.anchors[anchor] = node
while not self.parser.check_event(MappingEndEvent):
# key_event = self.parser.peek_event()
item_key = self.compose_node(node, None)
# if item_key in node.value:
# raise ComposerError("while composing a mapping",
# start_event.start_mark,
# "found duplicate key", key_event.start_mark)
item_value = self.compose_node(node, item_key)
# node.value[item_key] = item_value
node.value.append((item_key, item_value))
end_event = self.parser.get_event()
if node.flow_style is True and end_event.comment is not None:
node.comment = end_event.comment
node.end_mark = end_event.end_mark
self.check_end_doc_comment(end_event, node)
return node
def check_end_doc_comment(self, end_event: Any, node: Any) -> None:
if end_event.comment and end_event.comment[1]:
# pre comments on an end_event, no following to move to
if node.comment is None:
node.comment = [None, None]
assert not isinstance(node, ScalarEvent)
# this is a post comment on a mapping node, add as third element
# in the list
node.comment.append(end_event.comment[1])
end_event.comment[1] = None

View File

@@ -0,0 +1,17 @@
from __future__ import annotations
import warnings
from ruamel.yaml.util import configobj_walker as new_configobj_walker
if False: # MYPY
from typing import Any
def configobj_walker(cfg: Any) -> Any:
warnings.warn(
'configobj_walker has moved to ruamel.yaml.util, please update your code',
stacklevel=2,
)
return new_configobj_walker(cfg)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
from __future__ import annotations
from _ruamel_yaml import CParser, CEmitter # type: ignore
from ruamel.yaml.constructor import Constructor, BaseConstructor, SafeConstructor
from ruamel.yaml.representer import Representer, SafeRepresenter, BaseRepresenter
from ruamel.yaml.resolver import Resolver, BaseResolver
if False: # MYPY
from typing import Any, Union, Optional # NOQA
from ruamel.yaml.compat import StreamTextType, StreamType, VersionType # NOQA
__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader', 'CBaseDumper', 'CSafeDumper', 'CDumper']
# this includes some hacks to solve the usage of resolver by lower level
# parts of the parser
class CBaseLoader(CParser, BaseConstructor, BaseResolver): # type: ignore
def __init__(
self,
stream: StreamTextType,
version: Optional[VersionType] = None,
preserve_quotes: Optional[bool] = None,
) -> None:
CParser.__init__(self, stream)
self._parser = self._composer = self
BaseConstructor.__init__(self, loader=self)
BaseResolver.__init__(self, loadumper=self)
# self.descend_resolver = self._resolver.descend_resolver
# self.ascend_resolver = self._resolver.ascend_resolver
# self.resolve = self._resolver.resolve
class CSafeLoader(CParser, SafeConstructor, Resolver): # type: ignore
def __init__(
self,
stream: StreamTextType,
version: Optional[VersionType] = None,
preserve_quotes: Optional[bool] = None,
) -> None:
CParser.__init__(self, stream)
self._parser = self._composer = self
SafeConstructor.__init__(self, loader=self)
Resolver.__init__(self, loadumper=self)
# self.descend_resolver = self._resolver.descend_resolver
# self.ascend_resolver = self._resolver.ascend_resolver
# self.resolve = self._resolver.resolve
class CLoader(CParser, Constructor, Resolver): # type: ignore
def __init__(
self,
stream: StreamTextType,
version: Optional[VersionType] = None,
preserve_quotes: Optional[bool] = None,
) -> None:
CParser.__init__(self, stream)
self._parser = self._composer = self
Constructor.__init__(self, loader=self)
Resolver.__init__(self, loadumper=self)
# self.descend_resolver = self._resolver.descend_resolver
# self.ascend_resolver = self._resolver.ascend_resolver
# self.resolve = self._resolver.resolve
class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): # type: ignore
def __init__(
self: StreamType,
stream: Any,
default_style: Any = None,
default_flow_style: Any = None,
canonical: Optional[bool] = None,
indent: Optional[int] = None,
width: Optional[int] = None,
allow_unicode: Optional[bool] = None,
line_break: Any = None,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Any = None,
tags: Any = None,
block_seq_indent: Any = None,
top_level_colon_align: Any = None,
prefix_colon: Any = None,
) -> None:
# NOQA
CEmitter.__init__(
self,
stream,
canonical=canonical,
indent=indent,
width=width,
encoding=encoding,
allow_unicode=allow_unicode,
line_break=line_break,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
)
self._emitter = self._serializer = self._representer = self
BaseRepresenter.__init__(
self,
default_style=default_style,
default_flow_style=default_flow_style,
dumper=self,
)
BaseResolver.__init__(self, loadumper=self)
class CSafeDumper(CEmitter, SafeRepresenter, Resolver): # type: ignore
def __init__(
self: StreamType,
stream: Any,
default_style: Any = None,
default_flow_style: Any = None,
canonical: Optional[bool] = None,
indent: Optional[int] = None,
width: Optional[int] = None,
allow_unicode: Optional[bool] = None,
line_break: Any = None,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Any = None,
tags: Any = None,
block_seq_indent: Any = None,
top_level_colon_align: Any = None,
prefix_colon: Any = None,
) -> None:
# NOQA
self._emitter = self._serializer = self._representer = self
CEmitter.__init__(
self,
stream,
canonical=canonical,
indent=indent,
width=width,
encoding=encoding,
allow_unicode=allow_unicode,
line_break=line_break,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
)
self._emitter = self._serializer = self._representer = self
SafeRepresenter.__init__(
self, default_style=default_style, default_flow_style=default_flow_style,
)
Resolver.__init__(self)
class CDumper(CEmitter, Representer, Resolver): # type: ignore
def __init__(
self: StreamType,
stream: Any,
default_style: Any = None,
default_flow_style: Any = None,
canonical: Optional[bool] = None,
indent: Optional[int] = None,
width: Optional[int] = None,
allow_unicode: Optional[bool] = None,
line_break: Any = None,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Any = None,
tags: Any = None,
block_seq_indent: Any = None,
top_level_colon_align: Any = None,
prefix_colon: Any = None,
) -> None:
# NOQA
CEmitter.__init__(
self,
stream,
canonical=canonical,
indent=indent,
width=width,
encoding=encoding,
allow_unicode=allow_unicode,
line_break=line_break,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
)
self._emitter = self._serializer = self._representer = self
Representer.__init__(
self, default_style=default_style, default_flow_style=default_flow_style,
)
Resolver.__init__(self)

View File

@@ -0,0 +1,130 @@
from __future__ import annotations
"""
DocInfo
Although it was possible to read tag directives before this, all handle/prefix
pairs for all documents in all streams were stored in one dictionary per
YAML instance, making it impossible to distinguish where such a pair came
from without sublassing the scanner.
ToDo:
DocInfo can be used by a yaml dumper to dump a class
- if connected to the root of a data structure
- if provided to the dumper?
"""
if False: # MYPY
from typing import Optional, Tuple, Any
# from dataclasses import dataclass, field, MISSING # NOQA
# @dataclass(order=True, frozen=True)
class Version:
# major: int
# minor: int
def __init__(self, major: int, minor: int) -> None:
self._major = major
self._minor = minor
@property
def major(self) -> int:
return self._major
@property
def minor(self) -> int:
return self._minor
def __eq__(self, v: Any) -> bool:
if not isinstance(v, Version):
return False
return self._major == v._major and self._minor == v._minor
def __lt__(self, v: Version) -> bool:
if self._major < v._major:
return True
if self._major > v._major:
return False
return self._minor < v._minor
def __le__(self, v: Version) -> bool:
if self._major < v._major:
return True
if self._major > v._major:
return False
return self._minor <= v._minor
def __gt__(self, v: Version) -> bool:
if self._major > v._major:
return True
if self._major < v._major:
return False
return self._minor > v._minor
def __ge__(self, v: Version) -> bool:
if self._major > v._major:
return True
if self._major < v._major:
return False
return self._minor >= v._minor
def version(
major: int | str | Tuple[int, int] | None,
minor: Optional[int] = None,
) -> Optional[Version]:
if major is None:
assert minor is None
return None
if isinstance(major, str):
assert minor is None
parts = major.split('.')
assert len(parts) == 2
return Version(int(parts[0]), int(parts[1]))
elif isinstance(major, tuple):
assert minor is None
assert len(major) == 2
major, minor = major
assert minor is not None
return Version(major, minor)
# @dataclass(frozen=True)
class Tag:
# handle: str
# prefix: str
def __init__(self, handle: str, prefix: str) -> None:
self._handle = handle
self._prefix = prefix
@property
def handle(self) -> str:
return self._handle
@property
def prefix(self) -> str:
return self._prefix
# @dataclass
class DocInfo:
"""
Store document information, can be used for analysis of a loaded YAML document
requested_version: if explicitly set before load
doc_version: from %YAML directive
tags: from %TAG directives in scanned order
"""
# requested_version: Optional[Version] = None
# doc_version: Optional[Version] = None
# tags: list[Tag] = field(default_factory=list)
def __init__(
self,
requested_version: Optional[Version] = None,
doc_version: Optional[Version] = None,
tags: Optional[list[Tag]] = None,
):
self.requested_version = requested_version
self.doc_version = doc_version
self.tags = [] if tags is None else tags

View File

@@ -0,0 +1,220 @@
from __future__ import annotations
from ruamel.yaml.emitter import Emitter
from ruamel.yaml.serializer import Serializer
from ruamel.yaml.representer import (
Representer,
SafeRepresenter,
BaseRepresenter,
RoundTripRepresenter,
)
from ruamel.yaml.resolver import Resolver, BaseResolver, VersionedResolver
if False: # MYPY
from typing import Any, Dict, List, Union, Optional # NOQA
from ruamel.yaml.compat import StreamType, VersionType # NOQA
__all__ = ['BaseDumper', 'SafeDumper', 'Dumper', 'RoundTripDumper']
class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver):
def __init__(
self: Any,
stream: StreamType,
default_style: Any = None,
default_flow_style: Any = None,
canonical: Optional[bool] = None,
indent: Optional[int] = None,
width: Optional[int] = None,
allow_unicode: Optional[bool] = None,
line_break: Any = None,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Any = None,
tags: Any = None,
block_seq_indent: Any = None,
top_level_colon_align: Any = None,
prefix_colon: Any = None,
) -> None:
# NOQA
Emitter.__init__(
self,
stream,
canonical=canonical,
indent=indent,
width=width,
allow_unicode=allow_unicode,
line_break=line_break,
block_seq_indent=block_seq_indent,
dumper=self,
)
Serializer.__init__(
self,
encoding=encoding,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
dumper=self,
)
BaseRepresenter.__init__(
self,
default_style=default_style,
default_flow_style=default_flow_style,
dumper=self,
)
BaseResolver.__init__(self, loadumper=self)
class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver):
def __init__(
self,
stream: StreamType,
default_style: Any = None,
default_flow_style: Any = None,
canonical: Optional[bool] = None,
indent: Optional[int] = None,
width: Optional[int] = None,
allow_unicode: Optional[bool] = None,
line_break: Any = None,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Any = None,
tags: Any = None,
block_seq_indent: Any = None,
top_level_colon_align: Any = None,
prefix_colon: Any = None,
) -> None:
# NOQA
Emitter.__init__(
self,
stream,
canonical=canonical,
indent=indent,
width=width,
allow_unicode=allow_unicode,
line_break=line_break,
block_seq_indent=block_seq_indent,
dumper=self,
)
Serializer.__init__(
self,
encoding=encoding,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
dumper=self,
)
SafeRepresenter.__init__(
self,
default_style=default_style,
default_flow_style=default_flow_style,
dumper=self,
)
Resolver.__init__(self, loadumper=self)
class Dumper(Emitter, Serializer, Representer, Resolver):
def __init__(
self,
stream: StreamType,
default_style: Any = None,
default_flow_style: Any = None,
canonical: Optional[bool] = None,
indent: Optional[int] = None,
width: Optional[int] = None,
allow_unicode: Optional[bool] = None,
line_break: Any = None,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Any = None,
tags: Any = None,
block_seq_indent: Any = None,
top_level_colon_align: Any = None,
prefix_colon: Any = None,
) -> None:
# NOQA
Emitter.__init__(
self,
stream,
canonical=canonical,
indent=indent,
width=width,
allow_unicode=allow_unicode,
line_break=line_break,
block_seq_indent=block_seq_indent,
dumper=self,
)
Serializer.__init__(
self,
encoding=encoding,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
dumper=self,
)
Representer.__init__(
self,
default_style=default_style,
default_flow_style=default_flow_style,
dumper=self,
)
Resolver.__init__(self, loadumper=self)
class RoundTripDumper(Emitter, Serializer, RoundTripRepresenter, VersionedResolver):
def __init__(
self,
stream: StreamType,
default_style: Any = None,
default_flow_style: Optional[bool] = None,
canonical: Optional[int] = None,
indent: Optional[int] = None,
width: Optional[int] = None,
allow_unicode: Optional[bool] = None,
line_break: Any = None,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Any = None,
tags: Any = None,
block_seq_indent: Any = None,
top_level_colon_align: Any = None,
prefix_colon: Any = None,
) -> None:
# NOQA
Emitter.__init__(
self,
stream,
canonical=canonical,
indent=indent,
width=width,
allow_unicode=allow_unicode,
line_break=line_break,
block_seq_indent=block_seq_indent,
top_level_colon_align=top_level_colon_align,
prefix_colon=prefix_colon,
dumper=self,
)
Serializer.__init__(
self,
encoding=encoding,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
dumper=self,
)
RoundTripRepresenter.__init__(
self,
default_style=default_style,
default_flow_style=default_flow_style,
dumper=self,
)
VersionedResolver.__init__(self, loader=self)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
from __future__ import annotations
import warnings
# import textwrap
if False: # MYPY
from typing import Any, Dict, Optional, List, Text # NOQA
__all__ = [
'FileMark',
'StringMark',
'CommentMark',
'YAMLError',
'MarkedYAMLError',
'ReusedAnchorWarning',
'UnsafeLoaderWarning',
'MarkedYAMLWarning',
'MarkedYAMLFutureWarning',
]
class StreamMark:
__slots__ = 'name', 'index', 'line', 'column'
def __init__(self, name: Any, index: int, line: int, column: int) -> None:
self.name = name
self.index = index
self.line = line
self.column = column
def __str__(self) -> Any:
where = f' in "{self.name!s}", line {self.line + 1:d}, column {self.column + 1:d}'
return where
def __eq__(self, other: Any) -> bool:
if self.line != other.line or self.column != other.column:
return False
if self.name != other.name or self.index != other.index:
return False
return True
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
class FileMark(StreamMark):
__slots__ = ()
class StringMark(StreamMark):
__slots__ = 'name', 'index', 'line', 'column', 'buffer', 'pointer'
def __init__(
self, name: Any, index: int, line: int, column: int, buffer: Any, pointer: Any,
) -> None:
StreamMark.__init__(self, name, index, line, column)
self.buffer = buffer
self.pointer = pointer
def get_snippet(self, indent: int = 4, max_length: int = 75) -> Any:
if self.buffer is None: # always False
return None
head = ""
start = self.pointer
while start > 0 and self.buffer[start - 1] not in '\0\r\n\x85\u2028\u2029':
start -= 1
if self.pointer - start > max_length / 2 - 1:
head = ' ... '
start += 5
break
tail = ""
end = self.pointer
while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029':
end += 1
if end - self.pointer > max_length / 2 - 1:
tail = ' ... '
end -= 5
break
snippet = self.buffer[start:end]
caret = '^'
caret = f'^ (line: {self.line + 1})'
return (
' ' * indent
+ head
+ snippet
+ tail
+ '\n'
+ ' ' * (indent + self.pointer - start + len(head))
+ caret
)
def __str__(self) -> Any:
snippet = self.get_snippet()
where = f' in "{self.name!s}", line {self.line + 1:d}, column {self.column + 1:d}'
if snippet is not None:
where += ':\n' + snippet
return where
def __repr__(self) -> Any:
snippet = self.get_snippet()
where = f' in "{self.name!s}", line {self.line + 1:d}, column {self.column + 1:d}'
if snippet is not None:
where += ':\n' + snippet
return where
class CommentMark:
__slots__ = ('column',)
def __init__(self, column: Any) -> None:
self.column = column
class YAMLError(Exception):
pass
class MarkedYAMLError(YAMLError):
def __init__(
self,
context: Any = None,
context_mark: Any = None,
problem: Any = None,
problem_mark: Any = None,
note: Any = None,
warn: Any = None,
) -> None:
self.context = context
self.context_mark = context_mark
self.problem = problem
self.problem_mark = problem_mark
self.note = note
# warn is ignored
def __str__(self) -> Any:
lines: list[str] = []
if self.context is not None:
lines.append(self.context)
if self.context_mark is not None and (
self.problem is None
or self.problem_mark is None
or self.context_mark.name != self.problem_mark.name
or self.context_mark.line != self.problem_mark.line
or self.context_mark.column != self.problem_mark.column
):
lines.append(str(self.context_mark))
if self.problem is not None:
lines.append(self.problem)
if self.problem_mark is not None:
lines.append(str(self.problem_mark))
# if self.note is not None and self.note:
# note = textwrap.dedent(self.note)
# lines.append(note)
self.check_append(lines, self.note)
return '\n'.join(lines)
def check_append(self, lines: list[str], val: Optional[str]) -> None:
if val is None or not val:
return
import textwrap
note = textwrap.dedent(val)
lines.append(note)
class YAMLStreamError(Exception):
pass
class YAMLWarning(Warning):
pass
class MarkedYAMLWarning(YAMLWarning):
def __init__(
self,
context: Any = None,
context_mark: Any = None,
problem: Any = None,
problem_mark: Any = None,
note: Any = None,
warn: Any = None,
) -> None:
self.context = context
self.context_mark = context_mark
self.problem = problem
self.problem_mark = problem_mark
self.note = note
self.warn = warn
def __str__(self) -> Any:
lines: List[str] = []
if self.context is not None:
lines.append(self.context)
if self.context_mark is not None and (
self.problem is None
or self.problem_mark is None
or self.context_mark.name != self.problem_mark.name
or self.context_mark.line != self.problem_mark.line
or self.context_mark.column != self.problem_mark.column
):
lines.append(str(self.context_mark))
if self.problem is not None:
lines.append(self.problem)
if self.problem_mark is not None:
lines.append(str(self.problem_mark))
# if self.note is not None and self.note:
# note = textwrap.dedent(self.note)
# lines.append(note)
self.check_append(lines, self.note)
# if self.warn is not None and self.warn:
# warn = textwrap.dedent(self.warn)
# lines.append(warn)
self.check_append(lines, self.warn)
return '\n'.join(lines)
def check_append(self, lines: list[str], val: Optional[str]) -> None:
if val is None or not val:
return
import textwrap
note = textwrap.dedent(val)
lines.append(note)
class ReusedAnchorWarning(YAMLWarning):
pass
class UnsafeLoaderWarning(YAMLWarning):
text = """
The default 'Loader' for 'load(stream)' without further arguments can be unsafe.
Use 'load(stream, Loader=ruamel.yaml.Loader)' explicitly if that is OK.
Alternatively include the following in your code:
import warnings
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
In most other cases you should consider using 'safe_load(stream)'"""
pass
warnings.simplefilter('once', UnsafeLoaderWarning)
class MantissaNoDotYAML1_1Warning(YAMLWarning):
def __init__(self, node: Any, flt_str: Any) -> None:
self.node = node
self.flt = flt_str
def __str__(self) -> Any:
line = self.node.start_mark.line
col = self.node.start_mark.column
return f"""
In YAML 1.1 floating point values should have a dot ('.') in their mantissa.
See the Floating-Point Language-Independent Type for YAML™ Version 1.1 specification
( http://yaml.org/type/float.html ). This dot is not required for JSON nor for YAML 1.2
Correct your float: "{self.flt}" on line: {line}, column: {col}
or alternatively include the following in your code:
import warnings
warnings.simplefilter('ignore', ruamel.yaml.error.MantissaNoDotYAML1_1Warning)
"""
warnings.simplefilter('once', MantissaNoDotYAML1_1Warning)
class YAMLFutureWarning(Warning):
pass
class MarkedYAMLFutureWarning(YAMLFutureWarning):
def __init__(
self,
context: Any = None,
context_mark: Any = None,
problem: Any = None,
problem_mark: Any = None,
note: Any = None,
warn: Any = None,
) -> None:
self.context = context
self.context_mark = context_mark
self.problem = problem
self.problem_mark = problem_mark
self.note = note
self.warn = warn
def __str__(self) -> Any:
lines: List[str] = []
if self.context is not None:
lines.append(self.context)
if self.context_mark is not None and (
self.problem is None
or self.problem_mark is None
or self.context_mark.name != self.problem_mark.name
or self.context_mark.line != self.problem_mark.line
or self.context_mark.column != self.problem_mark.column
):
lines.append(str(self.context_mark))
if self.problem is not None:
lines.append(self.problem)
if self.problem_mark is not None:
lines.append(str(self.problem_mark))
# if self.note is not None and self.note:
# note = textwrap.dedent(self.note)
# lines.append(note)
self.check_append(lines, self.note)
# if self.warn is not None and self.warn:
# warn = textwrap.dedent(self.warn)
# lines.append(warn)
self.check_append(lines, self.warn)
return '\n'.join(lines)
def check_append(self, lines: list[str], val: Optional[str]) -> None:
if val is None or not val:
return
import textwrap
note = textwrap.dedent(val)
lines.append(note)

View File

@@ -0,0 +1,266 @@
from __future__ import annotations
# Abstract classes.
if False: # MYPY
from typing import Any, Dict, Optional, List # NOQA
from ruamel.yaml.tag import Tag
SHOW_LINES = False
def CommentCheck() -> None:
pass
class Event:
__slots__ = 'start_mark', 'end_mark', 'comment'
crepr = 'Unspecified Event'
def __init__(
self, start_mark: Any = None, end_mark: Any = None, comment: Any = CommentCheck,
) -> None:
self.start_mark = start_mark
self.end_mark = end_mark
# assert comment is not CommentCheck
if comment is CommentCheck:
comment = None
self.comment = comment
def __repr__(self) -> Any:
if True:
arguments = []
if hasattr(self, 'value'):
# if you use repr(getattr(self, 'value')) then flake8 complains about
# abuse of getattr with a constant. When you change to self.value
# then mypy throws an error
arguments.append(repr(self.value))
for key in ['anchor', 'tag', 'implicit', 'flow_style', 'style']:
v = getattr(self, key, None)
if v is not None:
arguments.append(f'{key!s}={v!r}')
if self.comment not in [None, CommentCheck]:
arguments.append(f'comment={self.comment!r}')
if SHOW_LINES:
arguments.append(
f'({self.start_mark.line}:{self.start_mark.column}/'
f'{self.end_mark.line}:{self.end_mark.column})',
)
arguments = ', '.join(arguments) # type: ignore
else:
attributes = [
key
for key in ['anchor', 'tag', 'implicit', 'value', 'flow_style', 'style']
if hasattr(self, key)
]
arguments = ', '.join([f'{key!s}={getattr(self, key)!r}' for key in attributes])
if self.comment not in [None, CommentCheck]:
arguments += f', comment={self.comment!r}'
return f'{self.__class__.__name__!s}({arguments!s})'
def compact_repr(self) -> str:
return f'{self.crepr}'
class NodeEvent(Event):
__slots__ = ('anchor',)
def __init__(
self, anchor: Any, start_mark: Any = None, end_mark: Any = None, comment: Any = None,
) -> None:
Event.__init__(self, start_mark, end_mark, comment)
self.anchor = anchor
class CollectionStartEvent(NodeEvent):
__slots__ = 'ctag', 'implicit', 'flow_style', 'nr_items'
def __init__(
self,
anchor: Any,
tag: Any,
implicit: Any,
start_mark: Any = None,
end_mark: Any = None,
flow_style: Any = None,
comment: Any = None,
nr_items: Optional[int] = None,
) -> None:
NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
self.ctag = tag
self.implicit = implicit
self.flow_style = flow_style
self.nr_items = nr_items
@property
def tag(self) -> Optional[str]:
return None if self.ctag is None else str(self.ctag)
class CollectionEndEvent(Event):
__slots__ = ()
# Implementations.
class StreamStartEvent(Event):
__slots__ = ('encoding',)
crepr = '+STR'
def __init__(
self,
start_mark: Any = None,
end_mark: Any = None,
encoding: Any = None,
comment: Any = None,
) -> None:
Event.__init__(self, start_mark, end_mark, comment)
self.encoding = encoding
class StreamEndEvent(Event):
__slots__ = ()
crepr = '-STR'
class DocumentStartEvent(Event):
__slots__ = 'explicit', 'version', 'tags'
crepr = '+DOC'
def __init__(
self,
start_mark: Any = None,
end_mark: Any = None,
explicit: Any = None,
version: Any = None,
tags: Any = None,
comment: Any = None,
) -> None:
Event.__init__(self, start_mark, end_mark, comment)
self.explicit = explicit
self.version = version
self.tags = tags
def compact_repr(self) -> str:
start = ' ---' if self.explicit else ''
return f'{self.crepr}{start}'
class DocumentEndEvent(Event):
__slots__ = ('explicit',)
crepr = '-DOC'
def __init__(
self,
start_mark: Any = None,
end_mark: Any = None,
explicit: Any = None,
comment: Any = None,
) -> None:
Event.__init__(self, start_mark, end_mark, comment)
self.explicit = explicit
def compact_repr(self) -> str:
end = ' ...' if self.explicit else ''
return f'{self.crepr}{end}'
class AliasEvent(NodeEvent):
__slots__ = 'style'
crepr = '=ALI'
def __init__(
self,
anchor: Any,
start_mark: Any = None,
end_mark: Any = None,
style: Any = None,
comment: Any = None,
) -> None:
NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
self.style = style
def compact_repr(self) -> str:
return f'{self.crepr} *{self.anchor}'
class ScalarEvent(NodeEvent):
__slots__ = 'ctag', 'implicit', 'value', 'style'
crepr = '=VAL'
def __init__(
self,
anchor: Any,
tag: Any,
implicit: Any,
value: Any,
start_mark: Any = None,
end_mark: Any = None,
style: Any = None,
comment: Any = None,
) -> None:
NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
self.ctag = tag
self.implicit = implicit
self.value = value
self.style = style
@property
def tag(self) -> Optional[str]:
return None if self.ctag is None else str(self.ctag)
@tag.setter
def tag(self, val: Any) -> None:
if isinstance(val, str):
val = Tag(suffix=val)
self.ctag = val
def compact_repr(self) -> str:
style = ':' if self.style is None else self.style
anchor = f'&{self.anchor} ' if self.anchor else ''
tag = f'<{self.tag!s}> ' if self.tag else ''
value = self.value
for ch, rep in [
('\\', '\\\\'),
('\t', '\\t'),
('\n', '\\n'),
('\a', ''), # remove from folded
('\r', '\\r'),
('\b', '\\b'),
]:
value = value.replace(ch, rep)
return f'{self.crepr} {anchor}{tag}{style}{value}'
class SequenceStartEvent(CollectionStartEvent):
__slots__ = ()
crepr = '+SEQ'
def compact_repr(self) -> str:
flow = ' []' if self.flow_style else ''
anchor = f' &{self.anchor}' if self.anchor else ''
tag = f' <{self.tag!s}>' if self.tag else ''
return f'{self.crepr}{flow}{anchor}{tag}'
class SequenceEndEvent(CollectionEndEvent):
__slots__ = ()
crepr = '-SEQ'
class MappingStartEvent(CollectionStartEvent):
__slots__ = ()
crepr = '+MAP'
def compact_repr(self) -> str:
flow = ' {}' if self.flow_style else ''
anchor = f' &{self.anchor}' if self.anchor else ''
tag = f' <{self.tag!s}>' if self.tag else ''
return f'{self.crepr}{flow}{anchor}{tag}'
class MappingEndEvent(CollectionEndEvent):
__slots__ = ()
crepr = '-MAP'

View File

@@ -0,0 +1,92 @@
from __future__ import annotations
from ruamel.yaml.reader import Reader
from ruamel.yaml.scanner import Scanner, RoundTripScanner
from ruamel.yaml.parser import Parser, RoundTripParser
from ruamel.yaml.composer import Composer
from ruamel.yaml.constructor import (
BaseConstructor,
SafeConstructor,
Constructor,
RoundTripConstructor,
)
from ruamel.yaml.resolver import VersionedResolver
if False: # MYPY
from typing import Any, Dict, List, Union, Optional # NOQA
from ruamel.yaml.compat import StreamTextType, VersionType # NOQA
__all__ = ['BaseLoader', 'SafeLoader', 'Loader', 'RoundTripLoader']
class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, VersionedResolver):
def __init__(
self,
stream: StreamTextType,
version: Optional[VersionType] = None,
preserve_quotes: Optional[bool] = None,
) -> None:
self.comment_handling = None
Reader.__init__(self, stream, loader=self)
Scanner.__init__(self, loader=self)
Parser.__init__(self, loader=self)
Composer.__init__(self, loader=self)
BaseConstructor.__init__(self, loader=self)
VersionedResolver.__init__(self, version, loader=self)
class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, VersionedResolver):
def __init__(
self,
stream: StreamTextType,
version: Optional[VersionType] = None,
preserve_quotes: Optional[bool] = None,
) -> None:
self.comment_handling = None
Reader.__init__(self, stream, loader=self)
Scanner.__init__(self, loader=self)
Parser.__init__(self, loader=self)
Composer.__init__(self, loader=self)
SafeConstructor.__init__(self, loader=self)
VersionedResolver.__init__(self, version, loader=self)
class Loader(Reader, Scanner, Parser, Composer, Constructor, VersionedResolver):
def __init__(
self,
stream: StreamTextType,
version: Optional[VersionType] = None,
preserve_quotes: Optional[bool] = None,
) -> None:
self.comment_handling = None
Reader.__init__(self, stream, loader=self)
Scanner.__init__(self, loader=self)
Parser.__init__(self, loader=self)
Composer.__init__(self, loader=self)
Constructor.__init__(self, loader=self)
VersionedResolver.__init__(self, version, loader=self)
class RoundTripLoader(
Reader,
RoundTripScanner,
RoundTripParser,
Composer,
RoundTripConstructor,
VersionedResolver,
):
def __init__(
self,
stream: StreamTextType,
version: Optional[VersionType] = None,
preserve_quotes: Optional[bool] = None,
) -> None:
# self.reader = Reader.__init__(self, stream)
self.comment_handling = None # issue 385
Reader.__init__(self, stream, loader=self)
RoundTripScanner.__init__(self, loader=self)
RoundTripParser.__init__(self, loader=self)
Composer.__init__(self, loader=self)
RoundTripConstructor.__init__(self, preserve_quotes=preserve_quotes, loader=self)
VersionedResolver.__init__(self, version, loader=self)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
from __future__ import annotations
if False: # MYPY
from typing import Any, Dict, List, Union, Optional, Iterator # NOQA
merge_attrib = '_yaml_merge'
class MergeValue:
attrib = merge_attrib
def __init__(self) -> None:
self.value: List[Any] = []
self.sequence = None
self.merge_pos: Optional[int] = None # position of merge in the mapping
def __getitem__(self, index: Any) -> Any:
return self.value[index]
def __setitem__(self, index: Any, val: Any) -> None:
self.value[index] = val
def __repr__(self) -> Any:
return f'MergeValue({self.value!r})'
def __len__(self) -> Any:
return len(self.value)
def append(self, elem: Any) -> Any:
self.value.append(elem)
def extend(self, elements: Any) -> None:
self.value.extend(elements)
def set_sequence(self, seq: Any) -> None:
# print('mergevalue.set_sequence node', node.anchor)
self.sequence = seq

View File

@@ -0,0 +1,149 @@
from __future__ import annotations
import sys
if False: # MYPY
from typing import Dict, Any, Text, Optional # NOQA
from ruamel.yaml.tag import Tag
class Node:
__slots__ = 'ctag', 'value', 'start_mark', 'end_mark', 'comment', 'anchor'
def __init__(
self,
tag: Any,
value: Any,
start_mark: Any,
end_mark: Any,
comment: Any = None,
anchor: Any = None,
) -> None:
# you can still get a string from the serializer
self.ctag = tag if isinstance(tag, Tag) else Tag(suffix=tag)
self.value = value
self.start_mark = start_mark
self.end_mark = end_mark
self.comment = comment
self.anchor = anchor
@property
def tag(self) -> Optional[str]:
return None if self.ctag is None else str(self.ctag)
@tag.setter
def tag(self, val: Any) -> None:
if isinstance(val, str):
val = Tag(suffix=val)
self.ctag = val
def __repr__(self) -> Any:
value = self.value
# if isinstance(value, list):
# if len(value) == 0:
# value = '<empty>'
# elif len(value) == 1:
# value = '<1 item>'
# else:
# value = f'<{len(value)} items>'
# else:
# if len(value) > 75:
# value = repr(value[:70]+' ... ')
# else:
# value = repr(value)
value = repr(value)
if self.anchor is not None:
return f'{self.__class__.__name__!s}(tag={self.tag!r}, anchor={self.anchor!r}, value={value!s})' # NOQA
return f'{self.__class__.__name__!s}(tag={self.tag!r}, value={value!s})'
def dump(self, indent: int = 0) -> None:
xx = self.__class__.__name__
xi = ' ' * indent
if isinstance(self.value, str):
sys.stdout.write(f'{xi}{xx}(tag={self.tag!r}, value={self.value!r})\n')
if self.comment:
sys.stdout.write(f' {xi}comment: {self.comment})\n')
return
sys.stdout.write(f'{xi}{xx}(tag={self.tag!r})\n')
if self.comment:
sys.stdout.write(f' {xi}comment: {self.comment})\n')
for v in self.value:
if isinstance(v, tuple):
for v1 in v:
v1.dump(indent + 1)
elif isinstance(v, Node):
v.dump(indent + 1)
else:
sys.stdout.write(f'Node value type? {type(v)}\n')
class ScalarNode(Node):
"""
styles:
? -> set() ? key, no value
- -> suppressable null value in set
" -> double quoted
' -> single quoted
| -> literal style
> -> folding style
"""
__slots__ = ('style',)
id = 'scalar'
def __init__(
self,
tag: Any,
value: Any,
start_mark: Any = None,
end_mark: Any = None,
style: Any = None,
comment: Any = None,
anchor: Any = None,
) -> None:
Node.__init__(self, tag, value, start_mark, end_mark, comment=comment, anchor=anchor)
self.style = style
class CollectionNode(Node):
__slots__ = ('flow_style',)
def __init__(
self,
tag: Any,
value: Any,
start_mark: Any = None,
end_mark: Any = None,
flow_style: Any = None,
comment: Any = None,
anchor: Any = None,
) -> None:
Node.__init__(self, tag, value, start_mark, end_mark, comment=comment)
self.flow_style = flow_style
self.anchor = anchor
class SequenceNode(CollectionNode):
__slots__ = ()
id = 'sequence'
class MappingNode(CollectionNode):
__slots__ = ('merge',)
id = 'mapping'
def __init__(
self,
tag: Any,
value: Any,
start_mark: Any = None,
end_mark: Any = None,
flow_style: Any = None,
comment: Any = None,
anchor: Any = None,
) -> None:
CollectionNode.__init__(
self, tag, value, start_mark, end_mark, flow_style, comment, anchor,
)
self.merge = None

View File

@@ -0,0 +1,862 @@
from __future__ import annotations
# The following YAML grammar is LL(1) and is parsed by a recursive descent
# parser.
#
# stream ::= STREAM-START implicit_document? explicit_document*
# STREAM-END
# implicit_document ::= block_node DOCUMENT-END*
# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
# block_node_or_indentless_sequence ::=
# ALIAS
# | properties (block_content |
# indentless_block_sequence)?
# | block_content
# | indentless_block_sequence
# block_node ::= ALIAS
# | properties block_content?
# | block_content
# flow_node ::= ALIAS
# | properties flow_content?
# | flow_content
# properties ::= TAG ANCHOR? | ANCHOR TAG?
# block_content ::= block_collection | flow_collection | SCALAR
# flow_content ::= flow_collection | SCALAR
# block_collection ::= block_sequence | block_mapping
# flow_collection ::= flow_sequence | flow_mapping
# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)*
# BLOCK-END
# indentless_sequence ::= (BLOCK-ENTRY block_node?)+
# block_mapping ::= BLOCK-MAPPING_START
# ((KEY block_node_or_indentless_sequence?)?
# (VALUE block_node_or_indentless_sequence?)?)*
# BLOCK-END
# flow_sequence ::= FLOW-SEQUENCE-START
# (flow_sequence_entry FLOW-ENTRY)*
# flow_sequence_entry?
# FLOW-SEQUENCE-END
# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
# flow_mapping ::= FLOW-MAPPING-START
# (flow_mapping_entry FLOW-ENTRY)*
# flow_mapping_entry?
# FLOW-MAPPING-END
# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
#
# FIRST sets:
#
# stream: { STREAM-START <}
# explicit_document: { DIRECTIVE DOCUMENT-START }
# implicit_document: FIRST(block_node)
# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START
# BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START }
# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START }
# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START
# FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR }
# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START }
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
# block_sequence: { BLOCK-SEQUENCE-START }
# block_mapping: { BLOCK-MAPPING-START }
# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR
# BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START
# FLOW-MAPPING-START BLOCK-ENTRY }
# indentless_sequence: { ENTRY }
# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START }
# flow_sequence: { FLOW-SEQUENCE-START }
# flow_mapping: { FLOW-MAPPING-START }
# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START
# FLOW-MAPPING-START KEY }
# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START
# FLOW-MAPPING-START KEY }
# need to have full path with import, as pkg_resources tries to load parser.py in __init__.py
# only to not do anything with the package afterwards
# and for Jython too
from ruamel.yaml.error import MarkedYAMLError
from ruamel.yaml.tokens import * # NOQA
from ruamel.yaml.events import * # NOQA
from ruamel.yaml.scanner import Scanner, RoundTripScanner, ScannerError # NOQA
from ruamel.yaml.scanner import BlankLineComment
from ruamel.yaml.comments import C_PRE, C_POST, C_SPLIT_ON_FIRST_BLANK
from ruamel.yaml.compat import nprint, nprintf # NOQA
from ruamel.yaml.tag import Tag
if False: # MYPY
from typing import Any, Dict, Optional, List, Optional # NOQA
__all__ = ['Parser', 'RoundTripParser', 'ParserError']
def xprintf(*args: Any, **kw: Any) -> Any:
return nprintf(*args, **kw)
pass
class ParserError(MarkedYAMLError):
pass
class Parser:
# Since writing a recursive-descendant parser is a straightforward task, we
# do not give many comments here.
DEFAULT_TAGS = {'!': '!', '!!': 'tag:yaml.org,2002:'}
def __init__(self, loader: Any) -> None:
self.loader = loader
if self.loader is not None and getattr(self.loader, '_parser', None) is None:
self.loader._parser = self
self.reset_parser()
def reset_parser(self) -> None:
# Reset the state attributes (to clear self-references)
self.current_event = self.last_event = None
self.tag_handles: Dict[Any, Any] = {}
self.states: List[Any] = []
self.marks: List[Any] = []
self.state: Any = self.parse_stream_start
def dispose(self) -> None:
self.reset_parser()
@property
def scanner(self) -> Any:
if hasattr(self.loader, 'typ'):
return self.loader.scanner
return self.loader._scanner
@property
def resolver(self) -> Any:
if hasattr(self.loader, 'typ'):
return self.loader.resolver
return self.loader._resolver
def check_event(self, *choices: Any) -> bool:
# Check the type of the next event.
if self.current_event is None:
if self.state:
self.current_event = self.state()
if self.current_event is not None:
if not choices:
return True
for choice in choices:
if isinstance(self.current_event, choice):
return True
return False
def peek_event(self) -> Any:
# Get the next event.
if self.current_event is None:
if self.state:
self.current_event = self.state()
return self.current_event
def get_event(self) -> Any:
# Get the next event and proceed further.
if self.current_event is None:
if self.state:
self.current_event = self.state()
# assert self.current_event is not None
# if self.current_event.end_mark.line != self.peek_event().start_mark.line:
# xprintf('get_event', repr(self.current_event), self.peek_event().start_mark.line)
self.last_event = value = self.current_event
self.current_event = None
return value
# stream ::= STREAM-START implicit_document? explicit_document*
# STREAM-END
# implicit_document ::= block_node DOCUMENT-END*
# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
def parse_stream_start(self) -> Any:
# Parse the stream start.
token = self.scanner.get_token()
self.move_token_comment(token)
event = StreamStartEvent(token.start_mark, token.end_mark, encoding=token.encoding)
# Prepare the next state.
self.state = self.parse_implicit_document_start
return event
def parse_implicit_document_start(self) -> Any:
# Parse an implicit document.
if not self.scanner.check_token(DirectiveToken, DocumentStartToken, StreamEndToken):
# don't need copy, as an implicit tag doesn't add tag_handles
self.tag_handles = self.DEFAULT_TAGS
token = self.scanner.peek_token()
start_mark = end_mark = token.start_mark
event = DocumentStartEvent(start_mark, end_mark, explicit=False)
# Prepare the next state.
self.states.append(self.parse_document_end)
self.state = self.parse_block_node
return event
else:
return self.parse_document_start()
def parse_document_start(self) -> Any:
# Parse any extra document end indicators.
while self.scanner.check_token(DocumentEndToken):
self.scanner.get_token()
# Parse an explicit document.
if not self.scanner.check_token(StreamEndToken):
version, tags = self.process_directives()
if not self.scanner.check_token(DocumentStartToken):
raise ParserError(
None,
None,
"expected '<document start>', "
f'but found {self.scanner.peek_token().id,!r}',
self.scanner.peek_token().start_mark,
)
token = self.scanner.get_token()
start_mark = token.start_mark
end_mark = token.end_mark
# if self.loader is not None and \
# end_mark.line != self.scanner.peek_token().start_mark.line:
# self.loader.scalar_after_indicator = False
event: Any = DocumentStartEvent(
start_mark,
end_mark,
explicit=True,
version=version,
tags=tags,
comment=token.comment,
)
self.states.append(self.parse_document_end)
self.state = self.parse_document_content
else:
# Parse the end of the stream.
token = self.scanner.get_token()
event = StreamEndEvent(token.start_mark, token.end_mark, comment=token.comment)
assert not self.states
assert not self.marks
self.state = None
return event
def parse_document_end(self) -> Any:
# Parse the document end.
token = self.scanner.peek_token()
start_mark = end_mark = token.start_mark
explicit = False
if self.scanner.check_token(DocumentEndToken):
token = self.scanner.get_token()
# if token.end_mark.line != self.peek_event().start_mark.line:
pt = self.scanner.peek_token()
if not isinstance(pt, StreamEndToken) and (
token.end_mark.line == pt.start_mark.line
):
raise ParserError(
None,
None,
'found non-comment content after document end marker, '
f'{self.scanner.peek_token().id,!r}',
self.scanner.peek_token().start_mark,
)
end_mark = token.end_mark
explicit = True
event = DocumentEndEvent(start_mark, end_mark, explicit=explicit)
# Prepare the next state.
if self.resolver.processing_version == (1, 1):
self.state = self.parse_document_start
else:
if explicit:
# found a document end marker, can be followed by implicit document
self.state = self.parse_implicit_document_start
else:
self.state = self.parse_document_start
return event
def parse_document_content(self) -> Any:
if self.scanner.check_token(
DirectiveToken, DocumentStartToken, DocumentEndToken, StreamEndToken,
):
event = self.process_empty_scalar(self.scanner.peek_token().start_mark)
self.state = self.states.pop()
return event
else:
return self.parse_block_node()
def process_directives(self) -> Any:
yaml_version = None
self.tag_handles = {}
while self.scanner.check_token(DirectiveToken):
token = self.scanner.get_token()
if token.name == 'YAML':
if yaml_version is not None:
raise ParserError(
None, None, 'found duplicate YAML directive', token.start_mark,
)
major, minor = token.value
if major != 1:
raise ParserError(
None,
None,
'found incompatible YAML document (version 1.* is required)',
token.start_mark,
)
yaml_version = token.value
elif token.name == 'TAG':
handle, prefix = token.value
if handle in self.tag_handles:
raise ParserError(
None, None, f'duplicate tag handle {handle!r}', token.start_mark,
)
self.tag_handles[handle] = prefix
if bool(self.tag_handles):
value: Any = (yaml_version, self.tag_handles.copy())
else:
value = yaml_version, None
if self.loader is not None and hasattr(self.loader, 'tags'):
# ToDo: this is used to keep a single loaded file from losing its version
# info, but it affects following versions that have no explicit directive
self.loader.version = yaml_version
if self.loader.tags is None:
self.loader.tags = {}
for k in self.tag_handles:
self.loader.tags[k] = self.tag_handles[k]
self.loader.doc_infos[-1].tags.append((k, self.tag_handles[k]))
for key in self.DEFAULT_TAGS:
if key not in self.tag_handles:
self.tag_handles[key] = self.DEFAULT_TAGS[key]
return value
# block_node_or_indentless_sequence ::= ALIAS
# | properties (block_content | indentless_block_sequence)?
# | block_content
# | indentless_block_sequence
# block_node ::= ALIAS
# | properties block_content?
# | block_content
# flow_node ::= ALIAS
# | properties flow_content?
# | flow_content
# properties ::= TAG ANCHOR? | ANCHOR TAG?
# block_content ::= block_collection | flow_collection | SCALAR
# flow_content ::= flow_collection | SCALAR
# block_collection ::= block_sequence | block_mapping
# flow_collection ::= flow_sequence | flow_mapping
def parse_block_node(self) -> Any:
return self.parse_node(block=True)
def parse_flow_node(self) -> Any:
return self.parse_node()
def parse_block_node_or_indentless_sequence(self) -> Any:
return self.parse_node(block=True, indentless_sequence=True)
# def transform_tag(self, handle: Any, suffix: Any) -> Any:
# return self.tag_handles[handle] + suffix
def select_tag_transform(self, tag: Tag) -> None:
if tag is None:
return
tag.select_transform(False)
def parse_node(self, block: bool = False, indentless_sequence: bool = False) -> Any:
if self.scanner.check_token(AliasToken):
token = self.scanner.get_token()
event: Any = AliasEvent(token.value, token.start_mark, token.end_mark)
self.state = self.states.pop()
return event
anchor = None
tag = None
start_mark = end_mark = tag_mark = None
if self.scanner.check_token(AnchorToken):
token = self.scanner.get_token()
self.move_token_comment(token)
start_mark = token.start_mark
end_mark = token.end_mark
anchor = token.value
if self.scanner.check_token(TagToken):
token = self.scanner.get_token()
tag_mark = token.start_mark
end_mark = token.end_mark
# tag = token.value
tag = Tag(
handle=token.value[0], suffix=token.value[1], handles=self.tag_handles,
)
elif self.scanner.check_token(TagToken):
token = self.scanner.get_token()
try:
self.move_token_comment(token)
except NotImplementedError:
pass
start_mark = tag_mark = token.start_mark
end_mark = token.end_mark
# tag = token.value
tag = Tag(handle=token.value[0], suffix=token.value[1], handles=self.tag_handles)
if self.scanner.check_token(AnchorToken):
token = self.scanner.get_token()
start_mark = tag_mark = token.start_mark
end_mark = token.end_mark
anchor = token.value
if tag is not None:
self.select_tag_transform(tag)
if tag.check_handle():
raise ParserError(
'while parsing a node',
start_mark,
f'found undefined tag handle {tag.handle!r}',
tag_mark,
)
if start_mark is None:
start_mark = end_mark = self.scanner.peek_token().start_mark
event = None
implicit = tag is None or str(tag) == '!'
if indentless_sequence and self.scanner.check_token(BlockEntryToken):
comment = None
pt = self.scanner.peek_token()
if self.loader and self.loader.comment_handling is None:
if pt.comment and pt.comment[0]:
comment = [pt.comment[0], []]
pt.comment[0] = None
elif pt.comment and pt.comment[0] is None and pt.comment[1]:
comment = [None, pt.comment[1]]
pt.comment[1] = None
elif self.loader:
if pt.comment:
comment = pt.comment
end_mark = self.scanner.peek_token().end_mark
event = SequenceStartEvent(
anchor, tag, implicit, start_mark, end_mark, flow_style=False, comment=comment,
)
self.state = self.parse_indentless_sequence_entry
return event
if self.scanner.check_token(ScalarToken):
token = self.scanner.get_token()
# self.scanner.peek_token_same_line_comment(token)
end_mark = token.end_mark
if (token.plain and tag is None) or str(tag) == '!':
dimplicit = (True, False)
elif tag is None:
dimplicit = (False, True)
else:
dimplicit = (False, False)
event = ScalarEvent(
anchor,
tag,
dimplicit,
token.value,
start_mark,
end_mark,
style=token.style,
comment=token.comment,
)
self.state = self.states.pop()
elif self.scanner.check_token(FlowSequenceStartToken):
pt = self.scanner.peek_token()
end_mark = pt.end_mark
event = SequenceStartEvent(
anchor,
tag,
implicit,
start_mark,
end_mark,
flow_style=True,
comment=pt.comment,
)
self.state = self.parse_flow_sequence_first_entry
elif self.scanner.check_token(FlowMappingStartToken):
pt = self.scanner.peek_token()
end_mark = pt.end_mark
event = MappingStartEvent(
anchor,
tag,
implicit,
start_mark,
end_mark,
flow_style=True,
comment=pt.comment,
)
self.state = self.parse_flow_mapping_first_key
elif block and self.scanner.check_token(BlockSequenceStartToken):
end_mark = self.scanner.peek_token().start_mark
# should inserting the comment be dependent on the
# indentation?
pt = self.scanner.peek_token()
comment = pt.comment
# nprint('pt0', type(pt))
if comment is None or comment[1] is None:
comment = pt.split_old_comment()
# nprint('pt1', comment)
event = SequenceStartEvent(
anchor, tag, implicit, start_mark, end_mark, flow_style=False, comment=comment,
)
self.state = self.parse_block_sequence_first_entry
elif block and self.scanner.check_token(BlockMappingStartToken):
end_mark = self.scanner.peek_token().start_mark
comment = self.scanner.peek_token().comment
event = MappingStartEvent(
anchor, tag, implicit, start_mark, end_mark, flow_style=False, comment=comment,
)
self.state = self.parse_block_mapping_first_key
elif anchor is not None or tag is not None:
# Empty scalars are allowed even if a tag or an anchor is
# specified.
event = ScalarEvent(anchor, tag, (implicit, False), "", start_mark, end_mark)
self.state = self.states.pop()
else:
if block:
node = 'block'
else:
node = 'flow'
token = self.scanner.peek_token()
raise ParserError(
f'while parsing a {node!s} node',
start_mark,
f'expected the node content, but found {token.id!r}',
token.start_mark,
)
return event
# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)*
# BLOCK-END
def parse_block_sequence_first_entry(self) -> Any:
token = self.scanner.get_token()
# move any comment from start token
# self.move_token_comment(token)
self.marks.append(token.start_mark)
return self.parse_block_sequence_entry()
def parse_block_sequence_entry(self) -> Any:
if self.scanner.check_token(BlockEntryToken):
token = self.scanner.get_token()
self.move_token_comment(token)
if not self.scanner.check_token(BlockEntryToken, BlockEndToken):
self.states.append(self.parse_block_sequence_entry)
return self.parse_block_node()
else:
self.state = self.parse_block_sequence_entry
return self.process_empty_scalar(token.end_mark)
if not self.scanner.check_token(BlockEndToken):
token = self.scanner.peek_token()
raise ParserError(
'while parsing a block collection',
self.marks[-1],
f'expected <block end>, but found {token.id!r}',
token.start_mark,
)
token = self.scanner.get_token() # BlockEndToken
event = SequenceEndEvent(token.start_mark, token.end_mark, comment=token.comment)
self.state = self.states.pop()
self.marks.pop()
return event
# indentless_sequence ::= (BLOCK-ENTRY block_node?)+
# indentless_sequence?
# sequence:
# - entry
# - nested
def parse_indentless_sequence_entry(self) -> Any:
if self.scanner.check_token(BlockEntryToken):
token = self.scanner.get_token()
self.move_token_comment(token)
if not self.scanner.check_token(
BlockEntryToken, KeyToken, ValueToken, BlockEndToken,
):
self.states.append(self.parse_indentless_sequence_entry)
return self.parse_block_node()
else:
self.state = self.parse_indentless_sequence_entry
return self.process_empty_scalar(token.end_mark)
token = self.scanner.peek_token()
c = None
if self.loader and self.loader.comment_handling is None:
c = token.comment
start_mark = token.start_mark
else:
start_mark = self.last_event.end_mark # type: ignore
c = self.distribute_comment(token.comment, start_mark.line) # type: ignore
event = SequenceEndEvent(start_mark, start_mark, comment=c)
self.state = self.states.pop()
return event
# block_mapping ::= BLOCK-MAPPING_START
# ((KEY block_node_or_indentless_sequence?)?
# (VALUE block_node_or_indentless_sequence?)?)*
# BLOCK-END
def parse_block_mapping_first_key(self) -> Any:
token = self.scanner.get_token()
self.marks.append(token.start_mark)
return self.parse_block_mapping_key()
def parse_block_mapping_key(self) -> Any:
if self.scanner.check_token(KeyToken):
token = self.scanner.get_token()
self.move_token_comment(token)
if not self.scanner.check_token(KeyToken, ValueToken, BlockEndToken):
self.states.append(self.parse_block_mapping_value)
return self.parse_block_node_or_indentless_sequence()
else:
self.state = self.parse_block_mapping_value
return self.process_empty_scalar(token.end_mark)
if self.resolver.processing_version > (1, 1) and self.scanner.check_token(ValueToken):
self.state = self.parse_block_mapping_value
return self.process_empty_scalar(self.scanner.peek_token().start_mark)
if not self.scanner.check_token(BlockEndToken):
token = self.scanner.peek_token()
raise ParserError(
'while parsing a block mapping',
self.marks[-1],
f'expected <block end>, but found {token.id!r}',
token.start_mark,
)
token = self.scanner.get_token()
self.move_token_comment(token)
event = MappingEndEvent(token.start_mark, token.end_mark, comment=token.comment)
self.state = self.states.pop()
self.marks.pop()
return event
def parse_block_mapping_value(self) -> Any:
if self.scanner.check_token(ValueToken):
token = self.scanner.get_token()
# value token might have post comment move it to e.g. block
if self.scanner.check_token(ValueToken):
self.move_token_comment(token)
else:
if not self.scanner.check_token(KeyToken):
self.move_token_comment(token, empty=True)
# else: empty value for this key cannot move token.comment
if not self.scanner.check_token(KeyToken, ValueToken, BlockEndToken):
self.states.append(self.parse_block_mapping_key)
return self.parse_block_node_or_indentless_sequence()
else:
self.state = self.parse_block_mapping_key
comment = token.comment
if comment is None:
token = self.scanner.peek_token()
comment = token.comment
if comment:
token._comment = [None, comment[1]]
comment = [comment[0], None]
return self.process_empty_scalar(token.end_mark, comment=comment)
else:
self.state = self.parse_block_mapping_key
token = self.scanner.peek_token()
return self.process_empty_scalar(token.start_mark)
# flow_sequence ::= FLOW-SEQUENCE-START
# (flow_sequence_entry FLOW-ENTRY)*
# flow_sequence_entry?
# FLOW-SEQUENCE-END
# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
#
# Note that while production rules for both flow_sequence_entry and
# flow_mapping_entry are equal, their interpretations are different.
# For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?`
# generate an inline mapping (set syntax).
def parse_flow_sequence_first_entry(self) -> Any:
token = self.scanner.get_token()
self.marks.append(token.start_mark)
return self.parse_flow_sequence_entry(first=True)
def parse_flow_sequence_entry(self, first: bool = False) -> Any:
if not self.scanner.check_token(FlowSequenceEndToken):
if not first:
if self.scanner.check_token(FlowEntryToken):
self.scanner.get_token()
else:
token = self.scanner.peek_token()
raise ParserError(
'while parsing a flow sequence',
self.marks[-1],
f"expected ',' or ']', but got {token.id!r}",
token.start_mark,
)
if self.scanner.check_token(KeyToken):
token = self.scanner.peek_token()
event: Any = MappingStartEvent(
None, None, True, token.start_mark, token.end_mark, flow_style=True,
)
self.state = self.parse_flow_sequence_entry_mapping_key
return event
elif not self.scanner.check_token(FlowSequenceEndToken):
self.states.append(self.parse_flow_sequence_entry)
return self.parse_flow_node()
token = self.scanner.get_token()
event = SequenceEndEvent(token.start_mark, token.end_mark, comment=token.comment)
self.state = self.states.pop()
self.marks.pop()
return event
def parse_flow_sequence_entry_mapping_key(self) -> Any:
token = self.scanner.get_token()
if not self.scanner.check_token(ValueToken, FlowEntryToken, FlowSequenceEndToken):
self.states.append(self.parse_flow_sequence_entry_mapping_value)
return self.parse_flow_node()
else:
self.state = self.parse_flow_sequence_entry_mapping_value
return self.process_empty_scalar(token.end_mark)
def parse_flow_sequence_entry_mapping_value(self) -> Any:
if self.scanner.check_token(ValueToken):
token = self.scanner.get_token()
if not self.scanner.check_token(FlowEntryToken, FlowSequenceEndToken):
self.states.append(self.parse_flow_sequence_entry_mapping_end)
return self.parse_flow_node()
else:
self.state = self.parse_flow_sequence_entry_mapping_end
return self.process_empty_scalar(token.end_mark)
else:
self.state = self.parse_flow_sequence_entry_mapping_end
token = self.scanner.peek_token()
return self.process_empty_scalar(token.start_mark)
def parse_flow_sequence_entry_mapping_end(self) -> Any:
self.state = self.parse_flow_sequence_entry
token = self.scanner.peek_token()
return MappingEndEvent(token.start_mark, token.start_mark)
# flow_mapping ::= FLOW-MAPPING-START
# (flow_mapping_entry FLOW-ENTRY)*
# flow_mapping_entry?
# FLOW-MAPPING-END
# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
def parse_flow_mapping_first_key(self) -> Any:
token = self.scanner.get_token()
self.marks.append(token.start_mark)
return self.parse_flow_mapping_key(first=True)
def parse_flow_mapping_key(self, first: Any = False) -> Any:
if not self.scanner.check_token(FlowMappingEndToken):
if not first:
if self.scanner.check_token(FlowEntryToken):
self.scanner.get_token()
else:
token = self.scanner.peek_token()
raise ParserError(
'while parsing a flow mapping',
self.marks[-1],
f"expected ',' or '}}', but got {token.id!r}",
token.start_mark,
)
if self.scanner.check_token(KeyToken):
token = self.scanner.get_token()
if not self.scanner.check_token(
ValueToken, FlowEntryToken, FlowMappingEndToken,
):
self.states.append(self.parse_flow_mapping_value)
return self.parse_flow_node()
else:
self.state = self.parse_flow_mapping_value
return self.process_empty_scalar(token.end_mark)
elif self.resolver.processing_version > (1, 1) and self.scanner.check_token(
ValueToken,
):
self.state = self.parse_flow_mapping_value
return self.process_empty_scalar(self.scanner.peek_token().end_mark)
elif not self.scanner.check_token(FlowMappingEndToken):
self.states.append(self.parse_flow_mapping_empty_value)
return self.parse_flow_node()
token = self.scanner.get_token()
event = MappingEndEvent(token.start_mark, token.end_mark, comment=token.comment)
self.state = self.states.pop()
self.marks.pop()
return event
def parse_flow_mapping_value(self) -> Any:
if self.scanner.check_token(ValueToken):
token = self.scanner.get_token()
if not self.scanner.check_token(FlowEntryToken, FlowMappingEndToken):
self.states.append(self.parse_flow_mapping_key)
return self.parse_flow_node()
else:
self.state = self.parse_flow_mapping_key
return self.process_empty_scalar(token.end_mark)
else:
self.state = self.parse_flow_mapping_key
token = self.scanner.peek_token()
return self.process_empty_scalar(token.start_mark)
def parse_flow_mapping_empty_value(self) -> Any:
self.state = self.parse_flow_mapping_key
return self.process_empty_scalar(self.scanner.peek_token().start_mark)
def process_empty_scalar(self, mark: Any, comment: Any = None) -> Any:
return ScalarEvent(None, None, (True, False), "", mark, mark, comment=comment)
def move_token_comment(
self, token: Any, nt: Optional[Any] = None, empty: Optional[bool] = False,
) -> Any:
pass
class RoundTripParser(Parser):
"""roundtrip is a safe loader, that wants to see the unmangled tag"""
def select_tag_transform(self, tag: Tag) -> None:
if tag is None:
return
tag.select_transform(True)
def move_token_comment(
self, token: Any, nt: Optional[Any] = None, empty: Optional[bool] = False,
) -> Any:
token.move_old_comment(self.scanner.peek_token() if nt is None else nt, empty=empty)
class RoundTripParserSC(RoundTripParser):
"""roundtrip is a safe loader, that wants to see the unmangled tag"""
# some of the differences are based on the superclass testing
# if self.loader.comment_handling is not None
def move_token_comment(
self: Any, token: Any, nt: Any = None, empty: Optional[bool] = False,
) -> None:
token.move_new_comment(self.scanner.peek_token() if nt is None else nt, empty=empty)
def distribute_comment(self, comment: Any, line: Any) -> Any:
# ToDo, look at indentation of the comment to determine attachment
if comment is None:
return None
if not comment[0]:
return None
# if comment[0][0] != line + 1:
# nprintf('>>>dcxxx', comment, line)
assert comment[0][0] == line + 1
# if comment[0] - line > 1:
# return
typ = self.loader.comment_handling & 0b11
# nprintf('>>>dca', comment, line, typ)
if typ == C_POST:
return None
if typ == C_PRE:
c = [None, None, comment[0]]
comment[0] = None
return c
# nprintf('>>>dcb', comment[0])
for _idx, cmntidx in enumerate(comment[0]):
# nprintf('>>>dcb', cmntidx)
if isinstance(self.scanner.comments[cmntidx], BlankLineComment):
break
else:
return None # no space found
if _idx == 0:
return None # first line was blank
# nprintf('>>>dcc', idx)
if typ == C_SPLIT_ON_FIRST_BLANK:
c = [None, None, comment[0][:_idx]]
comment[0] = comment[0][_idx:]
return c
raise NotImplementedError # reserved

View File

@@ -0,0 +1,277 @@
from __future__ import annotations
# This module contains abstractions for the input stream. You don't have to
# looks further, there are no pretty code.
#
# We define two classes here.
#
# Mark(source, line, column)
# It's just a record and its only use is producing nice error messages.
# Parser does not use it for any other purposes.
#
# Reader(source, data)
# Reader determines the encoding of `data` and converts it to unicode.
# Reader provides the following methods and attributes:
# reader.peek(length=1) - return the next `length` characters
# reader.forward(length=1) - move the current position to `length`
# characters.
# reader.index - the number of the current character.
# reader.line, stream.column - the line and the column of the current
# character.
import codecs
from ruamel.yaml.error import YAMLError, FileMark, StringMark, YAMLStreamError
from ruamel.yaml.util import RegExp
if False: # MYPY
from typing import Any, Dict, Optional, List, Union, Text, Tuple, Optional # NOQA
# from ruamel.yaml.compat import StreamTextType # NOQA
__all__ = ['Reader', 'ReaderError']
class ReaderError(YAMLError):
def __init__(
self, name: Any, position: Any, character: Any, encoding: Any, reason: Any,
) -> None:
self.name = name
self.character = character
self.position = position
self.encoding = encoding
self.reason = reason
def __str__(self) -> Any:
if isinstance(self.character, bytes):
return (
f"'{self.encoding!s}' codec can't decode byte #x{ord(self.character):02x}: "
f'{self.reason!s}\n'
f' in "{self.name!s}", position {self.position:d}'
)
else:
return (
f'unacceptable character #x{self.character:04x}: {self.reason!s}\n'
f' in "{self.name!s}", position {self.position:d}'
)
class Reader:
# Reader:
# - determines the data encoding and converts it to a unicode string,
# - checks if characters are in allowed range,
# - adds '\0' to the end.
# Reader accepts
# - a `bytes` object,
# - a `str` object,
# - a file-like object with its `read` method returning `str`,
# - a file-like object with its `read` method returning `unicode`.
# Yeah, it's ugly and slow.
def __init__(self, stream: Any, loader: Any = None) -> None:
self.loader = loader
if self.loader is not None and getattr(self.loader, '_reader', None) is None:
self.loader._reader = self
self.reset_reader()
self.stream: Any = stream # as .read is called
def reset_reader(self) -> None:
self.name: Any = None
self.stream_pointer = 0
self.eof = True
self.buffer = ""
self.pointer = 0
self.raw_buffer: Any = None
self.raw_decode = None
self.encoding: Optional[Text] = None
self.index = 0
self.line = 0
self.column = 0
@property
def stream(self) -> Any:
try:
return self._stream
except AttributeError:
raise YAMLStreamError('input stream needs to be specified')
@stream.setter
def stream(self, val: Any) -> None:
if val is None:
return
self._stream = None
if isinstance(val, str):
self.name = '<unicode string>'
self.check_printable(val)
self.buffer = val + '\0'
elif isinstance(val, bytes):
self.name = '<byte string>'
self.raw_buffer = val
self.determine_encoding()
else:
if not hasattr(val, 'read'):
raise YAMLStreamError('stream argument needs to have a read() method')
self._stream = val
self.name = getattr(self.stream, 'name', '<file>')
self.eof = False
self.raw_buffer = None
self.determine_encoding()
def peek(self, index: int = 0) -> Text:
try:
return self.buffer[self.pointer + index]
except IndexError:
self.update(index + 1)
return self.buffer[self.pointer + index]
def prefix(self, length: int = 1) -> Any:
if self.pointer + length >= len(self.buffer):
self.update(length)
return self.buffer[self.pointer : self.pointer + length]
def forward_1_1(self, length: int = 1) -> None:
if self.pointer + length + 1 >= len(self.buffer):
self.update(length + 1)
while length != 0:
ch = self.buffer[self.pointer]
self.pointer += 1
self.index += 1
if ch in '\n\x85\u2028\u2029' or (
ch == '\r' and self.buffer[self.pointer] != '\n'
):
self.line += 1
self.column = 0
elif ch != '\uFEFF':
self.column += 1
length -= 1
def forward(self, length: int = 1) -> None:
if self.pointer + length + 1 >= len(self.buffer):
self.update(length + 1)
while length != 0:
ch = self.buffer[self.pointer]
self.pointer += 1
self.index += 1
if ch == '\n' or (ch == '\r' and self.buffer[self.pointer] != '\n'):
self.line += 1
self.column = 0
elif ch != '\uFEFF':
self.column += 1
length -= 1
def get_mark(self) -> Any:
if self.stream is None:
return StringMark(
self.name, self.index, self.line, self.column, self.buffer, self.pointer,
)
else:
return FileMark(self.name, self.index, self.line, self.column)
def determine_encoding(self) -> None:
while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2):
self.update_raw()
if isinstance(self.raw_buffer, bytes):
if self.raw_buffer.startswith(codecs.BOM_UTF16_LE):
self.raw_decode = codecs.utf_16_le_decode # type: ignore
self.encoding = 'utf-16-le'
elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE):
self.raw_decode = codecs.utf_16_be_decode # type: ignore
self.encoding = 'utf-16-be'
else:
self.raw_decode = codecs.utf_8_decode # type: ignore
self.encoding = 'utf-8'
self.update(1)
NON_PRINTABLE = RegExp(
'[^\x09\x0A\x0D\x20-\x7E\x85' '\xA0-\uD7FF' '\uE000-\uFFFD' '\U00010000-\U0010FFFF' ']' # NOQA
)
_printable_ascii = ('\x09\x0A\x0D' + "".join(map(chr, range(0x20, 0x7F)))).encode('ascii')
@classmethod
def _get_non_printable_ascii(cls: Text, data: bytes) -> Optional[Tuple[int, Text]]: # type: ignore # NOQA
ascii_bytes = data.encode('ascii') # type: ignore
non_printables = ascii_bytes.translate(None, cls._printable_ascii) # type: ignore
if not non_printables:
return None
non_printable = non_printables[:1]
return ascii_bytes.index(non_printable), non_printable.decode('ascii')
@classmethod
def _get_non_printable_regex(cls, data: Text) -> Optional[Tuple[int, Text]]:
match = cls.NON_PRINTABLE.search(data)
if not bool(match):
return None
return match.start(), match.group()
@classmethod
def _get_non_printable(cls, data: Text) -> Optional[Tuple[int, Text]]:
try:
return cls._get_non_printable_ascii(data) # type: ignore
except UnicodeEncodeError:
return cls._get_non_printable_regex(data)
def check_printable(self, data: Any) -> None:
non_printable_match = self._get_non_printable(data)
if non_printable_match is not None:
start, character = non_printable_match
position = self.index + (len(self.buffer) - self.pointer) + start
raise ReaderError(
self.name,
position,
ord(character),
'unicode',
'special characters are not allowed',
)
def update(self, length: int) -> None:
if self.raw_buffer is None:
return
self.buffer = self.buffer[self.pointer :]
self.pointer = 0
while len(self.buffer) < length:
if not self.eof:
self.update_raw()
if self.raw_decode is not None:
try:
data, converted = self.raw_decode(self.raw_buffer, 'strict', self.eof)
except UnicodeDecodeError as exc:
character = self.raw_buffer[exc.start]
if self.stream is not None:
position = self.stream_pointer - len(self.raw_buffer) + exc.start
elif self.stream is not None:
position = self.stream_pointer - len(self.raw_buffer) + exc.start
else:
position = exc.start
raise ReaderError(self.name, position, character, exc.encoding, exc.reason)
else:
data = self.raw_buffer
converted = len(data)
self.check_printable(data)
self.buffer += data
self.raw_buffer = self.raw_buffer[converted:]
if self.eof:
self.buffer += '\0'
self.raw_buffer = None
break
def update_raw(self, size: Optional[int] = None) -> None:
if size is None:
size = 4096
data = self.stream.read(size)
if self.raw_buffer is None:
self.raw_buffer = data
else:
self.raw_buffer += data
self.stream_pointer += len(data)
if not data:
self.eof = True
# try:
# import psyco
# psyco.bind(Reader)
# except ImportError:
# pass

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,392 @@
from __future__ import annotations
import re
if False: # MYPY
from typing import Any, Dict, List, Union, Text, Optional # NOQA
from ruamel.yaml.compat import VersionType # NOQA
from ruamel.yaml.tag import Tag
from ruamel.yaml.compat import _DEFAULT_YAML_VERSION # NOQA
from ruamel.yaml.error import * # NOQA
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode # NOQA
from ruamel.yaml.util import RegExp # NOQA
__all__ = ['BaseResolver', 'Resolver', 'VersionedResolver']
# fmt: off
# resolvers consist of
# - a list of applicable version
# - a tag
# - a regexp
# - a list of first characters to match
implicit_resolvers = [
([(1, 2)],
'tag:yaml.org,2002:bool',
RegExp('''^(?:true|True|TRUE|false|False|FALSE)$''', re.X),
list('tTfF')),
([(1, 1)],
'tag:yaml.org,2002:bool',
RegExp('''^(?:y|Y|yes|Yes|YES|n|N|no|No|NO
|true|True|TRUE|false|False|FALSE
|on|On|ON|off|Off|OFF)$''', re.X),
list('yYnNtTfFoO')),
([(1, 2)],
'tag:yaml.org,2002:float',
RegExp('''^(?:
[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
|[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
|[-+]?\\.[0-9_]+(?:[eE][-+][0-9]+)?
|[-+]?\\.(?:inf|Inf|INF)
|\\.(?:nan|NaN|NAN))$''', re.X),
list('-+0123456789.')),
([(1, 1)],
'tag:yaml.org,2002:float',
RegExp('''^(?:
[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
|[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
|\\.[0-9_]+(?:[eE][-+][0-9]+)?
|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* # sexagesimal float
|[-+]?\\.(?:inf|Inf|INF)
|\\.(?:nan|NaN|NAN))$''', re.X),
list('-+0123456789.')),
([(1, 2)],
'tag:yaml.org,2002:int',
RegExp('''^(?:[-+]?0b[0-1_]+
|[-+]?0o?[0-7_]+
|[-+]?[0-9_]+
|[-+]?0x[0-9a-fA-F_]+)$''', re.X),
list('-+0123456789')),
([(1, 1)],
'tag:yaml.org,2002:int',
RegExp('''^(?:[-+]?0b[0-1_]+
|[-+]?0?[0-7_]+
|[-+]?(?:0|[1-9][0-9_]*)
|[-+]?0x[0-9a-fA-F_]+
|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), # sexagesimal int
list('-+0123456789')),
([(1, 2), (1, 1)],
'tag:yaml.org,2002:merge',
RegExp('^(?:<<)$'),
['<']),
([(1, 2), (1, 1)],
'tag:yaml.org,2002:null',
RegExp('''^(?: ~
|null|Null|NULL
| )$''', re.X),
['~', 'n', 'N', '']),
([(1, 2), (1, 1)],
'tag:yaml.org,2002:timestamp',
RegExp('''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
|[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?
(?:[Tt]|[ \\t]+)[0-9][0-9]?
:[0-9][0-9] :[0-9][0-9] (?:\\.[0-9]*)?
(?:[ \\t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X),
list('0123456789')),
([(1, 2), (1, 1)],
'tag:yaml.org,2002:value',
RegExp('^(?:=)$'),
['=']),
# The following resolver is only for documentation purposes. It cannot work
# because plain scalars cannot start with '!', '&', or '*'.
([(1, 2), (1, 1)],
'tag:yaml.org,2002:yaml',
RegExp('^(?:!|&|\\*)$'),
list('!&*')),
]
# fmt: on
class ResolverError(YAMLError):
pass
class BaseResolver:
DEFAULT_SCALAR_TAG = Tag(suffix='tag:yaml.org,2002:str')
DEFAULT_SEQUENCE_TAG = Tag(suffix='tag:yaml.org,2002:seq')
DEFAULT_MAPPING_TAG = Tag(suffix='tag:yaml.org,2002:map')
yaml_implicit_resolvers: Dict[Any, Any] = {}
yaml_path_resolvers: Dict[Any, Any] = {}
def __init__(self: Any, loadumper: Any = None) -> None:
self.loadumper = loadumper
if self.loadumper is not None and getattr(self.loadumper, '_resolver', None) is None:
self.loadumper._resolver = self.loadumper
self._loader_version: Any = None
self.resolver_exact_paths: List[Any] = []
self.resolver_prefix_paths: List[Any] = []
@property
def parser(self) -> Any:
if self.loadumper is not None:
if hasattr(self.loadumper, 'typ'):
return self.loadumper.parser
return self.loadumper._parser
return None
@classmethod
def add_implicit_resolver_base(cls, tag: Any, regexp: Any, first: Any) -> None:
if 'yaml_implicit_resolvers' not in cls.__dict__:
# deepcopy doesn't work here
cls.yaml_implicit_resolvers = {
k: cls.yaml_implicit_resolvers[k][:] for k in cls.yaml_implicit_resolvers
}
if first is None:
first = [None]
for ch in first:
cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
@classmethod
def add_implicit_resolver(cls, tag: Any, regexp: Any, first: Any) -> None:
if 'yaml_implicit_resolvers' not in cls.__dict__:
# deepcopy doesn't work here
cls.yaml_implicit_resolvers = {
k: cls.yaml_implicit_resolvers[k][:] for k in cls.yaml_implicit_resolvers
}
if first is None:
first = [None]
for ch in first:
cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
implicit_resolvers.append(([(1, 2), (1, 1)], tag, regexp, first))
# @classmethod
# def add_implicit_resolver(cls, tag, regexp, first):
@classmethod
def add_path_resolver(cls, tag: Any, path: Any, kind: Any = None) -> None:
# Note: `add_path_resolver` is experimental. The API could be changed.
# `new_path` is a pattern that is matched against the path from the
# root to the node that is being considered. `node_path` elements are
# tuples `(node_check, index_check)`. `node_check` is a node class:
# `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None`
# matches any kind of a node. `index_check` could be `None`, a boolean
# value, a string value, or a number. `None` and `False` match against
# any _value_ of sequence and mapping nodes. `True` matches against
# any _key_ of a mapping node. A string `index_check` matches against
# a mapping value that corresponds to a scalar key which content is
# equal to the `index_check` value. An integer `index_check` matches
# against a sequence value with the index equal to `index_check`.
if 'yaml_path_resolvers' not in cls.__dict__:
cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy()
new_path: List[Any] = []
for element in path:
if isinstance(element, (list, tuple)):
if len(element) == 2:
node_check, index_check = element
elif len(element) == 1:
node_check = element[0]
index_check = True
else:
raise ResolverError(f'Invalid path element: {element!s}')
else:
node_check = None
index_check = element
if node_check is str:
node_check = ScalarNode
elif node_check is list:
node_check = SequenceNode
elif node_check is dict:
node_check = MappingNode
elif (
node_check not in [ScalarNode, SequenceNode, MappingNode]
and not isinstance(node_check, str)
and node_check is not None
):
raise ResolverError(f'Invalid node checker: {node_check!s}')
if not isinstance(index_check, (str, int)) and index_check is not None:
raise ResolverError(f'Invalid index checker: {index_check!s}')
new_path.append((node_check, index_check))
if kind is str:
kind = ScalarNode
elif kind is list:
kind = SequenceNode
elif kind is dict:
kind = MappingNode
elif kind not in [ScalarNode, SequenceNode, MappingNode] and kind is not None:
raise ResolverError(f'Invalid node kind: {kind!s}')
cls.yaml_path_resolvers[tuple(new_path), kind] = tag
def descend_resolver(self, current_node: Any, current_index: Any) -> None:
if not self.yaml_path_resolvers:
return
exact_paths = {}
prefix_paths = []
if current_node:
depth = len(self.resolver_prefix_paths)
for path, kind in self.resolver_prefix_paths[-1]:
if self.check_resolver_prefix(depth, path, kind, current_node, current_index):
if len(path) > depth:
prefix_paths.append((path, kind))
else:
exact_paths[kind] = self.yaml_path_resolvers[path, kind]
else:
for path, kind in self.yaml_path_resolvers:
if not path:
exact_paths[kind] = self.yaml_path_resolvers[path, kind]
else:
prefix_paths.append((path, kind))
self.resolver_exact_paths.append(exact_paths)
self.resolver_prefix_paths.append(prefix_paths)
def ascend_resolver(self) -> None:
if not self.yaml_path_resolvers:
return
self.resolver_exact_paths.pop()
self.resolver_prefix_paths.pop()
def check_resolver_prefix(
self, depth: int, path: Any, kind: Any, current_node: Any, current_index: Any,
) -> bool:
node_check, index_check = path[depth - 1]
if isinstance(node_check, str):
if current_node.tag != node_check:
return False
elif node_check is not None:
if not isinstance(current_node, node_check):
return False
if index_check is True and current_index is not None:
return False
if (index_check is False or index_check is None) and current_index is None:
return False
if isinstance(index_check, str):
if not (
isinstance(current_index, ScalarNode) and index_check == current_index.value
):
return False
elif isinstance(index_check, int) and not isinstance(index_check, bool):
if index_check != current_index:
return False
return True
def resolve(self, kind: Any, value: Any, implicit: Any) -> Any:
if kind is ScalarNode and implicit[0]:
if value == "":
resolvers = self.yaml_implicit_resolvers.get("", [])
else:
resolvers = self.yaml_implicit_resolvers.get(value[0], [])
resolvers += self.yaml_implicit_resolvers.get(None, [])
for tag, regexp in resolvers:
if regexp.match(value):
return Tag(suffix=tag)
implicit = implicit[1]
if bool(self.yaml_path_resolvers):
exact_paths = self.resolver_exact_paths[-1]
if kind in exact_paths:
return Tag(suffix=exact_paths[kind])
if None in exact_paths:
return Tag(suffix=exact_paths[None])
if kind is ScalarNode:
return self.DEFAULT_SCALAR_TAG
elif kind is SequenceNode:
return self.DEFAULT_SEQUENCE_TAG
elif kind is MappingNode:
return self.DEFAULT_MAPPING_TAG
@property
def processing_version(self) -> Any:
return None
class Resolver(BaseResolver):
pass
for ir in implicit_resolvers:
if (1, 2) in ir[0]:
Resolver.add_implicit_resolver_base(*ir[1:])
class VersionedResolver(BaseResolver):
"""
contrary to the "normal" resolver, the smart resolver delays loading
the pattern matching rules. That way it can decide to load 1.1 rules
or the (default) 1.2 rules, that no longer support octal without 0o, sexagesimals
and Yes/No/On/Off booleans.
"""
def __init__(
self, version: Optional[VersionType] = None, loader: Any = None, loadumper: Any = None,
) -> None:
if loader is None and loadumper is not None:
loader = loadumper
BaseResolver.__init__(self, loader)
self._loader_version = self.get_loader_version(version)
self._version_implicit_resolver: Dict[Any, Any] = {}
def add_version_implicit_resolver(
self, version: VersionType, tag: Any, regexp: Any, first: Any,
) -> None:
if first is None:
first = [None]
impl_resolver = self._version_implicit_resolver.setdefault(version, {})
for ch in first:
impl_resolver.setdefault(ch, []).append((tag, regexp))
def get_loader_version(self, version: Optional[VersionType]) -> Any:
if version is None or isinstance(version, tuple):
return version
if isinstance(version, list):
return tuple(version)
# assume string
assert isinstance(version, str)
return tuple(map(int, version.split('.')))
@property
def versioned_resolver(self) -> Any:
"""
select the resolver based on the version we are parsing
"""
version = self.processing_version
if isinstance(version, str):
version = tuple(map(int, version.split('.')))
if version not in self._version_implicit_resolver:
for x in implicit_resolvers:
if version in x[0]:
self.add_version_implicit_resolver(version, x[1], x[2], x[3])
return self._version_implicit_resolver[version]
def resolve(self, kind: Any, value: Any, implicit: Any) -> Any:
if kind is ScalarNode and implicit[0]:
if value == "":
resolvers = self.versioned_resolver.get("", [])
else:
resolvers = self.versioned_resolver.get(value[0], [])
resolvers += self.versioned_resolver.get(None, [])
for tag, regexp in resolvers:
if regexp.match(value):
return Tag(suffix=tag)
implicit = implicit[1]
if bool(self.yaml_path_resolvers):
exact_paths = self.resolver_exact_paths[-1]
if kind in exact_paths:
return Tag(suffix=exact_paths[kind])
if None in exact_paths:
return Tag(suffix=exact_paths[None])
if kind is ScalarNode:
return self.DEFAULT_SCALAR_TAG
elif kind is SequenceNode:
return self.DEFAULT_SEQUENCE_TAG
elif kind is MappingNode:
return self.DEFAULT_MAPPING_TAG
@property
def processing_version(self) -> Any:
try:
version = self.loadumper._scanner.yaml_version
except AttributeError:
try:
if hasattr(self.loadumper, 'typ'):
version = self.loadumper.version
else:
version = self.loadumper._serializer.use_version # dumping
except AttributeError:
version = None
if version is None:
version = self._loader_version
if version is None:
version = _DEFAULT_YAML_VERSION
return version

View File

@@ -0,0 +1,43 @@
"""
You cannot subclass bool, and this is necessary for round-tripping anchored
bool values (and also if you want to preserve the original way of writing)
bool.__bases__ is type 'int', so that is what is used as the basis for ScalarBoolean as well.
You can use these in an if statement, but not when testing equivalence
"""
from __future__ import annotations
from ruamel.yaml.anchor import Anchor
if False: # MYPY
from typing import Text, Any, Dict, List # NOQA
__all__ = ['ScalarBoolean']
class ScalarBoolean(int):
def __new__(cls: Any, *args: Any, **kw: Any) -> Any:
anchor = kw.pop('anchor', None)
b = int.__new__(cls, *args, **kw)
if anchor is not None:
b.yaml_set_anchor(anchor, always_dump=True)
return b
@property
def anchor(self) -> Any:
if not hasattr(self, Anchor.attrib):
setattr(self, Anchor.attrib, Anchor())
return getattr(self, Anchor.attrib)
def yaml_anchor(self, any: bool = False) -> Any:
if not hasattr(self, Anchor.attrib):
return None
if any or self.anchor.always_dump:
return self.anchor
return None
def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
self.anchor.value = value
self.anchor.always_dump = always_dump

View File

@@ -0,0 +1,105 @@
from __future__ import annotations
import sys
from ruamel.yaml.anchor import Anchor
if False: # MYPY
from typing import Text, Any, Dict, List # NOQA
__all__ = ['ScalarFloat', 'ExponentialFloat', 'ExponentialCapsFloat']
class ScalarFloat(float):
def __new__(cls: Any, *args: Any, **kw: Any) -> Any:
width = kw.pop('width', None)
prec = kw.pop('prec', None)
m_sign = kw.pop('m_sign', None)
m_lead0 = kw.pop('m_lead0', 0)
exp = kw.pop('exp', None)
e_width = kw.pop('e_width', None)
e_sign = kw.pop('e_sign', None)
underscore = kw.pop('underscore', None)
anchor = kw.pop('anchor', None)
v = float.__new__(cls, *args, **kw)
v._width = width
v._prec = prec
v._m_sign = m_sign
v._m_lead0 = m_lead0
v._exp = exp
v._e_width = e_width
v._e_sign = e_sign
v._underscore = underscore
if anchor is not None:
v.yaml_set_anchor(anchor, always_dump=True)
return v
def __iadd__(self, a: Any) -> Any: # type: ignore
return float(self) + a
x = type(self)(self + a)
x._width = self._width
x._underscore = self._underscore[:] if self._underscore is not None else None # NOQA
return x
def __ifloordiv__(self, a: Any) -> Any: # type: ignore
return float(self) // a
x = type(self)(self // a)
x._width = self._width
x._underscore = self._underscore[:] if self._underscore is not None else None # NOQA
return x
def __imul__(self, a: Any) -> Any: # type: ignore
return float(self) * a
x = type(self)(self * a)
x._width = self._width
x._underscore = self._underscore[:] if self._underscore is not None else None # NOQA
x._prec = self._prec # check for others
return x
def __ipow__(self, a: Any) -> Any: # type: ignore
return float(self) ** a
x = type(self)(self ** a)
x._width = self._width
x._underscore = self._underscore[:] if self._underscore is not None else None # NOQA
return x
def __isub__(self, a: Any) -> Any: # type: ignore
return float(self) - a
x = type(self)(self - a)
x._width = self._width
x._underscore = self._underscore[:] if self._underscore is not None else None # NOQA
return x
@property
def anchor(self) -> Any:
if not hasattr(self, Anchor.attrib):
setattr(self, Anchor.attrib, Anchor())
return getattr(self, Anchor.attrib)
def yaml_anchor(self, any: bool = False) -> Any:
if not hasattr(self, Anchor.attrib):
return None
if any or self.anchor.always_dump:
return self.anchor
return None
def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
self.anchor.value = value
self.anchor.always_dump = always_dump
def dump(self, out: Any = sys.stdout) -> None:
out.write(
f'ScalarFloat({self}| w:{self._width}, p:{self._prec}, ' # type: ignore
f's:{self._m_sign}, lz:{self._m_lead0}, _:{self._underscore}|{self._exp}'
f', w:{self._e_width}, s:{self._e_sign})\n',
)
class ExponentialFloat(ScalarFloat):
def __new__(cls, value: Any, width: Any = None, underscore: Any = None) -> Any:
return ScalarFloat.__new__(cls, value, width=width, underscore=underscore)
class ExponentialCapsFloat(ScalarFloat):
def __new__(cls, value: Any, width: Any = None, underscore: Any = None) -> Any:
return ScalarFloat.__new__(cls, value, width=width, underscore=underscore)

View File

@@ -0,0 +1,124 @@
from __future__ import annotations
from ruamel.yaml.anchor import Anchor
if False: # MYPY
from typing import Text, Any, Dict, List # NOQA
__all__ = ['ScalarInt', 'BinaryInt', 'OctalInt', 'HexInt', 'HexCapsInt', 'DecimalInt']
class ScalarInt(int):
def __new__(cls: Any, *args: Any, **kw: Any) -> Any:
width = kw.pop('width', None)
underscore = kw.pop('underscore', None)
anchor = kw.pop('anchor', None)
v = int.__new__(cls, *args, **kw)
v._width = width
v._underscore = underscore
if anchor is not None:
v.yaml_set_anchor(anchor, always_dump=True)
return v
def __iadd__(self, a: Any) -> Any: # type: ignore
x = type(self)(self + a)
x._width = self._width # type: ignore
x._underscore = ( # type: ignore
self._underscore[:] if self._underscore is not None else None # type: ignore
) # NOQA
return x
def __ifloordiv__(self, a: Any) -> Any: # type: ignore
x = type(self)(self // a)
x._width = self._width # type: ignore
x._underscore = ( # type: ignore
self._underscore[:] if self._underscore is not None else None # type: ignore
) # NOQA
return x
def __imul__(self, a: Any) -> Any: # type: ignore
x = type(self)(self * a)
x._width = self._width # type: ignore
x._underscore = ( # type: ignore
self._underscore[:] if self._underscore is not None else None # type: ignore
) # NOQA
return x
def __ipow__(self, a: Any) -> Any: # type: ignore
x = type(self)(self ** a)
x._width = self._width # type: ignore
x._underscore = ( # type: ignore
self._underscore[:] if self._underscore is not None else None # type: ignore
) # NOQA
return x
def __isub__(self, a: Any) -> Any: # type: ignore
x = type(self)(self - a)
x._width = self._width # type: ignore
x._underscore = ( # type: ignore
self._underscore[:] if self._underscore is not None else None # type: ignore
) # NOQA
return x
@property
def anchor(self) -> Any:
if not hasattr(self, Anchor.attrib):
setattr(self, Anchor.attrib, Anchor())
return getattr(self, Anchor.attrib)
def yaml_anchor(self, any: bool = False) -> Any:
if not hasattr(self, Anchor.attrib):
return None
if any or self.anchor.always_dump:
return self.anchor
return None
def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
self.anchor.value = value
self.anchor.always_dump = always_dump
class BinaryInt(ScalarInt):
def __new__(
cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
) -> Any:
return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)
class OctalInt(ScalarInt):
def __new__(
cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
) -> Any:
return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)
# mixed casing of A-F is not supported, when loading the first non digit
# determines the case
class HexInt(ScalarInt):
"""uses lower case (a-f)"""
def __new__(
cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
) -> Any:
return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)
class HexCapsInt(ScalarInt):
"""uses upper case (A-F)"""
def __new__(
cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
) -> Any:
return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)
class DecimalInt(ScalarInt):
"""needed if anchor"""
def __new__(
cls, value: Any, width: Any = None, underscore: Any = None, anchor: Any = None,
) -> Any:
return ScalarInt.__new__(cls, value, width=width, underscore=underscore, anchor=anchor)

View File

@@ -0,0 +1,142 @@
from __future__ import annotations
from ruamel.yaml.anchor import Anchor
if False: # MYPY
from typing import Text, Any, Dict, List # NOQA
from ruamel.yaml.compat import SupportsIndex
__all__ = [
'ScalarString',
'LiteralScalarString',
'FoldedScalarString',
'SingleQuotedScalarString',
'DoubleQuotedScalarString',
'PlainScalarString',
# PreservedScalarString is the old name, as it was the first to be preserved on rt,
# use LiteralScalarString instead
'PreservedScalarString',
]
class ScalarString(str):
__slots__ = Anchor.attrib
def __new__(cls, *args: Any, **kw: Any) -> Any:
anchor = kw.pop('anchor', None)
ret_val = str.__new__(cls, *args, **kw)
if anchor is not None:
ret_val.yaml_set_anchor(anchor, always_dump=True)
return ret_val
def replace(self, old: Any, new: Any, maxreplace: SupportsIndex = -1) -> Any:
return type(self)((str.replace(self, old, new, maxreplace)))
@property
def anchor(self) -> Any:
if not hasattr(self, Anchor.attrib):
setattr(self, Anchor.attrib, Anchor())
return getattr(self, Anchor.attrib)
def yaml_anchor(self, any: bool = False) -> Any:
if not hasattr(self, Anchor.attrib):
return None
if any or self.anchor.always_dump:
return self.anchor
return None
def yaml_set_anchor(self, value: Any, always_dump: bool = False) -> None:
self.anchor.value = value
self.anchor.always_dump = always_dump
class LiteralScalarString(ScalarString):
__slots__ = 'comment' # the comment after the | on the first line
style = '|'
def __new__(cls, value: Text, anchor: Any = None) -> Any:
return ScalarString.__new__(cls, value, anchor=anchor)
PreservedScalarString = LiteralScalarString
class FoldedScalarString(ScalarString):
__slots__ = ('fold_pos', 'comment') # the comment after the > on the first line
style = '>'
def __new__(cls, value: Text, anchor: Any = None) -> Any:
return ScalarString.__new__(cls, value, anchor=anchor)
class SingleQuotedScalarString(ScalarString):
__slots__ = ()
style = "'"
def __new__(cls, value: Text, anchor: Any = None) -> Any:
return ScalarString.__new__(cls, value, anchor=anchor)
class DoubleQuotedScalarString(ScalarString):
__slots__ = ()
style = '"'
def __new__(cls, value: Text, anchor: Any = None) -> Any:
return ScalarString.__new__(cls, value, anchor=anchor)
class PlainScalarString(ScalarString):
__slots__ = ()
style = ''
def __new__(cls, value: Text, anchor: Any = None) -> Any:
return ScalarString.__new__(cls, value, anchor=anchor)
def preserve_literal(s: Text) -> Text:
return LiteralScalarString(s.replace('\r\n', '\n').replace('\r', '\n'))
def walk_tree(base: Any, map: Any = None) -> None:
"""
the routine here walks over a simple yaml tree (recursing in
dict values and list items) and converts strings that
have multiple lines to literal scalars
You can also provide an explicit (ordered) mapping for multiple transforms
(first of which is executed):
map = ruamel.yaml.compat.ordereddict
map['\n'] = preserve_literal
map[':'] = SingleQuotedScalarString
walk_tree(data, map=map)
"""
from collections.abc import MutableMapping, MutableSequence
if map is None:
map = {'\n': preserve_literal}
if isinstance(base, MutableMapping):
for k in base:
v: Text = base[k]
if isinstance(v, str):
for ch in map:
if ch in v:
base[k] = map[ch](v)
break
else:
walk_tree(v, map=map)
elif isinstance(base, MutableSequence):
for idx, elem in enumerate(base):
if isinstance(elem, str):
for ch in map:
if ch in elem:
base[idx] = map[ch](elem)
break
else:
walk_tree(elem, map=map)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,233 @@
from __future__ import annotations
from ruamel.yaml.error import YAMLError
from ruamel.yaml.compat import nprint, DBG_NODE, dbg, nprintf # NOQA
from ruamel.yaml.util import RegExp
from ruamel.yaml.events import (
StreamStartEvent,
StreamEndEvent,
MappingStartEvent,
MappingEndEvent,
SequenceStartEvent,
SequenceEndEvent,
AliasEvent,
ScalarEvent,
DocumentStartEvent,
DocumentEndEvent,
)
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode
if False: # MYPY
from typing import Any, Dict, Union, Text, Optional # NOQA
from ruamel.yaml.compat import VersionType # NOQA
__all__ = ['Serializer', 'SerializerError']
class SerializerError(YAMLError):
pass
class Serializer:
# 'id' and 3+ numbers, but not 000
ANCHOR_TEMPLATE = 'id{:03d}'
ANCHOR_RE = RegExp('id(?!000$)\\d{3,}')
def __init__(
self,
encoding: Any = None,
explicit_start: Optional[bool] = None,
explicit_end: Optional[bool] = None,
version: Optional[VersionType] = None,
tags: Any = None,
dumper: Any = None,
) -> None:
# NOQA
self.dumper = dumper
if self.dumper is not None:
self.dumper._serializer = self
self.use_encoding = encoding
self.use_explicit_start = explicit_start
self.use_explicit_end = explicit_end
if isinstance(version, str):
self.use_version = tuple(map(int, version.split('.')))
else:
self.use_version = version # type: ignore
self.use_tags = tags
self.serialized_nodes: Dict[Any, Any] = {}
self.anchors: Dict[Any, Any] = {}
self.last_anchor_id = 0
self.closed: Optional[bool] = None
self._templated_id = None
@property
def emitter(self) -> Any:
if hasattr(self.dumper, 'typ'):
return self.dumper.emitter
return self.dumper._emitter
@property
def resolver(self) -> Any:
if hasattr(self.dumper, 'typ'):
self.dumper.resolver
return self.dumper._resolver
def open(self) -> None:
if self.closed is None:
self.emitter.emit(StreamStartEvent(encoding=self.use_encoding))
self.closed = False
elif self.closed:
raise SerializerError('serializer is closed')
else:
raise SerializerError('serializer is already opened')
def close(self) -> None:
if self.closed is None:
raise SerializerError('serializer is not opened')
elif not self.closed:
self.emitter.emit(StreamEndEvent())
self.closed = True
# def __del__(self):
# self.close()
def serialize(self, node: Any) -> None:
if dbg(DBG_NODE):
nprint('Serializing nodes')
node.dump()
if self.closed is None:
raise SerializerError('serializer is not opened')
elif self.closed:
raise SerializerError('serializer is closed')
self.emitter.emit(
DocumentStartEvent(
explicit=self.use_explicit_start, version=self.use_version, tags=self.use_tags,
),
)
self.anchor_node(node)
self.serialize_node(node, None, None)
self.emitter.emit(DocumentEndEvent(explicit=self.use_explicit_end))
self.serialized_nodes = {}
self.anchors = {}
self.last_anchor_id = 0
def anchor_node(self, node: Any) -> None:
if node in self.anchors:
if self.anchors[node] is None:
self.anchors[node] = self.generate_anchor(node)
else:
anchor = None
try:
if node.anchor.always_dump:
anchor = node.anchor.value
except: # NOQA
pass
self.anchors[node] = anchor
if isinstance(node, SequenceNode):
for item in node.value:
self.anchor_node(item)
elif isinstance(node, MappingNode):
for key, value in node.value:
self.anchor_node(key)
self.anchor_node(value)
def generate_anchor(self, node: Any) -> Any:
try:
anchor = node.anchor.value
except: # NOQA
anchor = None
if anchor is None:
self.last_anchor_id += 1
return self.ANCHOR_TEMPLATE.format(self.last_anchor_id)
return anchor
def serialize_node(self, node: Any, parent: Any, index: Any) -> None:
alias = self.anchors[node]
if node in self.serialized_nodes:
node_style = getattr(node, 'style', None)
if node_style != '?':
node_style = None
self.emitter.emit(AliasEvent(alias, style=node_style))
else:
self.serialized_nodes[node] = True
self.resolver.descend_resolver(parent, index)
if isinstance(node, ScalarNode):
# here check if the node.tag equals the one that would result from parsing
# if not equal quoting is necessary for strings
detected_tag = self.resolver.resolve(ScalarNode, node.value, (True, False))
default_tag = self.resolver.resolve(ScalarNode, node.value, (False, True))
implicit = (
(node.ctag == detected_tag),
(node.ctag == default_tag),
node.tag.startswith('tag:yaml.org,2002:'), # type: ignore
)
self.emitter.emit(
ScalarEvent(
alias,
node.ctag,
implicit,
node.value,
style=node.style,
comment=node.comment,
),
)
elif isinstance(node, SequenceNode):
implicit = node.ctag == self.resolver.resolve(SequenceNode, node.value, True)
comment = node.comment
end_comment = None
seq_comment = None
if node.flow_style is True:
if comment: # eol comment on flow style sequence
seq_comment = comment[0]
# comment[0] = None
if comment and len(comment) > 2:
end_comment = comment[2]
else:
end_comment = None
self.emitter.emit(
SequenceStartEvent(
alias,
node.ctag,
implicit,
flow_style=node.flow_style,
comment=node.comment,
),
)
index = 0
for item in node.value:
self.serialize_node(item, node, index)
index += 1
self.emitter.emit(SequenceEndEvent(comment=[seq_comment, end_comment]))
elif isinstance(node, MappingNode):
implicit = node.ctag == self.resolver.resolve(MappingNode, node.value, True)
comment = node.comment
end_comment = None
map_comment = None
if node.flow_style is True:
if comment: # eol comment on flow style sequence
map_comment = comment[0]
# comment[0] = None
if comment and len(comment) > 2:
end_comment = comment[2]
self.emitter.emit(
MappingStartEvent(
alias,
node.ctag,
implicit,
flow_style=node.flow_style,
comment=node.comment,
nr_items=len(node.value),
),
)
for key, value in node.value:
self.serialize_node(key, node, None)
self.serialize_node(value, node, key)
self.emitter.emit(MappingEndEvent(comment=[map_comment, end_comment]))
self.resolver.ascend_resolver()
def templated_id(s: Text) -> Any:
return Serializer.ANCHOR_RE.match(s)

View File

@@ -0,0 +1,126 @@
from __future__ import annotations
"""
In round-trip mode the original tag needs to be preserved, but the tag
transformed based on the directives needs to be available as well.
A Tag that is created during loading has a handle and a suffix.
Not all objects loaded currently have a Tag, that .tag attribute can be None
A Tag that is created for dumping only (on an object loaded without a tag) has a suffix
only.
"""
if False: # MYPY
from typing import Any, Dict, Optional, List, Union, Iterator # NOQA
tag_attrib = '_yaml_tag'
class Tag:
"""store original tag information for roundtripping"""
attrib = tag_attrib
def __init__(self, handle: Any = None, suffix: Any = None, handles: Any = None) -> None:
self.handle = handle
self.suffix = suffix
self.handles = handles
self._transform_type: Optional[bool] = None
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.trval!r})'
def __str__(self) -> str:
return f'{self.trval}'
def __hash__(self) -> int:
try:
return self._hash_id # type: ignore
except AttributeError:
self._hash_id = res = hash((self.handle, self.suffix))
return res
def __eq__(self, other: Any) -> bool:
# other should not be a string, but the serializer sometimes provides these
if isinstance(other, str):
return self.trval == other
return bool(self.trval == other.trval)
def startswith(self, x: str) -> bool:
if self.trval is not None:
return self.trval.startswith(x)
return False
@property
def trval(self) -> Optional[str]:
try:
return self._trval
except AttributeError:
pass
if self.handle is None:
self._trval: Optional[str] = self.uri_decoded_suffix
return self._trval
assert self._transform_type is not None
if not self._transform_type:
# the non-round-trip case
self._trval = self.handles[self.handle] + self.uri_decoded_suffix
return self._trval
# round-trip case
if self.handle == '!!' and self.suffix in (
'null',
'bool',
'int',
'float',
'binary',
'timestamp',
'omap',
'pairs',
'set',
'str',
'seq',
'map',
):
self._trval = self.handles[self.handle] + self.uri_decoded_suffix
else:
# self._trval = self.handle + self.suffix
self._trval = self.handles[self.handle] + self.uri_decoded_suffix
return self._trval
value = trval
@property
def uri_decoded_suffix(self) -> Optional[str]:
try:
return self._uri_decoded_suffix
except AttributeError:
pass
if self.suffix is None:
self._uri_decoded_suffix: Optional[str] = None
return None
res = ''
# don't have to check for scanner errors here
idx = 0
while idx < len(self.suffix):
ch = self.suffix[idx]
idx += 1
if ch != '%':
res += ch
else:
res += chr(int(self.suffix[idx : idx + 2], 16))
idx += 2
self._uri_decoded_suffix = res
return res
def select_transform(self, val: bool) -> None:
"""
val: False -> non-round-trip
True -> round-trip
"""
assert self._transform_type is None
self._transform_type = val
def check_handle(self) -> bool:
if self.handle is None:
return False
return self.handle not in self.handles

View File

@@ -0,0 +1,63 @@
from __future__ import annotations
import copy
import datetime
# ToDo: at least on PY3 you could probably attach the tzinfo correctly to the object
# a more complete datetime might be used by safe loading as well
#
# add type information (iso8601, spaced)
if False: # MYPY
from typing import Any, Dict, Optional, List # NOQA
class TimeStamp(datetime.datetime):
def __init__(self, *args: Any, **kw: Any) -> None:
self._yaml: Dict[str, Any] = dict(t=False, tz=None, delta=0)
def __new__(cls, *args: Any, **kw: Any) -> Any: # datetime is immutable
return datetime.datetime.__new__(cls, *args, **kw)
def __deepcopy__(self, memo: Any) -> Any:
ts = TimeStamp(self.year, self.month, self.day, self.hour, self.minute, self.second)
ts._yaml = copy.deepcopy(self._yaml)
return ts
def replace(
self,
year: Any = None,
month: Any = None,
day: Any = None,
hour: Any = None,
minute: Any = None,
second: Any = None,
microsecond: Any = None,
tzinfo: Any = True,
fold: Any = None,
) -> Any:
if year is None:
year = self.year
if month is None:
month = self.month
if day is None:
day = self.day
if hour is None:
hour = self.hour
if minute is None:
minute = self.minute
if second is None:
second = self.second
if microsecond is None:
microsecond = self.microsecond
if tzinfo is True:
tzinfo = self.tzinfo
if fold is None:
fold = self.fold
ts = type(self)(year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold)
ts._yaml = copy.deepcopy(self._yaml)
return ts
def __str__(self) -> str:
return self.isoformat('T' if self._yaml['t'] else ' ')

View File

@@ -0,0 +1,384 @@
from __future__ import annotations
from ruamel.yaml.compat import nprintf # NOQA
if False: # MYPY
from typing import Text, Any, Dict, Optional, List # NOQA
from .error import StreamMark # NOQA
SHOW_LINES = True
class Token:
__slots__ = 'start_mark', 'end_mark', '_comment'
def __init__(self, start_mark: StreamMark, end_mark: StreamMark) -> None:
self.start_mark = start_mark
self.end_mark = end_mark
def __repr__(self) -> Any:
# attributes = [key for key in self.__slots__ if not key.endswith('_mark') and
# hasattr('self', key)]
attributes = [key for key in self.__slots__ if not key.endswith('_mark')]
attributes.sort()
# arguments = ', '.join(
# [f'{key!s}={getattr(self, key)!r})' for key in attributes]
# )
arguments = [f'{key!s}={getattr(self, key)!r}' for key in attributes]
if SHOW_LINES:
try:
arguments.append('line: ' + str(self.start_mark.line))
except: # NOQA
pass
try:
arguments.append('comment: ' + str(self._comment))
except: # NOQA
pass
return f'{self.__class__.__name__}({", ".join(arguments)})'
@property
def column(self) -> int:
return self.start_mark.column
@column.setter
def column(self, pos: Any) -> None:
self.start_mark.column = pos
# old style ( <= 0.17) is a TWO element list with first being the EOL
# comment concatenated with following FLC/BLNK; and second being a list of FLC/BLNK
# preceding the token
# new style ( >= 0.17 ) is a THREE element list with the first being a list of
# preceding FLC/BLNK, the second EOL and the third following FLC/BLNK
# note that new style has differing order, and does not consist of CommentToken(s)
# but of CommentInfo instances
# any non-assigned values in new style are None, but first and last can be empty list
# new style routines add one comment at a time
# going to be deprecated in favour of add_comment_eol/post
def add_post_comment(self, comment: Any) -> None:
if not hasattr(self, '_comment'):
self._comment = [None, None]
else:
assert len(self._comment) in [2, 5] # make sure it is version 0
# if isinstance(comment, CommentToken):
# if comment.value.startswith('# C09'):
# raise
self._comment[0] = comment
# going to be deprecated in favour of add_comment_pre
def add_pre_comments(self, comments: Any) -> None:
if not hasattr(self, '_comment'):
self._comment = [None, None]
else:
assert len(self._comment) == 2 # make sure it is version 0
assert self._comment[1] is None
self._comment[1] = comments
return
# new style
def add_comment_pre(self, comment: Any) -> None:
if not hasattr(self, '_comment'):
self._comment = [[], None, None] # type: ignore
else:
assert len(self._comment) == 3
if self._comment[0] is None:
self._comment[0] = [] # type: ignore
self._comment[0].append(comment) # type: ignore
def add_comment_eol(self, comment: Any, comment_type: Any) -> None:
if not hasattr(self, '_comment'):
self._comment = [None, None, None]
else:
assert len(self._comment) == 3
assert self._comment[1] is None
if self.comment[1] is None:
self._comment[1] = [] # type: ignore
self._comment[1].extend([None] * (comment_type + 1 - len(self.comment[1]))) # type: ignore # NOQA
# nprintf('commy', self.comment, comment_type)
self._comment[1][comment_type] = comment # type: ignore
def add_comment_post(self, comment: Any) -> None:
if not hasattr(self, '_comment'):
self._comment = [None, None, []] # type: ignore
else:
assert len(self._comment) == 3
if self._comment[2] is None:
self._comment[2] = [] # type: ignore
self._comment[2].append(comment) # type: ignore
# def get_comment(self) -> Any:
# return getattr(self, '_comment', None)
@property
def comment(self) -> Any:
return getattr(self, '_comment', None)
def move_old_comment(self, target: Any, empty: bool = False) -> Any:
"""move a comment from this token to target (normally next token)
used to combine e.g. comments before a BlockEntryToken to the
ScalarToken that follows it
empty is a special for empty values -> comment after key
"""
c = self.comment
if c is None:
return
# don't push beyond last element
if isinstance(target, (StreamEndToken, DocumentStartToken)):
return
delattr(self, '_comment')
tc = target.comment
if not tc: # target comment, just insert
# special for empty value in key: value issue 25
if empty:
c = [c[0], c[1], None, None, c[0]]
target._comment = c
# nprint('mco2:', self, target, target.comment, empty)
return self
if c[0] and tc[0] or c[1] and tc[1]:
if isinstance(c[1], list) and isinstance(tc[1], list):
c[1].extend(tc[1])
else:
raise NotImplementedError(f'overlap in comment {c!r} {tc!r}')
if c[0]:
tc[0] = c[0]
if c[1]:
tc[1] = c[1]
return self
def split_old_comment(self) -> Any:
""" split the post part of a comment, and return it
as comment to be added. Delete second part if [None, None]
abc: # this goes to sequence
# this goes to first element
- first element
"""
comment = self.comment
if comment is None or comment[0] is None:
return None # nothing to do
ret_val = [comment[0], None]
if comment[1] is None:
delattr(self, '_comment')
return ret_val
def move_new_comment(self, target: Any, empty: bool = False) -> Any:
"""move a comment from this token to target (normally next token)
used to combine e.g. comments before a BlockEntryToken to the
ScalarToken that follows it
empty is a special for empty values -> comment after key
"""
c = self.comment
if c is None:
return
# don't push beyond last element
if isinstance(target, (StreamEndToken, DocumentStartToken)):
return
delattr(self, '_comment')
tc = target.comment
if not tc: # target comment, just insert
# special for empty value in key: value issue 25
if empty:
c = [c[0], c[1], c[2]]
target._comment = c
# nprint('mco2:', self, target, target.comment, empty)
return self
# if self and target have both pre, eol or post comments, something seems wrong
for idx in range(3):
if c[idx] is not None and tc[idx] is not None:
raise NotImplementedError(f'overlap in comment {c!r} {tc!r}')
# move the comment parts
for idx in range(3):
if c[idx]:
tc[idx] = c[idx]
return self
# class BOMToken(Token):
# id = '<byte order mark>'
class DirectiveToken(Token):
__slots__ = 'name', 'value'
id = '<directive>'
def __init__(self, name: Any, value: Any, start_mark: Any, end_mark: Any) -> None:
Token.__init__(self, start_mark, end_mark)
self.name = name
self.value = value
class DocumentStartToken(Token):
__slots__ = ()
id = '<document start>'
class DocumentEndToken(Token):
__slots__ = ()
id = '<document end>'
class StreamStartToken(Token):
__slots__ = ('encoding',)
id = '<stream start>'
def __init__(
self, start_mark: Any = None, end_mark: Any = None, encoding: Any = None,
) -> None:
Token.__init__(self, start_mark, end_mark)
self.encoding = encoding
class StreamEndToken(Token):
__slots__ = ()
id = '<stream end>'
class BlockSequenceStartToken(Token):
__slots__ = ()
id = '<block sequence start>'
class BlockMappingStartToken(Token):
__slots__ = ()
id = '<block mapping start>'
class BlockEndToken(Token):
__slots__ = ()
id = '<block end>'
class FlowSequenceStartToken(Token):
__slots__ = ()
id = '['
class FlowMappingStartToken(Token):
__slots__ = ()
id = '{'
class FlowSequenceEndToken(Token):
__slots__ = ()
id = ']'
class FlowMappingEndToken(Token):
__slots__ = ()
id = '}'
class KeyToken(Token):
__slots__ = ()
id = '?'
# def x__repr__(self):
# return f'KeyToken({self.start_mark.buffer[self.start_mark.index:].split(None, 1)[0]})'
class ValueToken(Token):
__slots__ = ()
id = ':'
class BlockEntryToken(Token):
__slots__ = ()
id = '-'
class FlowEntryToken(Token):
__slots__ = ()
id = ','
class AliasToken(Token):
__slots__ = ('value',)
id = '<alias>'
def __init__(self, value: Any, start_mark: Any, end_mark: Any) -> None:
Token.__init__(self, start_mark, end_mark)
self.value = value
class AnchorToken(Token):
__slots__ = ('value',)
id = '<anchor>'
def __init__(self, value: Any, start_mark: Any, end_mark: Any) -> None:
Token.__init__(self, start_mark, end_mark)
self.value = value
class TagToken(Token):
__slots__ = ('value',)
id = '<tag>'
def __init__(self, value: Any, start_mark: Any, end_mark: Any) -> None:
Token.__init__(self, start_mark, end_mark)
self.value = value
class ScalarToken(Token):
__slots__ = 'value', 'plain', 'style'
id = '<scalar>'
def __init__(
self, value: Any, plain: Any, start_mark: Any, end_mark: Any, style: Any = None,
) -> None:
Token.__init__(self, start_mark, end_mark)
self.value = value
self.plain = plain
self.style = style
class CommentToken(Token):
__slots__ = '_value', '_column', 'pre_done'
id = '<comment>'
def __init__(
self, value: Any, start_mark: Any = None, end_mark: Any = None, column: Any = None,
) -> None:
if start_mark is None:
assert column is not None
self._column = column
Token.__init__(self, start_mark, end_mark)
self._value = value
@property
def value(self) -> str:
if isinstance(self._value, str):
return self._value
return "".join(self._value)
@value.setter
def value(self, val: Any) -> None:
self._value = val
def reset(self) -> None:
if hasattr(self, 'pre_done'):
delattr(self, 'pre_done')
def __repr__(self) -> Any:
v = f'{self.value!r}'
if SHOW_LINES:
try:
v += ', line: ' + str(self.start_mark.line)
except: # NOQA
pass
try:
v += ', col: ' + str(self.start_mark.column)
except: # NOQA
pass
return f'CommentToken({v})'
def __eq__(self, other: Any) -> bool:
if self.start_mark != other.start_mark:
return False
if self.end_mark != other.end_mark:
return False
if self.value != other.value:
return False
return True
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)

View File

@@ -0,0 +1,264 @@
"""
some helper functions that might be generally useful
"""
from __future__ import annotations
import datetime
from functools import partial
import re
if False: # MYPY
from typing import Any, Dict, Optional, List, Text, Callable, Union # NOQA
from .compat import StreamTextType # NOQA
class LazyEval:
"""
Lightweight wrapper around lazily evaluated func(*args, **kwargs).
func is only evaluated when any attribute of its return value is accessed.
Every attribute access is passed through to the wrapped value.
(This only excludes special cases like method-wrappers, e.g., __hash__.)
The sole additional attribute is the lazy_self function which holds the
return value (or, prior to evaluation, func and arguments), in its closure.
"""
def __init__(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> None:
def lazy_self() -> Any:
return_value = func(*args, **kwargs)
object.__setattr__(self, 'lazy_self', lambda: return_value)
return return_value
object.__setattr__(self, 'lazy_self', lazy_self)
def __getattribute__(self, name: str) -> Any:
lazy_self = object.__getattribute__(self, 'lazy_self')
if name == 'lazy_self':
return lazy_self
return getattr(lazy_self(), name)
def __setattr__(self, name: str, value: Any) -> None:
setattr(self.lazy_self(), name, value)
RegExp = partial(LazyEval, re.compile)
timestamp_regexp = RegExp(
"""^(?P<year>[0-9][0-9][0-9][0-9])
-(?P<month>[0-9][0-9]?)
-(?P<day>[0-9][0-9]?)
(?:((?P<t>[Tt])|[ \\t]+) # explictly not retaining extra spaces
(?P<hour>[0-9][0-9]?)
:(?P<minute>[0-9][0-9])
:(?P<second>[0-9][0-9])
(?:\\.(?P<fraction>[0-9]*))?
(?:[ \\t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
(?::(?P<tz_minute>[0-9][0-9]))?))?)?$""",
re.X,
)
def create_timestamp(
year: Any,
month: Any,
day: Any,
t: Any,
hour: Any,
minute: Any,
second: Any,
fraction: Any,
tz: Any,
tz_sign: Any,
tz_hour: Any,
tz_minute: Any,
) -> Union[datetime.datetime, datetime.date]:
# create a timestamp from matching against timestamp_regexp
MAX_FRAC = 999999
year = int(year)
month = int(month)
day = int(day)
if hour is None:
return datetime.date(year, month, day)
hour = int(hour)
minute = int(minute)
second = int(second)
frac = 0
if fraction:
frac_s = fraction[:6]
while len(frac_s) < 6:
frac_s += '0'
frac = int(frac_s)
if len(fraction) > 6 and int(fraction[6]) > 4:
frac += 1
if frac > MAX_FRAC:
fraction = 0
else:
fraction = frac
else:
fraction = 0
tzinfo = None
delta = None
if tz_sign:
tz_hour = int(tz_hour)
tz_minute = int(tz_minute) if tz_minute else 0
td = datetime.timedelta(
hours=tz_hour, minutes=tz_minute,
)
if tz_sign == '-':
td = -td
tzinfo = datetime.timezone(td, name=tz)
elif tz == 'Z':
tzinfo = datetime.timezone(datetime.timedelta(hours=0), name=tz)
if frac > MAX_FRAC:
delta = -datetime.timedelta(seconds=1)
# should do something else instead (or hook this up to the preceding if statement
# in reverse
# if delta is None:
# return datetime.datetime(year, month, day, hour, minute, second, fraction)
# return datetime.datetime(year, month, day, hour, minute, second, fraction,
# datetime.timezone.utc)
# the above is not good enough though, should provide tzinfo. In Python3 that is easily
# doable drop that kind of support for Python2 as it has not native tzinfo
data = datetime.datetime(year, month, day, hour, minute, second, fraction, tzinfo)
if delta:
data -= delta
return data
# originally as comment
# https://github.com/pre-commit/pre-commit/pull/211#issuecomment-186466605
# if you use this in your code, I suggest adding a test in your test suite
# that check this routines output against a known piece of your YAML
# before upgrades to this code break your round-tripped YAML
def load_yaml_guess_indent(stream: StreamTextType, **kw: Any) -> Any:
"""guess the indent and block sequence indent of yaml stream/string
returns round_trip_loaded stream, indent level, block sequence indent
- block sequence indent is the number of spaces before a dash relative to previous indent
- if there are no block sequences, indent is taken from nested mappings, block sequence
indent is unset (None) in that case
"""
from .main import YAML
# load a YAML document, guess the indentation, if you use TABs you are on your own
def leading_spaces(line: Any) -> int:
idx = 0
while idx < len(line) and line[idx] == ' ':
idx += 1
return idx
if isinstance(stream, str):
yaml_str: Any = stream
elif isinstance(stream, bytes):
# most likely, but the Reader checks BOM for this
yaml_str = stream.decode('utf-8')
else:
yaml_str = stream.read()
map_indent = None
indent = None # default if not found for some reason
block_seq_indent = None
prev_line_key_only = None
key_indent = 0
for line in yaml_str.splitlines():
rline = line.rstrip()
lline = rline.lstrip()
if lline.startswith('- '):
l_s = leading_spaces(line)
block_seq_indent = l_s - key_indent
idx = l_s + 1
while line[idx] == ' ': # this will end as we rstripped
idx += 1
if line[idx] == '#': # comment after -
continue
indent = idx - key_indent
break
if map_indent is None and prev_line_key_only is not None and rline:
idx = 0
while line[idx] in ' -':
idx += 1
if idx > prev_line_key_only:
map_indent = idx - prev_line_key_only
if rline.endswith(':'):
key_indent = leading_spaces(line)
idx = 0
while line[idx] == ' ': # this will end on ':'
idx += 1
prev_line_key_only = idx
continue
prev_line_key_only = None
if indent is None and map_indent is not None:
indent = map_indent
yaml = YAML() if 'yaml' not in kw else kw.pop('yaml')
return yaml.load(yaml_str, **kw), indent, block_seq_indent
def configobj_walker(cfg: Any) -> Any:
"""
walks over a ConfigObj (INI file with comments) generating
corresponding YAML output (including comments
"""
from configobj import ConfigObj # type: ignore
assert isinstance(cfg, ConfigObj)
for c in cfg.initial_comment:
if c.strip():
yield c
for s in _walk_section(cfg):
if s.strip():
yield s
for c in cfg.final_comment:
if c.strip():
yield c
def _walk_section(s: Any, level: int = 0) -> Any:
from configobj import Section
assert isinstance(s, Section)
indent = ' ' * level
for name in s.scalars:
for c in s.comments[name]:
yield indent + c.strip()
x = s[name]
if '\n' in x:
i = indent + ' '
x = '|\n' + i + x.strip().replace('\n', '\n' + i)
elif ':' in x:
x = "'" + x.replace("'", "''") + "'"
line = f'{indent}{name}: {x}'
c = s.inline_comments[name]
if c:
line += ' ' + c
yield line
for name in s.sections:
for c in s.comments[name]:
yield indent + c.strip()
line = f'{indent}{name}:'
c = s.inline_comments[name]
if c:
line += ' ' + c
yield line
for val in _walk_section(s[name], level=level + 1):
yield val
# def config_obj_2_rt_yaml(cfg):
# from .comments import CommentedMap, CommentedSeq
# from configobj import ConfigObj
# assert isinstance(cfg, ConfigObj)
# #for c in cfg.initial_comment:
# # if c.strip():
# # pass
# cm = CommentedMap()
# for name in s.sections:
# cm[name] = d = CommentedMap()
#
#
# #for c in cfg.final_comment:
# # if c.strip():
# # yield c
# return cm