refactor dependencies and installation
There are hard copies of the graphviz and pyyaml dependencies included in the repo. Remove these. Sort out installation and dependency handling by writing a functional setup.py script. Rename top level documentation. Refactor wireviz.py slightly to allow it to run as an installed script. # Conflicts: # src/batch.py # src/wireviz/batch.py # src/wireviz/build_examples.py
This commit is contained in:
parent
dfb184c1a4
commit
5514fd4244
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,2 +1,9 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
temp/
|
.eggs
|
||||||
|
__pycache__
|
||||||
|
.*.swp
|
||||||
|
*.egg-info
|
||||||
|
*.pyc
|
||||||
|
build
|
||||||
|
data
|
||||||
|
dist
|
||||||
|
|||||||
@ -94,7 +94,7 @@ mywire.html HTML page with wiring diagram and BOM embedded
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
This is very much a [work in progress](todo.md). Source code, API, syntax and functionality may change wildly at any time.
|
This is very much a [work in progress](TODO). Source code, API, syntax and functionality may change wildly at any time.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
.
|
||||||
45
setup.py
Normal file
45
setup.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
project_name = 'wireviz'
|
||||||
|
|
||||||
|
# Utility function to read the README file.
|
||||||
|
# Used for the long_description. It's nice, because now 1) we have a top level
|
||||||
|
# README file and 2) it's easier to type in the README file than to put a raw
|
||||||
|
# string in below ...
|
||||||
|
def read(fname):
|
||||||
|
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=project_name,
|
||||||
|
version='0.1',
|
||||||
|
author='Daniel Rojas',
|
||||||
|
#author_email='',
|
||||||
|
description='Easily document cables and wiring harnesses',
|
||||||
|
long_description=read(os.path.join(os.path.dirname(__file__), 'README.md')),
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
install_requires=[
|
||||||
|
'pyyaml',
|
||||||
|
'graphviz',
|
||||||
|
],
|
||||||
|
license='GPLv3',
|
||||||
|
keywords='cable connector hardware harness wiring wiring-diagram wiring-harness',
|
||||||
|
url='https://github.com/n42/WireViz',
|
||||||
|
package_dir={'': 'src'},
|
||||||
|
packages=find_packages('src'),
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': ['wireviz=wireviz.wireviz:main'],
|
||||||
|
},
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Environment :: Console',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Topic :: Utilities',
|
||||||
|
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||||
|
],
|
||||||
|
|
||||||
|
)
|
||||||
@ -1,63 +0,0 @@
|
|||||||
# graphviz - create dot, save, render, view
|
|
||||||
|
|
||||||
"""Assemble DOT source code and render it with Graphviz.
|
|
||||||
|
|
||||||
>>> dot = Digraph(comment='The Round Table')
|
|
||||||
|
|
||||||
>>> dot.node('A', 'King Arthur')
|
|
||||||
>>> dot.node('B', 'Sir Bedevere the Wise')
|
|
||||||
>>> dot.node('L', 'Sir Lancelot the Brave')
|
|
||||||
|
|
||||||
>>> dot.edges(['AB', 'AL'])
|
|
||||||
|
|
||||||
>>> dot.edge('B', 'L', constraint='false')
|
|
||||||
|
|
||||||
>>> print(dot) #doctest: +NORMALIZE_WHITESPACE
|
|
||||||
// The Round Table
|
|
||||||
digraph {
|
|
||||||
A [label="King Arthur"]
|
|
||||||
B [label="Sir Bedevere the Wise"]
|
|
||||||
L [label="Sir Lancelot the Brave"]
|
|
||||||
A -> B
|
|
||||||
A -> L
|
|
||||||
B -> L [constraint=false]
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .dot import Graph, Digraph
|
|
||||||
from .files import Source
|
|
||||||
from .lang import escape, nohtml
|
|
||||||
from .backend import (render, pipe, version, view,
|
|
||||||
ENGINES, FORMATS, RENDERERS, FORMATTERS,
|
|
||||||
ExecutableNotFound, RequiredArgumentError)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'Graph', 'Digraph',
|
|
||||||
'Source',
|
|
||||||
'escape', 'nohtml',
|
|
||||||
'render', 'pipe', 'version', 'view',
|
|
||||||
'ENGINES', 'FORMATS', 'RENDERERS', 'FORMATTERS',
|
|
||||||
'ExecutableNotFound', 'RequiredArgumentError',
|
|
||||||
]
|
|
||||||
|
|
||||||
__title__ = 'graphviz'
|
|
||||||
__version__ = '0.14.1.dev0'
|
|
||||||
__author__ = 'Sebastian Bank <sebastian.bank@uni-leipzig.de>'
|
|
||||||
__license__ = 'MIT, see LICENSE.txt'
|
|
||||||
__copyright__ = 'Copyright (c) 2013-2020 Sebastian Bank'
|
|
||||||
|
|
||||||
#: Set of known layout commands used for rendering (``'dot'``, ``'neato'``, ...)
|
|
||||||
ENGINES = ENGINES
|
|
||||||
|
|
||||||
#: Set of known output formats for rendering (``'pdf'``, ``'png'``, ...)
|
|
||||||
FORMATS = FORMATS
|
|
||||||
|
|
||||||
#: Set of known output formatters for rendering (``'cairo'``, ``'gd'``, ...)
|
|
||||||
FORMATTERS = FORMATTERS
|
|
||||||
|
|
||||||
#: Set of known output renderers for rendering (``'cairo'``, ``'gd'``, ...)
|
|
||||||
RENDERERS = RENDERERS
|
|
||||||
|
|
||||||
ExecutableNotFound = ExecutableNotFound
|
|
||||||
|
|
||||||
RequiredArgumentError = RequiredArgumentError
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
# _compat.py - Python 2/3 compatibility
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import operator
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
PY2 = (sys.version_info.major == 2)
|
|
||||||
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
string_classes = (str, unicode) # needed individually for sublassing
|
|
||||||
text_type = unicode
|
|
||||||
|
|
||||||
iteritems = operator.methodcaller('iteritems')
|
|
||||||
|
|
||||||
def makedirs(name, mode=0o777, exist_ok=False):
|
|
||||||
try:
|
|
||||||
os.makedirs(name, mode)
|
|
||||||
except OSError:
|
|
||||||
if not exist_ok or not os.path.isdir(name):
|
|
||||||
raise
|
|
||||||
|
|
||||||
def stderr_write_bytes(data, flush=False):
|
|
||||||
"""Write data str to sys.stderr (flush if requested)."""
|
|
||||||
sys.stderr.write(data)
|
|
||||||
if flush:
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
def Popen_stderr_devnull(*args, **kwargs): # noqa: N802
|
|
||||||
with open(os.devnull, 'w') as f:
|
|
||||||
return subprocess.Popen(*args, stderr=f, **kwargs)
|
|
||||||
|
|
||||||
class CalledProcessError(subprocess.CalledProcessError):
|
|
||||||
|
|
||||||
def __init__(self, returncode, cmd, output=None, stderr=None):
|
|
||||||
super(CalledProcessError, self).__init__(returncode, cmd, output)
|
|
||||||
self.stderr = stderr
|
|
||||||
|
|
||||||
@property # pragma: no cover
|
|
||||||
def stdout(self):
|
|
||||||
return self.output
|
|
||||||
|
|
||||||
@stdout.setter # pragma: no cover
|
|
||||||
def stdout(self, value):
|
|
||||||
self.output = value
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
string_classes = (str,)
|
|
||||||
text_type = str
|
|
||||||
|
|
||||||
def iteritems(d):
|
|
||||||
return iter(d.items())
|
|
||||||
|
|
||||||
def makedirs(name, mode=0o777, exist_ok=False): # allow os.makedirs mocking
|
|
||||||
return os.makedirs(name, mode, exist_ok=exist_ok)
|
|
||||||
|
|
||||||
def stderr_write_bytes(data, flush=False):
|
|
||||||
"""Encode data str and write to sys.stderr (flush if requested)."""
|
|
||||||
encoding = sys.stderr.encoding or sys.getdefaultencoding()
|
|
||||||
sys.stderr.write(data.decode(encoding))
|
|
||||||
if flush:
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
def Popen_stderr_devnull(*args, **kwargs): # noqa: N802
|
|
||||||
return subprocess.Popen(*args, stderr=subprocess.DEVNULL, **kwargs)
|
|
||||||
|
|
||||||
CalledProcessError = subprocess.CalledProcessError
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
# backend.py - execute rendering, open files in viewer
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import errno
|
|
||||||
import logging
|
|
||||||
import platform
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from . import _compat
|
|
||||||
|
|
||||||
from . import tools
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'render', 'pipe', 'version', 'view',
|
|
||||||
'ENGINES', 'FORMATS', 'RENDERERS', 'FORMATTERS',
|
|
||||||
'ExecutableNotFound', 'RequiredArgumentError',
|
|
||||||
]
|
|
||||||
|
|
||||||
ENGINES = { # http://www.graphviz.org/pdf/dot.1.pdf
|
|
||||||
'dot', 'neato', 'twopi', 'circo', 'fdp', 'sfdp', 'patchwork', 'osage',
|
|
||||||
}
|
|
||||||
|
|
||||||
FORMATS = { # http://www.graphviz.org/doc/info/output.html
|
|
||||||
'bmp',
|
|
||||||
'canon', 'dot', 'gv', 'xdot', 'xdot1.2', 'xdot1.4',
|
|
||||||
'cgimage',
|
|
||||||
'cmap',
|
|
||||||
'eps',
|
|
||||||
'exr',
|
|
||||||
'fig',
|
|
||||||
'gd', 'gd2',
|
|
||||||
'gif',
|
|
||||||
'gtk',
|
|
||||||
'ico',
|
|
||||||
'imap', 'cmapx',
|
|
||||||
'imap_np', 'cmapx_np',
|
|
||||||
'ismap',
|
|
||||||
'jp2',
|
|
||||||
'jpg', 'jpeg', 'jpe',
|
|
||||||
'json', 'json0', 'dot_json', 'xdot_json', # Graphviz 2.40
|
|
||||||
'pct', 'pict',
|
|
||||||
'pdf',
|
|
||||||
'pic',
|
|
||||||
'plain', 'plain-ext',
|
|
||||||
'png',
|
|
||||||
'pov',
|
|
||||||
'ps',
|
|
||||||
'ps2',
|
|
||||||
'psd',
|
|
||||||
'sgi',
|
|
||||||
'svg', 'svgz',
|
|
||||||
'tga',
|
|
||||||
'tif', 'tiff',
|
|
||||||
'tk',
|
|
||||||
'vml', 'vmlz',
|
|
||||||
'vrml',
|
|
||||||
'wbmp',
|
|
||||||
'webp',
|
|
||||||
'xlib',
|
|
||||||
'x11',
|
|
||||||
}
|
|
||||||
|
|
||||||
RENDERERS = { # $ dot -T:
|
|
||||||
'cairo',
|
|
||||||
'dot',
|
|
||||||
'fig',
|
|
||||||
'gd',
|
|
||||||
'gdiplus',
|
|
||||||
'map',
|
|
||||||
'pic',
|
|
||||||
'pov',
|
|
||||||
'ps',
|
|
||||||
'svg',
|
|
||||||
'tk',
|
|
||||||
'vml',
|
|
||||||
'vrml',
|
|
||||||
'xdot',
|
|
||||||
}
|
|
||||||
|
|
||||||
FORMATTERS = {'cairo', 'core', 'gd', 'gdiplus', 'gdwbmp', 'xlib'}
|
|
||||||
|
|
||||||
PLATFORM = platform.system().lower()
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ExecutableNotFound(RuntimeError):
|
|
||||||
"""Exception raised if the Graphviz executable is not found."""
|
|
||||||
|
|
||||||
_msg = ('failed to execute %r, '
|
|
||||||
'make sure the Graphviz executables are on your systems\' PATH')
|
|
||||||
|
|
||||||
def __init__(self, args):
|
|
||||||
super(ExecutableNotFound, self).__init__(self._msg % args)
|
|
||||||
|
|
||||||
|
|
||||||
class RequiredArgumentError(Exception):
|
|
||||||
"""Exception raised if a required argument is missing."""
|
|
||||||
|
|
||||||
|
|
||||||
class CalledProcessError(_compat.CalledProcessError):
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
s = super(CalledProcessError, self).__str__()
|
|
||||||
return '%s [stderr: %r]' % (s, self.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def command(engine, format_, filepath=None, renderer=None, formatter=None):
|
|
||||||
"""Return args list for ``subprocess.Popen`` and name of the rendered file."""
|
|
||||||
if formatter is not None and renderer is None:
|
|
||||||
raise RequiredArgumentError('formatter given without renderer')
|
|
||||||
|
|
||||||
if engine not in ENGINES:
|
|
||||||
raise ValueError('unknown engine: %r' % engine)
|
|
||||||
if format_ not in FORMATS:
|
|
||||||
raise ValueError('unknown format: %r' % format_)
|
|
||||||
if renderer is not None and renderer not in RENDERERS:
|
|
||||||
raise ValueError('unknown renderer: %r' % renderer)
|
|
||||||
if formatter is not None and formatter not in FORMATTERS:
|
|
||||||
raise ValueError('unknown formatter: %r' % formatter)
|
|
||||||
|
|
||||||
output_format = [f for f in (format_, renderer, formatter) if f is not None]
|
|
||||||
cmd = [engine, '-T%s' % ':'.join(output_format)]
|
|
||||||
|
|
||||||
if filepath is None:
|
|
||||||
rendered = None
|
|
||||||
else:
|
|
||||||
cmd.extend(['-O', filepath])
|
|
||||||
suffix = '.'.join(reversed(output_format))
|
|
||||||
rendered = '%s.%s' % (filepath, suffix)
|
|
||||||
|
|
||||||
return cmd, rendered
|
|
||||||
|
|
||||||
|
|
||||||
if PLATFORM == 'windows': # pragma: no cover
|
|
||||||
def get_startupinfo():
|
|
||||||
"""Return subprocess.STARTUPINFO instance hiding the console window."""
|
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
|
||||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
||||||
startupinfo.wShowWindow = subprocess.SW_HIDE
|
|
||||||
return startupinfo
|
|
||||||
else:
|
|
||||||
def get_startupinfo():
|
|
||||||
"""Return None for startupinfo argument of ``subprocess.Popen``."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def run(cmd, input=None, capture_output=False, check=False, encoding=None,
|
|
||||||
quiet=False, **kwargs):
|
|
||||||
"""Run the command described by cmd and return its (stdout, stderr) tuple."""
|
|
||||||
log.debug('run %r', cmd)
|
|
||||||
|
|
||||||
if input is not None:
|
|
||||||
kwargs['stdin'] = subprocess.PIPE
|
|
||||||
if encoding is not None:
|
|
||||||
input = input.encode(encoding)
|
|
||||||
|
|
||||||
if capture_output:
|
|
||||||
kwargs['stdout'] = kwargs['stderr'] = subprocess.PIPE
|
|
||||||
|
|
||||||
try:
|
|
||||||
proc = subprocess.Popen(cmd, startupinfo=get_startupinfo(), **kwargs)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == errno.ENOENT:
|
|
||||||
raise ExecutableNotFound(cmd)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
out, err = proc.communicate(input)
|
|
||||||
|
|
||||||
if not quiet and err:
|
|
||||||
_compat.stderr_write_bytes(err, flush=True)
|
|
||||||
|
|
||||||
if encoding is not None:
|
|
||||||
if out is not None:
|
|
||||||
out = out.decode(encoding)
|
|
||||||
if err is not None:
|
|
||||||
err = err.decode(encoding)
|
|
||||||
|
|
||||||
if check and proc.returncode:
|
|
||||||
raise CalledProcessError(proc.returncode, cmd,
|
|
||||||
output=out, stderr=err)
|
|
||||||
|
|
||||||
return out, err
|
|
||||||
|
|
||||||
|
|
||||||
def render(engine, format, filepath, renderer=None, formatter=None, quiet=False):
|
|
||||||
"""Render file with Graphviz ``engine`` into ``format``, return result filename.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
engine: The layout commmand used for rendering (``'dot'``, ``'neato'``, ...).
|
|
||||||
format: The output format used for rendering (``'pdf'``, ``'png'``, ...).
|
|
||||||
filepath: Path to the DOT source file to render.
|
|
||||||
renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
quiet (bool): Suppress ``stderr`` output from the layout subprocess.
|
|
||||||
Returns:
|
|
||||||
The (possibly relative) path of the rendered file.
|
|
||||||
Raises:
|
|
||||||
ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are not known.
|
|
||||||
graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None.
|
|
||||||
graphviz.ExecutableNotFound: If the Graphviz executable is not found.
|
|
||||||
subprocess.CalledProcessError: If the exit status is non-zero.
|
|
||||||
|
|
||||||
The layout command is started from the directory of ``filepath``, so that
|
|
||||||
references to external files (e.g. ``[image=...]``) can be given as paths
|
|
||||||
relative to the DOT source file.
|
|
||||||
"""
|
|
||||||
dirname, filename = os.path.split(filepath)
|
|
||||||
del filepath
|
|
||||||
|
|
||||||
cmd, rendered = command(engine, format, filename, renderer, formatter)
|
|
||||||
if dirname:
|
|
||||||
cwd = dirname
|
|
||||||
rendered = os.path.join(dirname, rendered)
|
|
||||||
else:
|
|
||||||
cwd = None
|
|
||||||
|
|
||||||
run(cmd, capture_output=True, cwd=cwd, check=True, quiet=quiet)
|
|
||||||
return rendered
|
|
||||||
|
|
||||||
|
|
||||||
def pipe(engine, format, data, renderer=None, formatter=None, quiet=False):
|
|
||||||
"""Return ``data`` piped through Graphviz ``engine`` into ``format``.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
engine: The layout commmand used for rendering (``'dot'``, ``'neato'``, ...).
|
|
||||||
format: The output format used for rendering (``'pdf'``, ``'png'``, ...).
|
|
||||||
data: The binary (encoded) DOT source string to render.
|
|
||||||
renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
quiet (bool): Suppress ``stderr`` output from the layout subprocess.
|
|
||||||
Returns:
|
|
||||||
Binary (encoded) stdout of the layout command.
|
|
||||||
Raises:
|
|
||||||
ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are not known.
|
|
||||||
graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None.
|
|
||||||
graphviz.ExecutableNotFound: If the Graphviz executable is not found.
|
|
||||||
subprocess.CalledProcessError: If the exit status is non-zero.
|
|
||||||
"""
|
|
||||||
cmd, _ = command(engine, format, None, renderer, formatter)
|
|
||||||
out, _ = run(cmd, input=data, capture_output=True, check=True, quiet=quiet)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def version():
|
|
||||||
"""Return the version number tuple from the ``stderr`` output of ``dot -V``.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Two, three, or four ``int`` version ``tuple``.
|
|
||||||
Raises:
|
|
||||||
graphviz.ExecutableNotFound: If the Graphviz executable is not found.
|
|
||||||
subprocess.CalledProcessError: If the exit status is non-zero.
|
|
||||||
RuntimmeError: If the output cannot be parsed into a version number.
|
|
||||||
"""
|
|
||||||
cmd = ['dot', '-V']
|
|
||||||
out, _ = run(cmd, check=True, encoding='ascii',
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT)
|
|
||||||
|
|
||||||
ma = re.search(r'graphviz version (\d+\.\d+(?:\.\d+){,2}) ', out)
|
|
||||||
if ma is None:
|
|
||||||
raise RuntimeError('cannot parse %r output: %r' % (cmd, out))
|
|
||||||
|
|
||||||
return tuple(int(d) for d in ma.group(1).split('.'))
|
|
||||||
|
|
||||||
|
|
||||||
def view(filepath, quiet=False):
|
|
||||||
"""Open filepath with its default viewing application (platform-specific).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filepath: Path to the file to open in viewer.
|
|
||||||
quiet (bool): Suppress ``stderr`` output from the viewer process
|
|
||||||
(ineffective on Windows).
|
|
||||||
Raises:
|
|
||||||
RuntimeError: If the current platform is not supported.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
view_func = getattr(view, PLATFORM)
|
|
||||||
except AttributeError:
|
|
||||||
raise RuntimeError('platform %r not supported' % PLATFORM)
|
|
||||||
view_func(filepath, quiet)
|
|
||||||
|
|
||||||
|
|
||||||
@tools.attach(view, 'darwin')
|
|
||||||
def view_darwin(filepath, quiet):
|
|
||||||
"""Open filepath with its default application (mac)."""
|
|
||||||
cmd = ['open', filepath]
|
|
||||||
log.debug('view: %r', cmd)
|
|
||||||
popen_func = _compat.Popen_stderr_devnull if quiet else subprocess.Popen
|
|
||||||
popen_func(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
@tools.attach(view, 'linux')
|
|
||||||
@tools.attach(view, 'freebsd')
|
|
||||||
def view_unixoid(filepath, quiet):
|
|
||||||
"""Open filepath in the user's preferred application (linux, freebsd)."""
|
|
||||||
cmd = ['xdg-open', filepath]
|
|
||||||
log.debug('view: %r', cmd)
|
|
||||||
popen_func = _compat.Popen_stderr_devnull if quiet else subprocess.Popen
|
|
||||||
popen_func(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
@tools.attach(view, 'windows')
|
|
||||||
def view_windows(filepath, quiet):
|
|
||||||
"""Start filepath with its associated application (windows)."""
|
|
||||||
# TODO: implement quiet=True
|
|
||||||
filepath = os.path.normpath(filepath)
|
|
||||||
log.debug('view: %r', filepath)
|
|
||||||
os.startfile(filepath)
|
|
||||||
@ -1,287 +0,0 @@
|
|||||||
# dot.py - create dot code
|
|
||||||
|
|
||||||
r"""Assemble DOT source code objects.
|
|
||||||
|
|
||||||
>>> dot = Graph(comment=u'M\xf8nti Pyth\xf8n ik den H\xf8lie Grailen')
|
|
||||||
|
|
||||||
>>> dot.node(u'M\xf8\xf8se')
|
|
||||||
>>> dot.node('trained_by', u'trained by')
|
|
||||||
>>> dot.node('tutte', u'TUTTE HERMSGERVORDENBROTBORDA')
|
|
||||||
|
|
||||||
>>> dot.edge(u'M\xf8\xf8se', 'trained_by')
|
|
||||||
>>> dot.edge('trained_by', 'tutte')
|
|
||||||
|
|
||||||
>>> dot.node_attr['shape'] = 'rectangle'
|
|
||||||
|
|
||||||
>>> print(dot.source.replace(u'\xf8', '0')) #doctest: +NORMALIZE_WHITESPACE
|
|
||||||
// M0nti Pyth0n ik den H0lie Grailen
|
|
||||||
graph {
|
|
||||||
node [shape=rectangle]
|
|
||||||
"M00se"
|
|
||||||
trained_by [label="trained by"]
|
|
||||||
tutte [label="TUTTE HERMSGERVORDENBROTBORDA"]
|
|
||||||
"M00se" -- trained_by
|
|
||||||
trained_by -- tutte
|
|
||||||
}
|
|
||||||
|
|
||||||
>>> dot.view('test-output/m00se.gv') # doctest: +SKIP
|
|
||||||
'test-output/m00se.gv.pdf'
|
|
||||||
"""
|
|
||||||
|
|
||||||
from . import lang
|
|
||||||
from . import files
|
|
||||||
|
|
||||||
__all__ = ['Graph', 'Digraph']
|
|
||||||
|
|
||||||
|
|
||||||
class Dot(files.File):
|
|
||||||
"""Assemble, save, and render DOT source code, open result in viewer."""
|
|
||||||
|
|
||||||
_comment = '// %s'
|
|
||||||
_subgraph = 'subgraph %s{'
|
|
||||||
_subgraph_plain = '%s{'
|
|
||||||
_node = _attr = '\t%s%s'
|
|
||||||
_attr_plain = _attr % ('%s', '')
|
|
||||||
_tail = '}'
|
|
||||||
|
|
||||||
_quote = staticmethod(lang.quote)
|
|
||||||
_quote_edge = staticmethod(lang.quote_edge)
|
|
||||||
|
|
||||||
_a_list = staticmethod(lang.a_list)
|
|
||||||
_attr_list = staticmethod(lang.attr_list)
|
|
||||||
|
|
||||||
def __init__(self, name=None, comment=None,
|
|
||||||
filename=None, directory=None,
|
|
||||||
format=None, engine=None, encoding=files.ENCODING,
|
|
||||||
graph_attr=None, node_attr=None, edge_attr=None, body=None,
|
|
||||||
strict=False):
|
|
||||||
self.name = name
|
|
||||||
self.comment = comment
|
|
||||||
|
|
||||||
super(Dot, self).__init__(filename, directory, format, engine, encoding)
|
|
||||||
|
|
||||||
self.graph_attr = dict(graph_attr) if graph_attr is not None else {}
|
|
||||||
self.node_attr = dict(node_attr) if node_attr is not None else {}
|
|
||||||
self.edge_attr = dict(edge_attr) if edge_attr is not None else {}
|
|
||||||
|
|
||||||
self.body = list(body) if body is not None else []
|
|
||||||
|
|
||||||
self.strict = strict
|
|
||||||
|
|
||||||
def _kwargs(self):
|
|
||||||
result = super(Dot, self)._kwargs()
|
|
||||||
result.update(name=self.name,
|
|
||||||
comment=self.comment,
|
|
||||||
graph_attr=dict(self.graph_attr),
|
|
||||||
node_attr=dict(self.node_attr),
|
|
||||||
edge_attr=dict(self.edge_attr),
|
|
||||||
body=list(self.body),
|
|
||||||
strict=self.strict)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def clear(self, keep_attrs=False):
|
|
||||||
"""Reset content to an empty body, clear graph/node/egde_attr mappings.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
keep_attrs (bool): preserve graph/node/egde_attr mappings
|
|
||||||
"""
|
|
||||||
if not keep_attrs:
|
|
||||||
for a in (self.graph_attr, self.node_attr, self.edge_attr):
|
|
||||||
a.clear()
|
|
||||||
del self.body[:]
|
|
||||||
|
|
||||||
def __iter__(self, subgraph=False):
|
|
||||||
"""Yield the DOT source code line by line (as graph or subgraph)."""
|
|
||||||
if self.comment:
|
|
||||||
yield self._comment % self.comment
|
|
||||||
|
|
||||||
if subgraph:
|
|
||||||
if self.strict:
|
|
||||||
raise ValueError('subgraphs cannot be strict')
|
|
||||||
head = self._subgraph if self.name else self._subgraph_plain
|
|
||||||
else:
|
|
||||||
head = self._head_strict if self.strict else self._head
|
|
||||||
yield head % (self._quote(self.name) + ' ' if self.name else '')
|
|
||||||
|
|
||||||
for kw in ('graph', 'node', 'edge'):
|
|
||||||
attrs = getattr(self, '%s_attr' % kw)
|
|
||||||
if attrs:
|
|
||||||
yield self._attr % (kw, self._attr_list(None, attrs))
|
|
||||||
|
|
||||||
for line in self.body:
|
|
||||||
yield line
|
|
||||||
|
|
||||||
yield self._tail
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""The DOT source code as string."""
|
|
||||||
return '\n'.join(self)
|
|
||||||
|
|
||||||
source = property(__str__, doc=__str__.__doc__)
|
|
||||||
|
|
||||||
def node(self, name, label=None, _attributes=None, **attrs):
|
|
||||||
"""Create a node.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: Unique identifier for the node inside the source.
|
|
||||||
label: Caption to be displayed (defaults to the node ``name``).
|
|
||||||
attrs: Any additional node attributes (must be strings).
|
|
||||||
"""
|
|
||||||
name = self._quote(name)
|
|
||||||
attr_list = self._attr_list(label, attrs, _attributes)
|
|
||||||
line = self._node % (name, attr_list)
|
|
||||||
self.body.append(line)
|
|
||||||
|
|
||||||
def edge(self, tail_name, head_name, label=None, _attributes=None, **attrs):
|
|
||||||
"""Create an edge between two nodes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tail_name: Start node identifier.
|
|
||||||
head_name: End node identifier.
|
|
||||||
label: Caption to be displayed near the edge.
|
|
||||||
attrs: Any additional edge attributes (must be strings).
|
|
||||||
"""
|
|
||||||
tail_name = self._quote_edge(tail_name)
|
|
||||||
head_name = self._quote_edge(head_name)
|
|
||||||
attr_list = self._attr_list(label, attrs, _attributes)
|
|
||||||
line = self._edge % (tail_name, head_name, attr_list)
|
|
||||||
self.body.append(line)
|
|
||||||
|
|
||||||
def edges(self, tail_head_iter):
|
|
||||||
"""Create a bunch of edges.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tail_head_iter: Iterable of ``(tail_name, head_name)`` pairs.
|
|
||||||
"""
|
|
||||||
edge = self._edge_plain
|
|
||||||
quote = self._quote_edge
|
|
||||||
lines = (edge % (quote(t), quote(h)) for t, h in tail_head_iter)
|
|
||||||
self.body.extend(lines)
|
|
||||||
|
|
||||||
def attr(self, kw=None, _attributes=None, **attrs):
|
|
||||||
"""Add a general or graph/node/edge attribute statement.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
kw: Attributes target (``None`` or ``'graph'``, ``'node'``, ``'edge'``).
|
|
||||||
attrs: Attributes to be set (must be strings, may be empty).
|
|
||||||
|
|
||||||
See the :ref:`usage examples in the User Guide <attributes>`.
|
|
||||||
"""
|
|
||||||
if kw is not None and kw.lower() not in ('graph', 'node', 'edge'):
|
|
||||||
raise ValueError('attr statement must target graph, node, or edge: '
|
|
||||||
'%r' % kw)
|
|
||||||
if attrs or _attributes:
|
|
||||||
if kw is None:
|
|
||||||
a_list = self._a_list(None, attrs, _attributes)
|
|
||||||
line = self._attr_plain % a_list
|
|
||||||
else:
|
|
||||||
attr_list = self._attr_list(None, attrs, _attributes)
|
|
||||||
line = self._attr % (kw, attr_list)
|
|
||||||
self.body.append(line)
|
|
||||||
|
|
||||||
def subgraph(self, graph=None, name=None, comment=None,
|
|
||||||
graph_attr=None, node_attr=None, edge_attr=None, body=None):
|
|
||||||
"""Add the current content of the given sole ``graph`` argument as subgraph \
|
|
||||||
or return a context manager returning a new graph instance created \
|
|
||||||
with the given (``name``, ``comment``, etc.) arguments whose content is \
|
|
||||||
added as subgraph when leaving the context manager's ``with``-block.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
graph: An instance of the same kind (:class:`.Graph`, :class:`.Digraph`)
|
|
||||||
as the current graph (sole argument in non-with-block use).
|
|
||||||
name: Subgraph name (``with``-block use).
|
|
||||||
comment: Subgraph comment (``with``-block use).
|
|
||||||
graph_attr: Subgraph-level attribute-value mapping (``with``-block use).
|
|
||||||
node_attr: Node-level attribute-value mapping (``with``-block use).
|
|
||||||
edge_attr: Edge-level attribute-value mapping (``with``-block use).
|
|
||||||
body: Verbatim lines to add to the subgraph ``body`` (``with``-block use).
|
|
||||||
|
|
||||||
See the :ref:`usage examples in the User Guide <subgraphs>`.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
If the ``name`` of the subgraph begins with ``'cluster'`` (all lowercase)
|
|
||||||
the layout engine will treat it as a special cluster subgraph.
|
|
||||||
"""
|
|
||||||
if graph is None:
|
|
||||||
return SubgraphContext(self, {'name': name,
|
|
||||||
'comment': comment,
|
|
||||||
'graph_attr': graph_attr,
|
|
||||||
'node_attr': node_attr,
|
|
||||||
'edge_attr': edge_attr,
|
|
||||||
'body': body})
|
|
||||||
|
|
||||||
args = [name, comment, graph_attr, node_attr, edge_attr, body]
|
|
||||||
if not all(a is None for a in args):
|
|
||||||
raise ValueError('graph must be sole argument of subgraph()')
|
|
||||||
|
|
||||||
if graph.directed != self.directed:
|
|
||||||
raise ValueError('%r cannot add subgraph of different kind:'
|
|
||||||
' %r' % (self, graph))
|
|
||||||
|
|
||||||
lines = ['\t' + line for line in graph.__iter__(subgraph=True)]
|
|
||||||
self.body.extend(lines)
|
|
||||||
|
|
||||||
|
|
||||||
class SubgraphContext(object):
|
|
||||||
"""Return a blank instance of the parent and add as subgraph on exit."""
|
|
||||||
|
|
||||||
def __init__(self, parent, kwargs):
|
|
||||||
self.parent = parent
|
|
||||||
self.graph = parent.__class__(**kwargs)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self.graph
|
|
||||||
|
|
||||||
def __exit__(self, type_, value, traceback):
|
|
||||||
if type_ is None:
|
|
||||||
self.parent.subgraph(self.graph)
|
|
||||||
|
|
||||||
|
|
||||||
class Graph(Dot):
|
|
||||||
"""Graph source code in the DOT language.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: Graph name used in the source code.
|
|
||||||
comment: Comment added to the first line of the source.
|
|
||||||
filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``).
|
|
||||||
directory: (Sub)directory for source saving and rendering.
|
|
||||||
format: Rendering output format (``'pdf'``, ``'png'``, ...).
|
|
||||||
engine: Layout command used (``'dot'``, ``'neato'``, ...).
|
|
||||||
encoding: Encoding for saving the source.
|
|
||||||
graph_attr: Mapping of ``(attribute, value)`` pairs for the graph.
|
|
||||||
node_attr: Mapping of ``(attribute, value)`` pairs set for all nodes.
|
|
||||||
edge_attr: Mapping of ``(attribute, value)`` pairs set for all edges.
|
|
||||||
body: Iterable of verbatim lines to add to the graph ``body``.
|
|
||||||
strict (bool): Rendering should merge multi-edges.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
All parameters are optional and can be changed under their
|
|
||||||
corresponding attribute name after instance creation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_head = 'graph %s{'
|
|
||||||
_head_strict = 'strict %s' % _head
|
|
||||||
_edge = '\t%s -- %s%s'
|
|
||||||
_edge_plain = _edge % ('%s', '%s', '')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def directed(self):
|
|
||||||
"""``False``"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Digraph(Dot):
|
|
||||||
"""Directed graph source code in the DOT language."""
|
|
||||||
|
|
||||||
if Graph.__doc__ is not None:
|
|
||||||
__doc__ += Graph.__doc__.partition('.')[2]
|
|
||||||
|
|
||||||
_head = 'digraph %s{'
|
|
||||||
_head_strict = 'strict %s' % _head
|
|
||||||
_edge = '\t%s -> %s%s'
|
|
||||||
_edge_plain = _edge % ('%s', '%s', '')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def directed(self):
|
|
||||||
"""``True``"""
|
|
||||||
return True
|
|
||||||
@ -1,311 +0,0 @@
|
|||||||
# files.py - save, render, view
|
|
||||||
|
|
||||||
"""Save DOT code objects, render with Graphviz dot, and open in viewer."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import io
|
|
||||||
import codecs
|
|
||||||
import locale
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from ._compat import text_type
|
|
||||||
|
|
||||||
from . import backend
|
|
||||||
from . import tools
|
|
||||||
|
|
||||||
__all__ = ['File', 'Source']
|
|
||||||
|
|
||||||
ENCODING = 'utf-8'
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Base(object):
|
|
||||||
|
|
||||||
_format = 'pdf'
|
|
||||||
_engine = 'dot'
|
|
||||||
_encoding = ENCODING
|
|
||||||
|
|
||||||
@property
|
|
||||||
def format(self):
|
|
||||||
"""The output format used for rendering (``'pdf'``, ``'png'``, ...)."""
|
|
||||||
return self._format
|
|
||||||
|
|
||||||
@format.setter
|
|
||||||
def format(self, format):
|
|
||||||
format = format.lower()
|
|
||||||
if format not in backend.FORMATS:
|
|
||||||
raise ValueError('unknown format: %r' % format)
|
|
||||||
self._format = format
|
|
||||||
|
|
||||||
@property
|
|
||||||
def engine(self):
|
|
||||||
"""The layout commmand used for rendering (``'dot'``, ``'neato'``, ...)."""
|
|
||||||
return self._engine
|
|
||||||
|
|
||||||
@engine.setter
|
|
||||||
def engine(self, engine):
|
|
||||||
engine = engine.lower()
|
|
||||||
if engine not in backend.ENGINES:
|
|
||||||
raise ValueError('unknown engine: %r' % engine)
|
|
||||||
self._engine = engine
|
|
||||||
|
|
||||||
@property
|
|
||||||
def encoding(self):
|
|
||||||
"""The encoding for the saved source file."""
|
|
||||||
return self._encoding
|
|
||||||
|
|
||||||
@encoding.setter
|
|
||||||
def encoding(self, encoding):
|
|
||||||
if encoding is None:
|
|
||||||
encoding = locale.getpreferredencoding()
|
|
||||||
codecs.lookup(encoding) # raise early
|
|
||||||
self._encoding = encoding
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Return a copied instance of the object.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An independent copy of the current object.
|
|
||||||
"""
|
|
||||||
kwargs = self._kwargs()
|
|
||||||
return self.__class__(**kwargs)
|
|
||||||
|
|
||||||
def _kwargs(self):
|
|
||||||
ns = self.__dict__
|
|
||||||
return {a[1:]: ns[a] for a in ('_format', '_engine', '_encoding')
|
|
||||||
if a in ns}
|
|
||||||
|
|
||||||
|
|
||||||
class File(Base):
|
|
||||||
|
|
||||||
directory = ''
|
|
||||||
|
|
||||||
_default_extension = 'gv'
|
|
||||||
|
|
||||||
def __init__(self, filename=None, directory=None,
|
|
||||||
format=None, engine=None, encoding=ENCODING):
|
|
||||||
if filename is None:
|
|
||||||
name = getattr(self, 'name', None) or self.__class__.__name__
|
|
||||||
filename = '%s.%s' % (name, self._default_extension)
|
|
||||||
self.filename = filename
|
|
||||||
|
|
||||||
if directory is not None:
|
|
||||||
self.directory = directory
|
|
||||||
|
|
||||||
if format is not None:
|
|
||||||
self.format = format
|
|
||||||
|
|
||||||
if engine is not None:
|
|
||||||
self.engine = engine
|
|
||||||
|
|
||||||
self.encoding = encoding
|
|
||||||
|
|
||||||
def _kwargs(self):
|
|
||||||
result = super(File, self)._kwargs()
|
|
||||||
result['filename'] = self.filename
|
|
||||||
if 'directory' in self.__dict__:
|
|
||||||
result['directory'] = self.directory
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _repr_svg_(self):
|
|
||||||
return self.pipe(format='svg').decode(self._encoding)
|
|
||||||
|
|
||||||
def pipe(self, format=None, renderer=None, formatter=None, quiet=False):
|
|
||||||
"""Return the source piped through the Graphviz layout command.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
format: The output format used for rendering (``'pdf'``, ``'png'``, etc.).
|
|
||||||
renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
quiet (bool): Suppress ``stderr`` output from the layout subprocess.
|
|
||||||
Returns:
|
|
||||||
Binary (encoded) stdout of the layout command.
|
|
||||||
Raises:
|
|
||||||
ValueError: If ``format``, ``renderer``, or ``formatter`` are not known.
|
|
||||||
graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None.
|
|
||||||
graphviz.ExecutableNotFound: If the Graphviz executable is not found.
|
|
||||||
subprocess.CalledProcessError: If the exit status is non-zero.
|
|
||||||
"""
|
|
||||||
if format is None:
|
|
||||||
format = self._format
|
|
||||||
|
|
||||||
data = text_type(self.source).encode(self._encoding)
|
|
||||||
|
|
||||||
out = backend.pipe(self._engine, format, data,
|
|
||||||
renderer=renderer, formatter=formatter,
|
|
||||||
quiet=quiet)
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filepath(self):
|
|
||||||
return os.path.join(self.directory, self.filename)
|
|
||||||
|
|
||||||
def save(self, filename=None, directory=None):
|
|
||||||
"""Save the DOT source to file. Ensure the file ends with a newline.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``)
|
|
||||||
directory: (Sub)directory for source saving and rendering.
|
|
||||||
Returns:
|
|
||||||
The (possibly relative) path of the saved source file.
|
|
||||||
"""
|
|
||||||
if filename is not None:
|
|
||||||
self.filename = filename
|
|
||||||
if directory is not None:
|
|
||||||
self.directory = directory
|
|
||||||
|
|
||||||
filepath = self.filepath
|
|
||||||
tools.mkdirs(filepath)
|
|
||||||
|
|
||||||
data = text_type(self.source)
|
|
||||||
|
|
||||||
log.debug('write %d bytes to %r', len(data), filepath)
|
|
||||||
with io.open(filepath, 'w', encoding=self.encoding) as fd:
|
|
||||||
fd.write(data)
|
|
||||||
if not data.endswith(u'\n'):
|
|
||||||
fd.write(u'\n')
|
|
||||||
|
|
||||||
return filepath
|
|
||||||
|
|
||||||
def render(self, filename=None, directory=None, view=False, cleanup=False,
|
|
||||||
format=None, renderer=None, formatter=None,
|
|
||||||
quiet=False, quiet_view=False):
|
|
||||||
"""Save the source to file and render with the Graphviz engine.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``)
|
|
||||||
directory: (Sub)directory for source saving and rendering.
|
|
||||||
view (bool): Open the rendered result with the default application.
|
|
||||||
cleanup (bool): Delete the source file after rendering.
|
|
||||||
format: The output format used for rendering (``'pdf'``, ``'png'``, etc.).
|
|
||||||
renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...).
|
|
||||||
quiet (bool): Suppress ``stderr`` output from the layout subprocess.
|
|
||||||
quiet_view (bool): Suppress ``stderr`` output from the viewer process
|
|
||||||
(implies ``view=True``, ineffective on Windows).
|
|
||||||
Returns:
|
|
||||||
The (possibly relative) path of the rendered file.
|
|
||||||
Raises:
|
|
||||||
ValueError: If ``format``, ``renderer``, or ``formatter`` are not known.
|
|
||||||
graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None.
|
|
||||||
graphviz.ExecutableNotFound: If the Graphviz executable is not found.
|
|
||||||
subprocess.CalledProcessError: If the exit status is non-zero.
|
|
||||||
RuntimeError: If viewer opening is requested but not supported.
|
|
||||||
|
|
||||||
The layout command is started from the directory of ``filepath``, so that
|
|
||||||
references to external files (e.g. ``[image=...]``) can be given as paths
|
|
||||||
relative to the DOT source file.
|
|
||||||
"""
|
|
||||||
filepath = self.save(filename, directory)
|
|
||||||
|
|
||||||
if format is None:
|
|
||||||
format = self._format
|
|
||||||
|
|
||||||
rendered = backend.render(self._engine, format, filepath,
|
|
||||||
renderer=renderer, formatter=formatter,
|
|
||||||
quiet=quiet)
|
|
||||||
|
|
||||||
if cleanup:
|
|
||||||
log.debug('delete %r', filepath)
|
|
||||||
os.remove(filepath)
|
|
||||||
|
|
||||||
if quiet_view or view:
|
|
||||||
self._view(rendered, self._format, quiet_view)
|
|
||||||
|
|
||||||
return rendered
|
|
||||||
|
|
||||||
def view(self, filename=None, directory=None, cleanup=False,
|
|
||||||
quiet=False, quiet_view=False):
|
|
||||||
"""Save the source to file, open the rendered result in a viewer.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``)
|
|
||||||
directory: (Sub)directory for source saving and rendering.
|
|
||||||
cleanup (bool): Delete the source file after rendering.
|
|
||||||
quiet (bool): Suppress ``stderr`` output from the layout subprocess.
|
|
||||||
quiet_view (bool): Suppress ``stderr`` output from the viewer process
|
|
||||||
(ineffective on Windows).
|
|
||||||
Returns:
|
|
||||||
The (possibly relative) path of the rendered file.
|
|
||||||
Raises:
|
|
||||||
graphviz.ExecutableNotFound: If the Graphviz executable is not found.
|
|
||||||
subprocess.CalledProcessError: If the exit status is non-zero.
|
|
||||||
RuntimeError: If opening the viewer is not supported.
|
|
||||||
|
|
||||||
Short-cut method for calling :meth:`.render` with ``view=True``.
|
|
||||||
"""
|
|
||||||
return self.render(filename=filename, directory=directory,
|
|
||||||
view=True, cleanup=cleanup,
|
|
||||||
quiet=quiet, quiet_view=quiet_view)
|
|
||||||
|
|
||||||
def _view(self, filepath, format, quiet):
|
|
||||||
"""Start the right viewer based on file format and platform."""
|
|
||||||
methodnames = [
|
|
||||||
'_view_%s_%s' % (format, backend.PLATFORM),
|
|
||||||
'_view_%s' % backend.PLATFORM,
|
|
||||||
]
|
|
||||||
for name in methodnames:
|
|
||||||
view_method = getattr(self, name, None)
|
|
||||||
if view_method is not None:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError('%r has no built-in viewer support for %r'
|
|
||||||
' on %r platform' % (self.__class__, format,
|
|
||||||
backend.PLATFORM))
|
|
||||||
view_method(filepath, quiet)
|
|
||||||
|
|
||||||
_view_darwin = staticmethod(backend.view.darwin)
|
|
||||||
_view_freebsd = staticmethod(backend.view.freebsd)
|
|
||||||
_view_linux = staticmethod(backend.view.linux)
|
|
||||||
_view_windows = staticmethod(backend.view.windows)
|
|
||||||
|
|
||||||
|
|
||||||
class Source(File):
|
|
||||||
"""Verbatim DOT source code string to be rendered by Graphviz.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
source: The verbatim DOT source code string.
|
|
||||||
filename: Filename for saving the source (defaults to ``'Source.gv'``).
|
|
||||||
directory: (Sub)directory for source saving and rendering.
|
|
||||||
format: Rendering output format (``'pdf'``, ``'png'``, ...).
|
|
||||||
engine: Layout command used (``'dot'``, ``'neato'``, ...).
|
|
||||||
encoding: Encoding for saving the source.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
All parameters except ``source`` are optional. All of them can be changed
|
|
||||||
under their corresponding attribute name after instance creation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_file(cls, filename, directory=None,
|
|
||||||
format=None, engine=None, encoding=ENCODING):
|
|
||||||
"""Return an instance with the source string read from the given file.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filename: Filename for loading/saving the source.
|
|
||||||
directory: (Sub)directory for source loading/saving and rendering.
|
|
||||||
format: Rendering output format (``'pdf'``, ``'png'``, ...).
|
|
||||||
engine: Layout command used (``'dot'``, ``'neato'``, ...).
|
|
||||||
encoding: Encoding for loading/saving the source.
|
|
||||||
"""
|
|
||||||
filepath = os.path.join(directory or '', filename)
|
|
||||||
if encoding is None:
|
|
||||||
encoding = locale.getpreferredencoding()
|
|
||||||
log.debug('read %r with encoding %r', filepath, encoding)
|
|
||||||
with io.open(filepath, encoding=encoding) as fd:
|
|
||||||
source = fd.read()
|
|
||||||
return cls(source, filename, directory, format, engine, encoding)
|
|
||||||
|
|
||||||
def __init__(self, source, filename=None, directory=None,
|
|
||||||
format=None, engine=None, encoding=ENCODING):
|
|
||||||
super(Source, self).__init__(filename, directory,
|
|
||||||
format, engine, encoding)
|
|
||||||
self.source = source #: The verbatim DOT source code string.
|
|
||||||
|
|
||||||
def _kwargs(self):
|
|
||||||
result = super(Source, self)._kwargs()
|
|
||||||
result['source'] = self.source
|
|
||||||
return result
|
|
||||||
@ -1,195 +0,0 @@
|
|||||||
# lang.py - dot language creation helpers
|
|
||||||
|
|
||||||
"""Quote strings to be valid DOT identifiers, assemble attribute lists."""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import collections
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from . import _compat
|
|
||||||
|
|
||||||
from . import tools
|
|
||||||
|
|
||||||
__all__ = ['quote', 'quote_edge', 'a_list', 'attr_list', 'escape', 'nohtml']
|
|
||||||
|
|
||||||
# https://www.graphviz.org/doc/info/lang.html
|
|
||||||
# https://www.graphviz.org/doc/info/attrs.html#k:escString
|
|
||||||
|
|
||||||
HTML_STRING = re.compile(r'<.*>$', re.DOTALL)
|
|
||||||
|
|
||||||
ID = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*|-?(\.[0-9]+|[0-9]+(\.[0-9]*)?))$')
|
|
||||||
|
|
||||||
KEYWORDS = {'node', 'edge', 'graph', 'digraph', 'subgraph', 'strict'}
|
|
||||||
|
|
||||||
COMPASS = {'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'c', '_'} # TODO
|
|
||||||
|
|
||||||
QUOTE_OPTIONAL_BACKSLASHES = re.compile(r'(?P<bs>(?:\\\\)*)'
|
|
||||||
r'\\?(?P<quote>")')
|
|
||||||
|
|
||||||
ESCAPE_UNESCAPED_QUOTES = functools.partial(QUOTE_OPTIONAL_BACKSLASHES.sub,
|
|
||||||
r'\g<bs>\\\g<quote>')
|
|
||||||
|
|
||||||
|
|
||||||
def quote(identifier,
|
|
||||||
is_html_string=HTML_STRING.match,
|
|
||||||
is_valid_id=ID.match, dot_keywords=KEYWORDS,
|
|
||||||
escape_unescaped_quotes=ESCAPE_UNESCAPED_QUOTES):
|
|
||||||
r"""Return DOT identifier from string, quote if needed.
|
|
||||||
|
|
||||||
>>> quote('')
|
|
||||||
'""'
|
|
||||||
|
|
||||||
>>> quote('spam')
|
|
||||||
'spam'
|
|
||||||
|
|
||||||
>>> quote('spam spam')
|
|
||||||
'"spam spam"'
|
|
||||||
|
|
||||||
>>> quote('-4.2')
|
|
||||||
'-4.2'
|
|
||||||
|
|
||||||
>>> quote('.42')
|
|
||||||
'.42'
|
|
||||||
|
|
||||||
>>> quote('<<b>spam</b>>')
|
|
||||||
'<<b>spam</b>>'
|
|
||||||
|
|
||||||
>>> quote(nohtml('<>'))
|
|
||||||
'"<>"'
|
|
||||||
|
|
||||||
>>> print(quote('"'))
|
|
||||||
"\""
|
|
||||||
|
|
||||||
>>> print(quote('\\"'))
|
|
||||||
"\""
|
|
||||||
|
|
||||||
>>> print(quote('\\\\"'))
|
|
||||||
"\\\""
|
|
||||||
|
|
||||||
>>> print(quote('\\\\\\"'))
|
|
||||||
"\\\""
|
|
||||||
"""
|
|
||||||
if is_html_string(identifier) and not isinstance(identifier, NoHtml):
|
|
||||||
pass
|
|
||||||
elif not is_valid_id(identifier) or identifier.lower() in dot_keywords:
|
|
||||||
return '"%s"' % escape_unescaped_quotes(identifier)
|
|
||||||
return identifier
|
|
||||||
|
|
||||||
|
|
||||||
def quote_edge(identifier):
|
|
||||||
"""Return DOT edge statement node_id from string, quote if needed.
|
|
||||||
|
|
||||||
>>> quote_edge('spam')
|
|
||||||
'spam'
|
|
||||||
|
|
||||||
>>> quote_edge('spam spam:eggs eggs')
|
|
||||||
'"spam spam":"eggs eggs"'
|
|
||||||
|
|
||||||
>>> quote_edge('spam:eggs:s')
|
|
||||||
'spam:eggs:s'
|
|
||||||
"""
|
|
||||||
node, _, rest = identifier.partition(':')
|
|
||||||
parts = [quote(node)]
|
|
||||||
if rest:
|
|
||||||
port, _, compass = rest.partition(':')
|
|
||||||
parts.append(quote(port))
|
|
||||||
if compass:
|
|
||||||
parts.append(compass)
|
|
||||||
return ':'.join(parts)
|
|
||||||
|
|
||||||
|
|
||||||
def a_list(label=None, kwargs=None, attributes=None):
|
|
||||||
"""Return assembled DOT a_list string.
|
|
||||||
|
|
||||||
>>> a_list('spam', {'spam': None, 'ham': 'ham ham', 'eggs': ''})
|
|
||||||
'label=spam eggs="" ham="ham ham"'
|
|
||||||
"""
|
|
||||||
result = ['label=%s' % quote(label)] if label is not None else []
|
|
||||||
if kwargs:
|
|
||||||
items = ['%s=%s' % (quote(k), quote(v))
|
|
||||||
for k, v in tools.mapping_items(kwargs) if v is not None]
|
|
||||||
result.extend(items)
|
|
||||||
if attributes:
|
|
||||||
if hasattr(attributes, 'items'):
|
|
||||||
attributes = tools.mapping_items(attributes)
|
|
||||||
items = ['%s=%s' % (quote(k), quote(v))
|
|
||||||
for k, v in attributes if v is not None]
|
|
||||||
result.extend(items)
|
|
||||||
return ' '.join(result)
|
|
||||||
|
|
||||||
|
|
||||||
def attr_list(label=None, kwargs=None, attributes=None):
|
|
||||||
"""Return assembled DOT attribute list string.
|
|
||||||
|
|
||||||
Sorts ``kwargs`` and ``attributes`` if they are plain dicts (to avoid
|
|
||||||
unpredictable order from hash randomization in Python 3 versions).
|
|
||||||
|
|
||||||
>>> attr_list()
|
|
||||||
''
|
|
||||||
|
|
||||||
>>> attr_list('spam spam', kwargs={'eggs': 'eggs', 'ham': 'ham ham'})
|
|
||||||
' [label="spam spam" eggs=eggs ham="ham ham"]'
|
|
||||||
|
|
||||||
>>> attr_list(kwargs={'spam': None, 'eggs': ''})
|
|
||||||
' [eggs=""]'
|
|
||||||
"""
|
|
||||||
content = a_list(label, kwargs, attributes)
|
|
||||||
if not content:
|
|
||||||
return ''
|
|
||||||
return ' [%s]' % content
|
|
||||||
|
|
||||||
|
|
||||||
def escape(s):
|
|
||||||
r"""Return ``s`` as literal disabling special meaning of backslashes and ``'<...>'``.
|
|
||||||
|
|
||||||
see also https://www.graphviz.org/doc/info/attrs.html#k:escString
|
|
||||||
|
|
||||||
Args:
|
|
||||||
s: String in which backslashes and ``'<...>'`` should be treated as literal.
|
|
||||||
Raises:
|
|
||||||
TypeError: If ``s`` is not a ``str`` on Python 3, or a ``str``/``unicode`` on Python 2.
|
|
||||||
|
|
||||||
>>> print(escape(r'\l'))
|
|
||||||
\\l
|
|
||||||
"""
|
|
||||||
return nohtml(s.replace('\\', '\\\\'))
|
|
||||||
|
|
||||||
|
|
||||||
class NoHtml(object):
|
|
||||||
"""Mixin for string subclasses disabling fall-through of ``'<...>'``."""
|
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
_doc = "%s subclass that does not treat ``'<...>'`` as DOT HTML string."
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _subcls(cls, other):
|
|
||||||
name = '%s_%s' % (cls.__name__, other.__name__)
|
|
||||||
bases = (other, cls)
|
|
||||||
ns = {'__doc__': cls._doc % other.__name__}
|
|
||||||
return type(name, bases, ns)
|
|
||||||
|
|
||||||
|
|
||||||
NOHTML = collections.OrderedDict((c, NoHtml._subcls(c)) for c in _compat.string_classes)
|
|
||||||
|
|
||||||
|
|
||||||
def nohtml(s):
|
|
||||||
"""Return copy of ``s`` that will not treat ``'<...>'`` as DOT HTML string in quoting.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
s: String in which leading ``'<'`` and trailing ``'>'`` should be treated as literal.
|
|
||||||
Raises:
|
|
||||||
TypeError: If ``s`` is not a ``str`` on Python 3, or a ``str``/``unicode`` on Python 2.
|
|
||||||
|
|
||||||
>>> quote('<>-*-<>')
|
|
||||||
'<>-*-<>'
|
|
||||||
|
|
||||||
>>> quote(nohtml('<>-*-<>'))
|
|
||||||
'"<>-*-<>"'
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
subcls = NOHTML[type(s)]
|
|
||||||
except KeyError:
|
|
||||||
raise TypeError('%r does not have one of the required types:'
|
|
||||||
' %r' % (s, list(NOHTML)))
|
|
||||||
return subcls(s)
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
# tools.py - generic helpers
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from . import _compat
|
|
||||||
|
|
||||||
__all__ = ['attach', 'mkdirs', 'mapping_items']
|
|
||||||
|
|
||||||
|
|
||||||
def attach(object, name):
|
|
||||||
"""Return a decorator doing ``setattr(object, name)`` with its argument.
|
|
||||||
|
|
||||||
>>> spam = type('Spam', (object,), {})()
|
|
||||||
>>> @attach(spam, 'eggs')
|
|
||||||
... def func():
|
|
||||||
... pass
|
|
||||||
>>> spam.eggs # doctest: +ELLIPSIS
|
|
||||||
<function func at 0x...>
|
|
||||||
"""
|
|
||||||
def decorator(func):
|
|
||||||
setattr(object, name, func)
|
|
||||||
return func
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def mkdirs(filename, mode=0o777):
|
|
||||||
"""Recursively create directories up to the path of ``filename`` as needed."""
|
|
||||||
dirname = os.path.dirname(filename)
|
|
||||||
if not dirname:
|
|
||||||
return
|
|
||||||
_compat.makedirs(dirname, mode=mode, exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def mapping_items(mapping):
|
|
||||||
"""Return an iterator over the ``mapping`` items, sort if it's a plain dict.
|
|
||||||
|
|
||||||
>>> list(mapping_items({'spam': 0, 'ham': 1, 'eggs': 2}))
|
|
||||||
[('eggs', 2), ('ham', 1), ('spam', 0)]
|
|
||||||
|
|
||||||
>>> from collections import OrderedDict
|
|
||||||
>>> list(mapping_items(OrderedDict(enumerate(['spam', 'ham', 'eggs']))))
|
|
||||||
[(0, 'spam'), (1, 'ham'), (2, 'eggs')]
|
|
||||||
"""
|
|
||||||
result = _compat.iteritems(mapping)
|
|
||||||
if type(mapping) is dict:
|
|
||||||
result = iter(sorted(result))
|
|
||||||
return result
|
|
||||||
0
src/wireviz/__init__.py
Normal file
0
src/wireviz/__init__.py
Normal file
54
src/wireviz/build_examples.py
Normal file
54
src/wireviz/build_examples.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import wireviz
|
||||||
|
import os
|
||||||
|
|
||||||
|
demos = 2 # 2
|
||||||
|
<<<<<<< HEAD:src/wireviz/batch.py
|
||||||
|
examples = 9 # 9
|
||||||
|
=======
|
||||||
|
examples = 6 # 6
|
||||||
|
>>>>>>> 2d428e8... refactor dependencies and installation:src/wireviz/build_examples.py
|
||||||
|
tutorials = 7 # 7
|
||||||
|
|
||||||
|
if demos:
|
||||||
|
for i in range(1,demos+1):
|
||||||
|
fn = '../examples/demo{:02d}.yml'.format(i)
|
||||||
|
print(fn)
|
||||||
|
wireviz.parse(fn, gen_bom=True)
|
||||||
|
|
||||||
|
if examples:
|
||||||
|
with open(os.path.abspath('../examples/readme.md'), 'w') as file:
|
||||||
|
file.write('# Example gallery\n')
|
||||||
|
for i in range(1,examples+1):
|
||||||
|
fn = '../examples/ex{:02d}.yml'.format(i)
|
||||||
|
print(fn)
|
||||||
|
wireviz.parse(fn, gen_bom=True)
|
||||||
|
|
||||||
|
file.write('## Example {:02d}\n'.format(i))
|
||||||
|
file.write('\n\n'.format(i))
|
||||||
|
file.write('[Source](ex{:02d}.yml) - [Bill of Materials](ex{:02d}.bom.tsv)\n\n\n'.format(i,i))
|
||||||
|
|
||||||
|
if tutorials:
|
||||||
|
with open(os.path.abspath('../tutorial/readme.md'), 'w') as file:
|
||||||
|
file.write('# WireViz Tutorial\n')
|
||||||
|
for i in range(1,tutorials+1):
|
||||||
|
fn = '../tutorial/tutorial{:02d}.yml'.format(i)
|
||||||
|
print(fn)
|
||||||
|
wireviz.parse(fn, gen_bom=True)
|
||||||
|
|
||||||
|
with open(os.path.abspath('../tutorial/tutorial{:02d}.md'.format(i)), 'r') as info:
|
||||||
|
for line in info:
|
||||||
|
file.write(line.replace('## ', '## {} - '.format(i)))
|
||||||
|
file.write('\n[Source](tutorial{:02d}.yml):\n\n'.format(i))
|
||||||
|
|
||||||
|
with open(os.path.abspath('../tutorial/tutorial{:02d}.yml'.format(i)), 'r') as src:
|
||||||
|
file.write('```yaml\n')
|
||||||
|
for line in src:
|
||||||
|
file.write(line)
|
||||||
|
file.write('```\n')
|
||||||
|
file.write('\n')
|
||||||
|
|
||||||
|
file.write('\nOutput:\n\n'.format(i))
|
||||||
|
|
||||||
|
file.write('\n\n'.format(i))
|
||||||
|
|
||||||
|
file.write('[Bill of Materials](tutorial{:02d}.bom.tsv)\n\n\n'.format(i))
|
||||||
@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
@ -6,8 +8,8 @@ from collections import Counter
|
|||||||
import yaml
|
import yaml
|
||||||
from graphviz import Graph
|
from graphviz import Graph
|
||||||
|
|
||||||
import wv_colors
|
from wireviz import wv_colors
|
||||||
from wv_helper import nested, int2tuple, awg_equiv, flatten2d, tuplelist2tsv
|
from wireviz.wv_helper import nested, int2tuple, awg_equiv, flatten2d, tuplelist2tsv
|
||||||
|
|
||||||
class Harness:
|
class Harness:
|
||||||
|
|
||||||
@ -620,8 +622,7 @@ def parse(file_in, file_out=None, gen_bom=False):
|
|||||||
|
|
||||||
h.output(filename=file_out, format=('png','svg'), gen_bom=gen_bom, view=False)
|
h.output(filename=file_out, format=('png','svg'), gen_bom=gen_bom, view=False)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def main():
|
||||||
import argparse
|
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument('file_input', nargs='?', default='_test/test.yml')
|
ap.add_argument('file_input', nargs='?', default='_test/test.yml')
|
||||||
ap.add_argument('file_output', nargs='?', default=None)
|
ap.add_argument('file_output', nargs='?', default=None)
|
||||||
@ -629,3 +630,6 @@ if __name__ == '__main__':
|
|||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
parse(args.file_input, file_out=args.file_output, gen_bom=args.bom)
|
parse(args.file_input, file_out=args.file_output, gen_bom=args.bom)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|||||||
@ -1,427 +0,0 @@
|
|||||||
|
|
||||||
from .error import *
|
|
||||||
|
|
||||||
from .tokens import *
|
|
||||||
from .events import *
|
|
||||||
from .nodes import *
|
|
||||||
|
|
||||||
from .loader import *
|
|
||||||
from .dumper import *
|
|
||||||
|
|
||||||
__version__ = '5.3.1'
|
|
||||||
try:
|
|
||||||
from .cyaml import *
|
|
||||||
__with_libyaml__ = True
|
|
||||||
except ImportError:
|
|
||||||
__with_libyaml__ = False
|
|
||||||
|
|
||||||
import io
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
# Warnings control
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# 'Global' warnings state:
|
|
||||||
_warnings_enabled = {
|
|
||||||
'YAMLLoadWarning': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get or set global warnings' state
|
|
||||||
def warnings(settings=None):
|
|
||||||
if settings is None:
|
|
||||||
return _warnings_enabled
|
|
||||||
|
|
||||||
if type(settings) is dict:
|
|
||||||
for key in settings:
|
|
||||||
if key in _warnings_enabled:
|
|
||||||
_warnings_enabled[key] = settings[key]
|
|
||||||
|
|
||||||
# Warn when load() is called without Loader=...
|
|
||||||
class YAMLLoadWarning(RuntimeWarning):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def load_warning(method):
|
|
||||||
if _warnings_enabled['YAMLLoadWarning'] is False:
|
|
||||||
return
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
message = (
|
|
||||||
"calling yaml.%s() without Loader=... is deprecated, as the "
|
|
||||||
"default Loader is unsafe. Please read "
|
|
||||||
"https://msg.pyyaml.org/load for full details."
|
|
||||||
) % method
|
|
||||||
|
|
||||||
warnings.warn(message, YAMLLoadWarning, stacklevel=3)
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
def scan(stream, Loader=Loader):
|
|
||||||
"""
|
|
||||||
Scan a YAML stream and produce scanning tokens.
|
|
||||||
"""
|
|
||||||
loader = Loader(stream)
|
|
||||||
try:
|
|
||||||
while loader.check_token():
|
|
||||||
yield loader.get_token()
|
|
||||||
finally:
|
|
||||||
loader.dispose()
|
|
||||||
|
|
||||||
def parse(stream, Loader=Loader):
|
|
||||||
"""
|
|
||||||
Parse a YAML stream and produce parsing events.
|
|
||||||
"""
|
|
||||||
loader = Loader(stream)
|
|
||||||
try:
|
|
||||||
while loader.check_event():
|
|
||||||
yield loader.get_event()
|
|
||||||
finally:
|
|
||||||
loader.dispose()
|
|
||||||
|
|
||||||
def compose(stream, Loader=Loader):
|
|
||||||
"""
|
|
||||||
Parse the first YAML document in a stream
|
|
||||||
and produce the corresponding representation tree.
|
|
||||||
"""
|
|
||||||
loader = Loader(stream)
|
|
||||||
try:
|
|
||||||
return loader.get_single_node()
|
|
||||||
finally:
|
|
||||||
loader.dispose()
|
|
||||||
|
|
||||||
def compose_all(stream, Loader=Loader):
|
|
||||||
"""
|
|
||||||
Parse all YAML documents in a stream
|
|
||||||
and produce corresponding representation trees.
|
|
||||||
"""
|
|
||||||
loader = Loader(stream)
|
|
||||||
try:
|
|
||||||
while loader.check_node():
|
|
||||||
yield loader.get_node()
|
|
||||||
finally:
|
|
||||||
loader.dispose()
|
|
||||||
|
|
||||||
def load(stream, Loader=None):
|
|
||||||
"""
|
|
||||||
Parse the first YAML document in a stream
|
|
||||||
and produce the corresponding Python object.
|
|
||||||
"""
|
|
||||||
if Loader is None:
|
|
||||||
load_warning('load')
|
|
||||||
Loader = FullLoader
|
|
||||||
|
|
||||||
loader = Loader(stream)
|
|
||||||
try:
|
|
||||||
return loader.get_single_data()
|
|
||||||
finally:
|
|
||||||
loader.dispose()
|
|
||||||
|
|
||||||
def load_all(stream, Loader=None):
|
|
||||||
"""
|
|
||||||
Parse all YAML documents in a stream
|
|
||||||
and produce corresponding Python objects.
|
|
||||||
"""
|
|
||||||
if Loader is None:
|
|
||||||
load_warning('load_all')
|
|
||||||
Loader = FullLoader
|
|
||||||
|
|
||||||
loader = Loader(stream)
|
|
||||||
try:
|
|
||||||
while loader.check_data():
|
|
||||||
yield loader.get_data()
|
|
||||||
finally:
|
|
||||||
loader.dispose()
|
|
||||||
|
|
||||||
def full_load(stream):
|
|
||||||
"""
|
|
||||||
Parse the first YAML document in a stream
|
|
||||||
and produce the corresponding Python object.
|
|
||||||
|
|
||||||
Resolve all tags except those known to be
|
|
||||||
unsafe on untrusted input.
|
|
||||||
"""
|
|
||||||
return load(stream, FullLoader)
|
|
||||||
|
|
||||||
def full_load_all(stream):
|
|
||||||
"""
|
|
||||||
Parse all YAML documents in a stream
|
|
||||||
and produce corresponding Python objects.
|
|
||||||
|
|
||||||
Resolve all tags except those known to be
|
|
||||||
unsafe on untrusted input.
|
|
||||||
"""
|
|
||||||
return load_all(stream, FullLoader)
|
|
||||||
|
|
||||||
def safe_load(stream):
|
|
||||||
"""
|
|
||||||
Parse the first YAML document in a stream
|
|
||||||
and produce the corresponding Python object.
|
|
||||||
|
|
||||||
Resolve only basic YAML tags. This is known
|
|
||||||
to be safe for untrusted input.
|
|
||||||
"""
|
|
||||||
return load(stream, SafeLoader)
|
|
||||||
|
|
||||||
def safe_load_all(stream):
|
|
||||||
"""
|
|
||||||
Parse all YAML documents in a stream
|
|
||||||
and produce corresponding Python objects.
|
|
||||||
|
|
||||||
Resolve only basic YAML tags. This is known
|
|
||||||
to be safe for untrusted input.
|
|
||||||
"""
|
|
||||||
return load_all(stream, SafeLoader)
|
|
||||||
|
|
||||||
def unsafe_load(stream):
|
|
||||||
"""
|
|
||||||
Parse the first YAML document in a stream
|
|
||||||
and produce the corresponding Python object.
|
|
||||||
|
|
||||||
Resolve all tags, even those known to be
|
|
||||||
unsafe on untrusted input.
|
|
||||||
"""
|
|
||||||
return load(stream, UnsafeLoader)
|
|
||||||
|
|
||||||
def unsafe_load_all(stream):
|
|
||||||
"""
|
|
||||||
Parse all YAML documents in a stream
|
|
||||||
and produce corresponding Python objects.
|
|
||||||
|
|
||||||
Resolve all tags, even those known to be
|
|
||||||
unsafe on untrusted input.
|
|
||||||
"""
|
|
||||||
return load_all(stream, UnsafeLoader)
|
|
||||||
|
|
||||||
def emit(events, stream=None, Dumper=Dumper,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None):
|
|
||||||
"""
|
|
||||||
Emit YAML parsing events into a stream.
|
|
||||||
If stream is None, return the produced string instead.
|
|
||||||
"""
|
|
||||||
getvalue = None
|
|
||||||
if stream is None:
|
|
||||||
stream = io.StringIO()
|
|
||||||
getvalue = stream.getvalue
|
|
||||||
dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
|
|
||||||
allow_unicode=allow_unicode, line_break=line_break)
|
|
||||||
try:
|
|
||||||
for event in events:
|
|
||||||
dumper.emit(event)
|
|
||||||
finally:
|
|
||||||
dumper.dispose()
|
|
||||||
if getvalue:
|
|
||||||
return getvalue()
|
|
||||||
|
|
||||||
def serialize_all(nodes, stream=None, Dumper=Dumper,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None):
|
|
||||||
"""
|
|
||||||
Serialize a sequence of representation trees into a YAML stream.
|
|
||||||
If stream is None, return the produced string instead.
|
|
||||||
"""
|
|
||||||
getvalue = None
|
|
||||||
if stream is None:
|
|
||||||
if encoding is None:
|
|
||||||
stream = io.StringIO()
|
|
||||||
else:
|
|
||||||
stream = io.BytesIO()
|
|
||||||
getvalue = stream.getvalue
|
|
||||||
dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
|
|
||||||
allow_unicode=allow_unicode, line_break=line_break,
|
|
||||||
encoding=encoding, version=version, tags=tags,
|
|
||||||
explicit_start=explicit_start, explicit_end=explicit_end)
|
|
||||||
try:
|
|
||||||
dumper.open()
|
|
||||||
for node in nodes:
|
|
||||||
dumper.serialize(node)
|
|
||||||
dumper.close()
|
|
||||||
finally:
|
|
||||||
dumper.dispose()
|
|
||||||
if getvalue:
|
|
||||||
return getvalue()
|
|
||||||
|
|
||||||
def serialize(node, stream=None, Dumper=Dumper, **kwds):
|
|
||||||
"""
|
|
||||||
Serialize a representation tree into a YAML stream.
|
|
||||||
If stream is None, return the produced string instead.
|
|
||||||
"""
|
|
||||||
return serialize_all([node], stream, Dumper=Dumper, **kwds)
|
|
||||||
|
|
||||||
def dump_all(documents, stream=None, Dumper=Dumper,
|
|
||||||
default_style=None, default_flow_style=False,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None, sort_keys=True):
|
|
||||||
"""
|
|
||||||
Serialize a sequence of Python objects into a YAML stream.
|
|
||||||
If stream is None, return the produced string instead.
|
|
||||||
"""
|
|
||||||
getvalue = None
|
|
||||||
if stream is None:
|
|
||||||
if encoding is None:
|
|
||||||
stream = io.StringIO()
|
|
||||||
else:
|
|
||||||
stream = io.BytesIO()
|
|
||||||
getvalue = stream.getvalue
|
|
||||||
dumper = Dumper(stream, default_style=default_style,
|
|
||||||
default_flow_style=default_flow_style,
|
|
||||||
canonical=canonical, indent=indent, width=width,
|
|
||||||
allow_unicode=allow_unicode, line_break=line_break,
|
|
||||||
encoding=encoding, version=version, tags=tags,
|
|
||||||
explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys)
|
|
||||||
try:
|
|
||||||
dumper.open()
|
|
||||||
for data in documents:
|
|
||||||
dumper.represent(data)
|
|
||||||
dumper.close()
|
|
||||||
finally:
|
|
||||||
dumper.dispose()
|
|
||||||
if getvalue:
|
|
||||||
return getvalue()
|
|
||||||
|
|
||||||
def dump(data, stream=None, Dumper=Dumper, **kwds):
|
|
||||||
"""
|
|
||||||
Serialize a Python object into a YAML stream.
|
|
||||||
If stream is None, return the produced string instead.
|
|
||||||
"""
|
|
||||||
return dump_all([data], stream, Dumper=Dumper, **kwds)
|
|
||||||
|
|
||||||
def safe_dump_all(documents, stream=None, **kwds):
|
|
||||||
"""
|
|
||||||
Serialize a sequence of Python objects into a YAML stream.
|
|
||||||
Produce only basic YAML tags.
|
|
||||||
If stream is None, return the produced string instead.
|
|
||||||
"""
|
|
||||||
return dump_all(documents, stream, Dumper=SafeDumper, **kwds)
|
|
||||||
|
|
||||||
def safe_dump(data, stream=None, **kwds):
|
|
||||||
"""
|
|
||||||
Serialize a Python object into a YAML stream.
|
|
||||||
Produce only basic YAML tags.
|
|
||||||
If stream is None, return the produced string instead.
|
|
||||||
"""
|
|
||||||
return dump_all([data], stream, Dumper=SafeDumper, **kwds)
|
|
||||||
|
|
||||||
def add_implicit_resolver(tag, regexp, first=None,
|
|
||||||
Loader=None, Dumper=Dumper):
|
|
||||||
"""
|
|
||||||
Add an implicit scalar detector.
|
|
||||||
If an implicit scalar value matches the given regexp,
|
|
||||||
the corresponding tag is assigned to the scalar.
|
|
||||||
first is a sequence of possible initial characters or None.
|
|
||||||
"""
|
|
||||||
if Loader is None:
|
|
||||||
loader.Loader.add_implicit_resolver(tag, regexp, first)
|
|
||||||
loader.FullLoader.add_implicit_resolver(tag, regexp, first)
|
|
||||||
loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first)
|
|
||||||
else:
|
|
||||||
Loader.add_implicit_resolver(tag, regexp, first)
|
|
||||||
Dumper.add_implicit_resolver(tag, regexp, first)
|
|
||||||
|
|
||||||
def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper):
|
|
||||||
"""
|
|
||||||
Add a path based resolver for the given tag.
|
|
||||||
A path is a list of keys that forms a path
|
|
||||||
to a node in the representation tree.
|
|
||||||
Keys can be string values, integers, or None.
|
|
||||||
"""
|
|
||||||
if Loader is None:
|
|
||||||
loader.Loader.add_path_resolver(tag, path, kind)
|
|
||||||
loader.FullLoader.add_path_resolver(tag, path, kind)
|
|
||||||
loader.UnsafeLoader.add_path_resolver(tag, path, kind)
|
|
||||||
else:
|
|
||||||
Loader.add_path_resolver(tag, path, kind)
|
|
||||||
Dumper.add_path_resolver(tag, path, kind)
|
|
||||||
|
|
||||||
def add_constructor(tag, constructor, Loader=None):
|
|
||||||
"""
|
|
||||||
Add a constructor for the given tag.
|
|
||||||
Constructor is a function that accepts a Loader instance
|
|
||||||
and a node object and produces the corresponding Python object.
|
|
||||||
"""
|
|
||||||
if Loader is None:
|
|
||||||
loader.Loader.add_constructor(tag, constructor)
|
|
||||||
loader.FullLoader.add_constructor(tag, constructor)
|
|
||||||
loader.UnsafeLoader.add_constructor(tag, constructor)
|
|
||||||
else:
|
|
||||||
Loader.add_constructor(tag, constructor)
|
|
||||||
|
|
||||||
def add_multi_constructor(tag_prefix, multi_constructor, Loader=None):
|
|
||||||
"""
|
|
||||||
Add a multi-constructor for the given tag prefix.
|
|
||||||
Multi-constructor is called for a node if its tag starts with tag_prefix.
|
|
||||||
Multi-constructor accepts a Loader instance, a tag suffix,
|
|
||||||
and a node object and produces the corresponding Python object.
|
|
||||||
"""
|
|
||||||
if Loader is None:
|
|
||||||
loader.Loader.add_multi_constructor(tag_prefix, multi_constructor)
|
|
||||||
loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor)
|
|
||||||
loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor)
|
|
||||||
else:
|
|
||||||
Loader.add_multi_constructor(tag_prefix, multi_constructor)
|
|
||||||
|
|
||||||
def add_representer(data_type, representer, Dumper=Dumper):
|
|
||||||
"""
|
|
||||||
Add a representer for the given type.
|
|
||||||
Representer is a function accepting a Dumper instance
|
|
||||||
and an instance of the given data type
|
|
||||||
and producing the corresponding representation node.
|
|
||||||
"""
|
|
||||||
Dumper.add_representer(data_type, representer)
|
|
||||||
|
|
||||||
def add_multi_representer(data_type, multi_representer, Dumper=Dumper):
|
|
||||||
"""
|
|
||||||
Add a representer for the given type.
|
|
||||||
Multi-representer is a function accepting a Dumper instance
|
|
||||||
and an instance of the given data type or subtype
|
|
||||||
and producing the corresponding representation node.
|
|
||||||
"""
|
|
||||||
Dumper.add_multi_representer(data_type, multi_representer)
|
|
||||||
|
|
||||||
class YAMLObjectMetaclass(type):
|
|
||||||
"""
|
|
||||||
The metaclass for YAMLObject.
|
|
||||||
"""
|
|
||||||
def __init__(cls, name, bases, kwds):
|
|
||||||
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
|
|
||||||
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
|
|
||||||
if isinstance(cls.yaml_loader, list):
|
|
||||||
for loader in cls.yaml_loader:
|
|
||||||
loader.add_constructor(cls.yaml_tag, cls.from_yaml)
|
|
||||||
else:
|
|
||||||
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
|
|
||||||
|
|
||||||
cls.yaml_dumper.add_representer(cls, cls.to_yaml)
|
|
||||||
|
|
||||||
class YAMLObject(metaclass=YAMLObjectMetaclass):
|
|
||||||
"""
|
|
||||||
An object that can dump itself to a YAML stream
|
|
||||||
and load itself from a YAML stream.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = () # no direct instantiation, so allow immutable subclasses
|
|
||||||
|
|
||||||
yaml_loader = [Loader, FullLoader, UnsafeLoader]
|
|
||||||
yaml_dumper = Dumper
|
|
||||||
|
|
||||||
yaml_tag = None
|
|
||||||
yaml_flow_style = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_yaml(cls, loader, node):
|
|
||||||
"""
|
|
||||||
Convert a representation node to a Python object.
|
|
||||||
"""
|
|
||||||
return loader.construct_yaml_object(node, cls)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_yaml(cls, dumper, data):
|
|
||||||
"""
|
|
||||||
Convert a Python object to a representation node.
|
|
||||||
"""
|
|
||||||
return dumper.represent_yaml_object(cls.yaml_tag, data, cls,
|
|
||||||
flow_style=cls.yaml_flow_style)
|
|
||||||
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
|
|
||||||
__all__ = ['Composer', 'ComposerError']
|
|
||||||
|
|
||||||
from .error import MarkedYAMLError
|
|
||||||
from .events import *
|
|
||||||
from .nodes import *
|
|
||||||
|
|
||||||
class ComposerError(MarkedYAMLError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Composer:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.anchors = {}
|
|
||||||
|
|
||||||
def check_node(self):
|
|
||||||
# Drop the STREAM-START event.
|
|
||||||
if self.check_event(StreamStartEvent):
|
|
||||||
self.get_event()
|
|
||||||
|
|
||||||
# If there are more documents available?
|
|
||||||
return not self.check_event(StreamEndEvent)
|
|
||||||
|
|
||||||
def get_node(self):
|
|
||||||
# Get the root node of the next document.
|
|
||||||
if not self.check_event(StreamEndEvent):
|
|
||||||
return self.compose_document()
|
|
||||||
|
|
||||||
def get_single_node(self):
|
|
||||||
# Drop the STREAM-START event.
|
|
||||||
self.get_event()
|
|
||||||
|
|
||||||
# Compose a document if the stream is not empty.
|
|
||||||
document = None
|
|
||||||
if not self.check_event(StreamEndEvent):
|
|
||||||
document = self.compose_document()
|
|
||||||
|
|
||||||
# Ensure that the stream contains no more documents.
|
|
||||||
if not self.check_event(StreamEndEvent):
|
|
||||||
event = self.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.get_event()
|
|
||||||
|
|
||||||
return document
|
|
||||||
|
|
||||||
def compose_document(self):
|
|
||||||
# Drop the DOCUMENT-START event.
|
|
||||||
self.get_event()
|
|
||||||
|
|
||||||
# Compose the root node.
|
|
||||||
node = self.compose_node(None, None)
|
|
||||||
|
|
||||||
# Drop the DOCUMENT-END event.
|
|
||||||
self.get_event()
|
|
||||||
|
|
||||||
self.anchors = {}
|
|
||||||
return node
|
|
||||||
|
|
||||||
def compose_node(self, parent, index):
|
|
||||||
if self.check_event(AliasEvent):
|
|
||||||
event = self.get_event()
|
|
||||||
anchor = event.anchor
|
|
||||||
if anchor not in self.anchors:
|
|
||||||
raise ComposerError(None, None, "found undefined alias %r"
|
|
||||||
% anchor, event.start_mark)
|
|
||||||
return self.anchors[anchor]
|
|
||||||
event = self.peek_event()
|
|
||||||
anchor = event.anchor
|
|
||||||
if anchor is not None:
|
|
||||||
if anchor in self.anchors:
|
|
||||||
raise ComposerError("found duplicate anchor %r; first occurrence"
|
|
||||||
% anchor, self.anchors[anchor].start_mark,
|
|
||||||
"second occurrence", event.start_mark)
|
|
||||||
self.descend_resolver(parent, index)
|
|
||||||
if self.check_event(ScalarEvent):
|
|
||||||
node = self.compose_scalar_node(anchor)
|
|
||||||
elif self.check_event(SequenceStartEvent):
|
|
||||||
node = self.compose_sequence_node(anchor)
|
|
||||||
elif self.check_event(MappingStartEvent):
|
|
||||||
node = self.compose_mapping_node(anchor)
|
|
||||||
self.ascend_resolver()
|
|
||||||
return node
|
|
||||||
|
|
||||||
def compose_scalar_node(self, anchor):
|
|
||||||
event = self.get_event()
|
|
||||||
tag = event.tag
|
|
||||||
if tag is None or tag == '!':
|
|
||||||
tag = self.resolve(ScalarNode, event.value, event.implicit)
|
|
||||||
node = ScalarNode(tag, event.value,
|
|
||||||
event.start_mark, event.end_mark, style=event.style)
|
|
||||||
if anchor is not None:
|
|
||||||
self.anchors[anchor] = node
|
|
||||||
return node
|
|
||||||
|
|
||||||
def compose_sequence_node(self, anchor):
|
|
||||||
start_event = self.get_event()
|
|
||||||
tag = start_event.tag
|
|
||||||
if tag is None or tag == '!':
|
|
||||||
tag = self.resolve(SequenceNode, None, start_event.implicit)
|
|
||||||
node = SequenceNode(tag, [],
|
|
||||||
start_event.start_mark, None,
|
|
||||||
flow_style=start_event.flow_style)
|
|
||||||
if anchor is not None:
|
|
||||||
self.anchors[anchor] = node
|
|
||||||
index = 0
|
|
||||||
while not self.check_event(SequenceEndEvent):
|
|
||||||
node.value.append(self.compose_node(node, index))
|
|
||||||
index += 1
|
|
||||||
end_event = self.get_event()
|
|
||||||
node.end_mark = end_event.end_mark
|
|
||||||
return node
|
|
||||||
|
|
||||||
def compose_mapping_node(self, anchor):
|
|
||||||
start_event = self.get_event()
|
|
||||||
tag = start_event.tag
|
|
||||||
if tag is None or tag == '!':
|
|
||||||
tag = self.resolve(MappingNode, None, start_event.implicit)
|
|
||||||
node = MappingNode(tag, [],
|
|
||||||
start_event.start_mark, None,
|
|
||||||
flow_style=start_event.flow_style)
|
|
||||||
if anchor is not None:
|
|
||||||
self.anchors[anchor] = node
|
|
||||||
while not self.check_event(MappingEndEvent):
|
|
||||||
#key_event = self.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.get_event()
|
|
||||||
node.end_mark = end_event.end_mark
|
|
||||||
return node
|
|
||||||
|
|
||||||
@ -1,748 +0,0 @@
|
|||||||
|
|
||||||
__all__ = [
|
|
||||||
'BaseConstructor',
|
|
||||||
'SafeConstructor',
|
|
||||||
'FullConstructor',
|
|
||||||
'UnsafeConstructor',
|
|
||||||
'Constructor',
|
|
||||||
'ConstructorError'
|
|
||||||
]
|
|
||||||
|
|
||||||
from .error import *
|
|
||||||
from .nodes import *
|
|
||||||
|
|
||||||
import collections.abc, datetime, base64, binascii, re, sys, types
|
|
||||||
|
|
||||||
class ConstructorError(MarkedYAMLError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class BaseConstructor:
|
|
||||||
|
|
||||||
yaml_constructors = {}
|
|
||||||
yaml_multi_constructors = {}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.constructed_objects = {}
|
|
||||||
self.recursive_objects = {}
|
|
||||||
self.state_generators = []
|
|
||||||
self.deep_construct = False
|
|
||||||
|
|
||||||
def check_data(self):
|
|
||||||
# If there are more documents available?
|
|
||||||
return self.check_node()
|
|
||||||
|
|
||||||
def check_state_key(self, key):
|
|
||||||
"""Block special attributes/methods from being set in a newly created
|
|
||||||
object, to prevent user-controlled methods from being called during
|
|
||||||
deserialization"""
|
|
||||||
if self.get_state_keys_blacklist_regexp().match(key):
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"blacklisted key '%s' in instance state found" % (key,), None)
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
# Construct and return the next document.
|
|
||||||
if self.check_node():
|
|
||||||
return self.construct_document(self.get_node())
|
|
||||||
|
|
||||||
def get_single_data(self):
|
|
||||||
# Ensure that the stream contains a single document and construct it.
|
|
||||||
node = self.get_single_node()
|
|
||||||
if node is not None:
|
|
||||||
return self.construct_document(node)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def construct_document(self, node):
|
|
||||||
data = self.construct_object(node)
|
|
||||||
while self.state_generators:
|
|
||||||
state_generators = self.state_generators
|
|
||||||
self.state_generators = []
|
|
||||||
for generator in state_generators:
|
|
||||||
for dummy in generator:
|
|
||||||
pass
|
|
||||||
self.constructed_objects = {}
|
|
||||||
self.recursive_objects = {}
|
|
||||||
self.deep_construct = False
|
|
||||||
return data
|
|
||||||
|
|
||||||
def construct_object(self, node, deep=False):
|
|
||||||
if node in self.constructed_objects:
|
|
||||||
return self.constructed_objects[node]
|
|
||||||
if deep:
|
|
||||||
old_deep = self.deep_construct
|
|
||||||
self.deep_construct = True
|
|
||||||
if node in self.recursive_objects:
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"found unconstructable recursive node", node.start_mark)
|
|
||||||
self.recursive_objects[node] = None
|
|
||||||
constructor = None
|
|
||||||
tag_suffix = None
|
|
||||||
if node.tag in self.yaml_constructors:
|
|
||||||
constructor = self.yaml_constructors[node.tag]
|
|
||||||
else:
|
|
||||||
for tag_prefix in self.yaml_multi_constructors:
|
|
||||||
if tag_prefix is not None and node.tag.startswith(tag_prefix):
|
|
||||||
tag_suffix = node.tag[len(tag_prefix):]
|
|
||||||
constructor = self.yaml_multi_constructors[tag_prefix]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if None in self.yaml_multi_constructors:
|
|
||||||
tag_suffix = node.tag
|
|
||||||
constructor = self.yaml_multi_constructors[None]
|
|
||||||
elif None in self.yaml_constructors:
|
|
||||||
constructor = self.yaml_constructors[None]
|
|
||||||
elif isinstance(node, ScalarNode):
|
|
||||||
constructor = self.__class__.construct_scalar
|
|
||||||
elif isinstance(node, SequenceNode):
|
|
||||||
constructor = self.__class__.construct_sequence
|
|
||||||
elif isinstance(node, MappingNode):
|
|
||||||
constructor = self.__class__.construct_mapping
|
|
||||||
if tag_suffix is None:
|
|
||||||
data = constructor(self, node)
|
|
||||||
else:
|
|
||||||
data = constructor(self, tag_suffix, node)
|
|
||||||
if isinstance(data, types.GeneratorType):
|
|
||||||
generator = data
|
|
||||||
data = next(generator)
|
|
||||||
if self.deep_construct:
|
|
||||||
for dummy in generator:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.state_generators.append(generator)
|
|
||||||
self.constructed_objects[node] = data
|
|
||||||
del self.recursive_objects[node]
|
|
||||||
if deep:
|
|
||||||
self.deep_construct = old_deep
|
|
||||||
return data
|
|
||||||
|
|
||||||
def construct_scalar(self, node):
|
|
||||||
if not isinstance(node, ScalarNode):
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"expected a scalar node, but found %s" % node.id,
|
|
||||||
node.start_mark)
|
|
||||||
return node.value
|
|
||||||
|
|
||||||
def construct_sequence(self, node, deep=False):
|
|
||||||
if not isinstance(node, SequenceNode):
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"expected a sequence node, but found %s" % node.id,
|
|
||||||
node.start_mark)
|
|
||||||
return [self.construct_object(child, deep=deep)
|
|
||||||
for child in node.value]
|
|
||||||
|
|
||||||
def construct_mapping(self, node, deep=False):
|
|
||||||
if not isinstance(node, MappingNode):
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"expected a mapping node, but found %s" % node.id,
|
|
||||||
node.start_mark)
|
|
||||||
mapping = {}
|
|
||||||
for key_node, value_node in node.value:
|
|
||||||
key = self.construct_object(key_node, deep=deep)
|
|
||||||
if not isinstance(key, collections.abc.Hashable):
|
|
||||||
raise ConstructorError("while constructing a mapping", node.start_mark,
|
|
||||||
"found unhashable key", key_node.start_mark)
|
|
||||||
value = self.construct_object(value_node, deep=deep)
|
|
||||||
mapping[key] = value
|
|
||||||
return mapping
|
|
||||||
|
|
||||||
def construct_pairs(self, node, deep=False):
|
|
||||||
if not isinstance(node, MappingNode):
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"expected a mapping node, but found %s" % node.id,
|
|
||||||
node.start_mark)
|
|
||||||
pairs = []
|
|
||||||
for key_node, value_node in node.value:
|
|
||||||
key = self.construct_object(key_node, deep=deep)
|
|
||||||
value = self.construct_object(value_node, deep=deep)
|
|
||||||
pairs.append((key, value))
|
|
||||||
return pairs
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_constructor(cls, tag, constructor):
|
|
||||||
if not 'yaml_constructors' in cls.__dict__:
|
|
||||||
cls.yaml_constructors = cls.yaml_constructors.copy()
|
|
||||||
cls.yaml_constructors[tag] = constructor
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_multi_constructor(cls, tag_prefix, multi_constructor):
|
|
||||||
if not 'yaml_multi_constructors' in cls.__dict__:
|
|
||||||
cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
|
|
||||||
cls.yaml_multi_constructors[tag_prefix] = multi_constructor
|
|
||||||
|
|
||||||
class SafeConstructor(BaseConstructor):
|
|
||||||
|
|
||||||
def construct_scalar(self, node):
|
|
||||||
if isinstance(node, MappingNode):
|
|
||||||
for key_node, value_node in node.value:
|
|
||||||
if key_node.tag == 'tag:yaml.org,2002:value':
|
|
||||||
return self.construct_scalar(value_node)
|
|
||||||
return super().construct_scalar(node)
|
|
||||||
|
|
||||||
def flatten_mapping(self, node):
|
|
||||||
merge = []
|
|
||||||
index = 0
|
|
||||||
while index < len(node.value):
|
|
||||||
key_node, value_node = node.value[index]
|
|
||||||
if key_node.tag == 'tag:yaml.org,2002:merge':
|
|
||||||
del node.value[index]
|
|
||||||
if isinstance(value_node, MappingNode):
|
|
||||||
self.flatten_mapping(value_node)
|
|
||||||
merge.extend(value_node.value)
|
|
||||||
elif isinstance(value_node, SequenceNode):
|
|
||||||
submerge = []
|
|
||||||
for subnode in value_node.value:
|
|
||||||
if not isinstance(subnode, MappingNode):
|
|
||||||
raise ConstructorError("while constructing a mapping",
|
|
||||||
node.start_mark,
|
|
||||||
"expected a mapping for merging, but found %s"
|
|
||||||
% subnode.id, subnode.start_mark)
|
|
||||||
self.flatten_mapping(subnode)
|
|
||||||
submerge.append(subnode.value)
|
|
||||||
submerge.reverse()
|
|
||||||
for value in submerge:
|
|
||||||
merge.extend(value)
|
|
||||||
else:
|
|
||||||
raise ConstructorError("while constructing a mapping", node.start_mark,
|
|
||||||
"expected a mapping or list of mappings for merging, but found %s"
|
|
||||||
% value_node.id, value_node.start_mark)
|
|
||||||
elif key_node.tag == 'tag:yaml.org,2002:value':
|
|
||||||
key_node.tag = 'tag:yaml.org,2002:str'
|
|
||||||
index += 1
|
|
||||||
else:
|
|
||||||
index += 1
|
|
||||||
if merge:
|
|
||||||
node.value = merge + node.value
|
|
||||||
|
|
||||||
def construct_mapping(self, node, deep=False):
|
|
||||||
if isinstance(node, MappingNode):
|
|
||||||
self.flatten_mapping(node)
|
|
||||||
return super().construct_mapping(node, deep=deep)
|
|
||||||
|
|
||||||
def construct_yaml_null(self, node):
|
|
||||||
self.construct_scalar(node)
|
|
||||||
return None
|
|
||||||
|
|
||||||
bool_values = {
|
|
||||||
'yes': True,
|
|
||||||
'no': False,
|
|
||||||
'true': True,
|
|
||||||
'false': False,
|
|
||||||
'on': True,
|
|
||||||
'off': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
def construct_yaml_bool(self, node):
|
|
||||||
value = self.construct_scalar(node)
|
|
||||||
return self.bool_values[value.lower()]
|
|
||||||
|
|
||||||
def construct_yaml_int(self, node):
|
|
||||||
value = self.construct_scalar(node)
|
|
||||||
value = value.replace('_', '')
|
|
||||||
sign = +1
|
|
||||||
if value[0] == '-':
|
|
||||||
sign = -1
|
|
||||||
if value[0] in '+-':
|
|
||||||
value = value[1:]
|
|
||||||
if value == '0':
|
|
||||||
return 0
|
|
||||||
elif value.startswith('0b'):
|
|
||||||
return sign*int(value[2:], 2)
|
|
||||||
elif value.startswith('0x'):
|
|
||||||
return sign*int(value[2:], 16)
|
|
||||||
elif value[0] == '0':
|
|
||||||
return sign*int(value, 8)
|
|
||||||
elif ':' in value:
|
|
||||||
digits = [int(part) for part in value.split(':')]
|
|
||||||
digits.reverse()
|
|
||||||
base = 1
|
|
||||||
value = 0
|
|
||||||
for digit in digits:
|
|
||||||
value += digit*base
|
|
||||||
base *= 60
|
|
||||||
return sign*value
|
|
||||||
else:
|
|
||||||
return sign*int(value)
|
|
||||||
|
|
||||||
inf_value = 1e300
|
|
||||||
while inf_value != inf_value*inf_value:
|
|
||||||
inf_value *= inf_value
|
|
||||||
nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99).
|
|
||||||
|
|
||||||
def construct_yaml_float(self, node):
|
|
||||||
value = self.construct_scalar(node)
|
|
||||||
value = value.replace('_', '').lower()
|
|
||||||
sign = +1
|
|
||||||
if value[0] == '-':
|
|
||||||
sign = -1
|
|
||||||
if value[0] in '+-':
|
|
||||||
value = value[1:]
|
|
||||||
if value == '.inf':
|
|
||||||
return sign*self.inf_value
|
|
||||||
elif value == '.nan':
|
|
||||||
return self.nan_value
|
|
||||||
elif ':' in value:
|
|
||||||
digits = [float(part) for part in value.split(':')]
|
|
||||||
digits.reverse()
|
|
||||||
base = 1
|
|
||||||
value = 0.0
|
|
||||||
for digit in digits:
|
|
||||||
value += digit*base
|
|
||||||
base *= 60
|
|
||||||
return sign*value
|
|
||||||
else:
|
|
||||||
return sign*float(value)
|
|
||||||
|
|
||||||
def construct_yaml_binary(self, node):
|
|
||||||
try:
|
|
||||||
value = self.construct_scalar(node).encode('ascii')
|
|
||||||
except UnicodeEncodeError as exc:
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"failed to convert base64 data into ascii: %s" % exc,
|
|
||||||
node.start_mark)
|
|
||||||
try:
|
|
||||||
if hasattr(base64, 'decodebytes'):
|
|
||||||
return base64.decodebytes(value)
|
|
||||||
else:
|
|
||||||
return base64.decodestring(value)
|
|
||||||
except binascii.Error as exc:
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"failed to decode base64 data: %s" % exc, node.start_mark)
|
|
||||||
|
|
||||||
timestamp_regexp = re.compile(
|
|
||||||
r'''^(?P<year>[0-9][0-9][0-9][0-9])
|
|
||||||
-(?P<month>[0-9][0-9]?)
|
|
||||||
-(?P<day>[0-9][0-9]?)
|
|
||||||
(?:(?:[Tt]|[ \t]+)
|
|
||||||
(?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 construct_yaml_timestamp(self, node):
|
|
||||||
value = self.construct_scalar(node)
|
|
||||||
match = self.timestamp_regexp.match(node.value)
|
|
||||||
values = match.groupdict()
|
|
||||||
year = int(values['year'])
|
|
||||||
month = int(values['month'])
|
|
||||||
day = int(values['day'])
|
|
||||||
if not values['hour']:
|
|
||||||
return datetime.date(year, month, day)
|
|
||||||
hour = int(values['hour'])
|
|
||||||
minute = int(values['minute'])
|
|
||||||
second = int(values['second'])
|
|
||||||
fraction = 0
|
|
||||||
tzinfo = None
|
|
||||||
if values['fraction']:
|
|
||||||
fraction = values['fraction'][:6]
|
|
||||||
while len(fraction) < 6:
|
|
||||||
fraction += '0'
|
|
||||||
fraction = int(fraction)
|
|
||||||
if values['tz_sign']:
|
|
||||||
tz_hour = int(values['tz_hour'])
|
|
||||||
tz_minute = int(values['tz_minute'] or 0)
|
|
||||||
delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute)
|
|
||||||
if values['tz_sign'] == '-':
|
|
||||||
delta = -delta
|
|
||||||
tzinfo = datetime.timezone(delta)
|
|
||||||
elif values['tz']:
|
|
||||||
tzinfo = datetime.timezone.utc
|
|
||||||
return datetime.datetime(year, month, day, hour, minute, second, fraction,
|
|
||||||
tzinfo=tzinfo)
|
|
||||||
|
|
||||||
def construct_yaml_omap(self, node):
|
|
||||||
# Note: we do not check for duplicate keys, because it's too
|
|
||||||
# CPU-expensive.
|
|
||||||
omap = []
|
|
||||||
yield omap
|
|
||||||
if not isinstance(node, SequenceNode):
|
|
||||||
raise ConstructorError("while constructing an ordered map", node.start_mark,
|
|
||||||
"expected a sequence, but found %s" % node.id, node.start_mark)
|
|
||||||
for subnode in node.value:
|
|
||||||
if not isinstance(subnode, MappingNode):
|
|
||||||
raise ConstructorError("while constructing an ordered map", node.start_mark,
|
|
||||||
"expected a mapping of length 1, but found %s" % subnode.id,
|
|
||||||
subnode.start_mark)
|
|
||||||
if len(subnode.value) != 1:
|
|
||||||
raise ConstructorError("while constructing an ordered map", node.start_mark,
|
|
||||||
"expected a single mapping item, but found %d items" % len(subnode.value),
|
|
||||||
subnode.start_mark)
|
|
||||||
key_node, value_node = subnode.value[0]
|
|
||||||
key = self.construct_object(key_node)
|
|
||||||
value = self.construct_object(value_node)
|
|
||||||
omap.append((key, value))
|
|
||||||
|
|
||||||
def construct_yaml_pairs(self, node):
|
|
||||||
# Note: the same code as `construct_yaml_omap`.
|
|
||||||
pairs = []
|
|
||||||
yield pairs
|
|
||||||
if not isinstance(node, SequenceNode):
|
|
||||||
raise ConstructorError("while constructing pairs", node.start_mark,
|
|
||||||
"expected a sequence, but found %s" % node.id, node.start_mark)
|
|
||||||
for subnode in node.value:
|
|
||||||
if not isinstance(subnode, MappingNode):
|
|
||||||
raise ConstructorError("while constructing pairs", node.start_mark,
|
|
||||||
"expected a mapping of length 1, but found %s" % subnode.id,
|
|
||||||
subnode.start_mark)
|
|
||||||
if len(subnode.value) != 1:
|
|
||||||
raise ConstructorError("while constructing pairs", node.start_mark,
|
|
||||||
"expected a single mapping item, but found %d items" % len(subnode.value),
|
|
||||||
subnode.start_mark)
|
|
||||||
key_node, value_node = subnode.value[0]
|
|
||||||
key = self.construct_object(key_node)
|
|
||||||
value = self.construct_object(value_node)
|
|
||||||
pairs.append((key, value))
|
|
||||||
|
|
||||||
def construct_yaml_set(self, node):
|
|
||||||
data = set()
|
|
||||||
yield data
|
|
||||||
value = self.construct_mapping(node)
|
|
||||||
data.update(value)
|
|
||||||
|
|
||||||
def construct_yaml_str(self, node):
|
|
||||||
return self.construct_scalar(node)
|
|
||||||
|
|
||||||
def construct_yaml_seq(self, node):
|
|
||||||
data = []
|
|
||||||
yield data
|
|
||||||
data.extend(self.construct_sequence(node))
|
|
||||||
|
|
||||||
def construct_yaml_map(self, node):
|
|
||||||
data = {}
|
|
||||||
yield data
|
|
||||||
value = self.construct_mapping(node)
|
|
||||||
data.update(value)
|
|
||||||
|
|
||||||
def construct_yaml_object(self, node, cls):
|
|
||||||
data = cls.__new__(cls)
|
|
||||||
yield data
|
|
||||||
if hasattr(data, '__setstate__'):
|
|
||||||
state = self.construct_mapping(node, deep=True)
|
|
||||||
data.__setstate__(state)
|
|
||||||
else:
|
|
||||||
state = self.construct_mapping(node)
|
|
||||||
data.__dict__.update(state)
|
|
||||||
|
|
||||||
def construct_undefined(self, node):
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"could not determine a constructor for the tag %r" % node.tag,
|
|
||||||
node.start_mark)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:null',
|
|
||||||
SafeConstructor.construct_yaml_null)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:bool',
|
|
||||||
SafeConstructor.construct_yaml_bool)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:int',
|
|
||||||
SafeConstructor.construct_yaml_int)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:float',
|
|
||||||
SafeConstructor.construct_yaml_float)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:binary',
|
|
||||||
SafeConstructor.construct_yaml_binary)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:timestamp',
|
|
||||||
SafeConstructor.construct_yaml_timestamp)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:omap',
|
|
||||||
SafeConstructor.construct_yaml_omap)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:pairs',
|
|
||||||
SafeConstructor.construct_yaml_pairs)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:set',
|
|
||||||
SafeConstructor.construct_yaml_set)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:str',
|
|
||||||
SafeConstructor.construct_yaml_str)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:seq',
|
|
||||||
SafeConstructor.construct_yaml_seq)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:map',
|
|
||||||
SafeConstructor.construct_yaml_map)
|
|
||||||
|
|
||||||
SafeConstructor.add_constructor(None,
|
|
||||||
SafeConstructor.construct_undefined)
|
|
||||||
|
|
||||||
class FullConstructor(SafeConstructor):
|
|
||||||
# 'extend' is blacklisted because it is used by
|
|
||||||
# construct_python_object_apply to add `listitems` to a newly generate
|
|
||||||
# python instance
|
|
||||||
def get_state_keys_blacklist(self):
|
|
||||||
return ['^extend$', '^__.*__$']
|
|
||||||
|
|
||||||
def get_state_keys_blacklist_regexp(self):
|
|
||||||
if not hasattr(self, 'state_keys_blacklist_regexp'):
|
|
||||||
self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')')
|
|
||||||
return self.state_keys_blacklist_regexp
|
|
||||||
|
|
||||||
def construct_python_str(self, node):
|
|
||||||
return self.construct_scalar(node)
|
|
||||||
|
|
||||||
def construct_python_unicode(self, node):
|
|
||||||
return self.construct_scalar(node)
|
|
||||||
|
|
||||||
def construct_python_bytes(self, node):
|
|
||||||
try:
|
|
||||||
value = self.construct_scalar(node).encode('ascii')
|
|
||||||
except UnicodeEncodeError as exc:
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"failed to convert base64 data into ascii: %s" % exc,
|
|
||||||
node.start_mark)
|
|
||||||
try:
|
|
||||||
if hasattr(base64, 'decodebytes'):
|
|
||||||
return base64.decodebytes(value)
|
|
||||||
else:
|
|
||||||
return base64.decodestring(value)
|
|
||||||
except binascii.Error as exc:
|
|
||||||
raise ConstructorError(None, None,
|
|
||||||
"failed to decode base64 data: %s" % exc, node.start_mark)
|
|
||||||
|
|
||||||
def construct_python_long(self, node):
|
|
||||||
return self.construct_yaml_int(node)
|
|
||||||
|
|
||||||
def construct_python_complex(self, node):
|
|
||||||
return complex(self.construct_scalar(node))
|
|
||||||
|
|
||||||
def construct_python_tuple(self, node):
|
|
||||||
return tuple(self.construct_sequence(node))
|
|
||||||
|
|
||||||
def find_python_module(self, name, mark, unsafe=False):
|
|
||||||
if not name:
|
|
||||||
raise ConstructorError("while constructing a Python module", mark,
|
|
||||||
"expected non-empty name appended to the tag", mark)
|
|
||||||
if unsafe:
|
|
||||||
try:
|
|
||||||
__import__(name)
|
|
||||||
except ImportError as exc:
|
|
||||||
raise ConstructorError("while constructing a Python module", mark,
|
|
||||||
"cannot find module %r (%s)" % (name, exc), mark)
|
|
||||||
if name not in sys.modules:
|
|
||||||
raise ConstructorError("while constructing a Python module", mark,
|
|
||||||
"module %r is not imported" % name, mark)
|
|
||||||
return sys.modules[name]
|
|
||||||
|
|
||||||
def find_python_name(self, name, mark, unsafe=False):
|
|
||||||
if not name:
|
|
||||||
raise ConstructorError("while constructing a Python object", mark,
|
|
||||||
"expected non-empty name appended to the tag", mark)
|
|
||||||
if '.' in name:
|
|
||||||
module_name, object_name = name.rsplit('.', 1)
|
|
||||||
else:
|
|
||||||
module_name = 'builtins'
|
|
||||||
object_name = name
|
|
||||||
if unsafe:
|
|
||||||
try:
|
|
||||||
__import__(module_name)
|
|
||||||
except ImportError as exc:
|
|
||||||
raise ConstructorError("while constructing a Python object", mark,
|
|
||||||
"cannot find module %r (%s)" % (module_name, exc), mark)
|
|
||||||
if module_name not in sys.modules:
|
|
||||||
raise ConstructorError("while constructing a Python object", mark,
|
|
||||||
"module %r is not imported" % module_name, mark)
|
|
||||||
module = sys.modules[module_name]
|
|
||||||
if not hasattr(module, object_name):
|
|
||||||
raise ConstructorError("while constructing a Python object", mark,
|
|
||||||
"cannot find %r in the module %r"
|
|
||||||
% (object_name, module.__name__), mark)
|
|
||||||
return getattr(module, object_name)
|
|
||||||
|
|
||||||
def construct_python_name(self, suffix, node):
|
|
||||||
value = self.construct_scalar(node)
|
|
||||||
if value:
|
|
||||||
raise ConstructorError("while constructing a Python name", node.start_mark,
|
|
||||||
"expected the empty value, but found %r" % value, node.start_mark)
|
|
||||||
return self.find_python_name(suffix, node.start_mark)
|
|
||||||
|
|
||||||
def construct_python_module(self, suffix, node):
|
|
||||||
value = self.construct_scalar(node)
|
|
||||||
if value:
|
|
||||||
raise ConstructorError("while constructing a Python module", node.start_mark,
|
|
||||||
"expected the empty value, but found %r" % value, node.start_mark)
|
|
||||||
return self.find_python_module(suffix, node.start_mark)
|
|
||||||
|
|
||||||
def make_python_instance(self, suffix, node,
|
|
||||||
args=None, kwds=None, newobj=False, unsafe=False):
|
|
||||||
if not args:
|
|
||||||
args = []
|
|
||||||
if not kwds:
|
|
||||||
kwds = {}
|
|
||||||
cls = self.find_python_name(suffix, node.start_mark)
|
|
||||||
if not (unsafe or isinstance(cls, type)):
|
|
||||||
raise ConstructorError("while constructing a Python instance", node.start_mark,
|
|
||||||
"expected a class, but found %r" % type(cls),
|
|
||||||
node.start_mark)
|
|
||||||
if newobj and isinstance(cls, type):
|
|
||||||
return cls.__new__(cls, *args, **kwds)
|
|
||||||
else:
|
|
||||||
return cls(*args, **kwds)
|
|
||||||
|
|
||||||
def set_python_instance_state(self, instance, state, unsafe=False):
|
|
||||||
if hasattr(instance, '__setstate__'):
|
|
||||||
instance.__setstate__(state)
|
|
||||||
else:
|
|
||||||
slotstate = {}
|
|
||||||
if isinstance(state, tuple) and len(state) == 2:
|
|
||||||
state, slotstate = state
|
|
||||||
if hasattr(instance, '__dict__'):
|
|
||||||
if not unsafe and state:
|
|
||||||
for key in state.keys():
|
|
||||||
self.check_state_key(key)
|
|
||||||
instance.__dict__.update(state)
|
|
||||||
elif state:
|
|
||||||
slotstate.update(state)
|
|
||||||
for key, value in slotstate.items():
|
|
||||||
if not unsafe:
|
|
||||||
self.check_state_key(key)
|
|
||||||
setattr(instance, key, value)
|
|
||||||
|
|
||||||
def construct_python_object(self, suffix, node):
|
|
||||||
# Format:
|
|
||||||
# !!python/object:module.name { ... state ... }
|
|
||||||
instance = self.make_python_instance(suffix, node, newobj=True)
|
|
||||||
yield instance
|
|
||||||
deep = hasattr(instance, '__setstate__')
|
|
||||||
state = self.construct_mapping(node, deep=deep)
|
|
||||||
self.set_python_instance_state(instance, state)
|
|
||||||
|
|
||||||
def construct_python_object_apply(self, suffix, node, newobj=False):
|
|
||||||
# Format:
|
|
||||||
# !!python/object/apply # (or !!python/object/new)
|
|
||||||
# args: [ ... arguments ... ]
|
|
||||||
# kwds: { ... keywords ... }
|
|
||||||
# state: ... state ...
|
|
||||||
# listitems: [ ... listitems ... ]
|
|
||||||
# dictitems: { ... dictitems ... }
|
|
||||||
# or short format:
|
|
||||||
# !!python/object/apply [ ... arguments ... ]
|
|
||||||
# The difference between !!python/object/apply and !!python/object/new
|
|
||||||
# is how an object is created, check make_python_instance for details.
|
|
||||||
if isinstance(node, SequenceNode):
|
|
||||||
args = self.construct_sequence(node, deep=True)
|
|
||||||
kwds = {}
|
|
||||||
state = {}
|
|
||||||
listitems = []
|
|
||||||
dictitems = {}
|
|
||||||
else:
|
|
||||||
value = self.construct_mapping(node, deep=True)
|
|
||||||
args = value.get('args', [])
|
|
||||||
kwds = value.get('kwds', {})
|
|
||||||
state = value.get('state', {})
|
|
||||||
listitems = value.get('listitems', [])
|
|
||||||
dictitems = value.get('dictitems', {})
|
|
||||||
instance = self.make_python_instance(suffix, node, args, kwds, newobj)
|
|
||||||
if state:
|
|
||||||
self.set_python_instance_state(instance, state)
|
|
||||||
if listitems:
|
|
||||||
instance.extend(listitems)
|
|
||||||
if dictitems:
|
|
||||||
for key in dictitems:
|
|
||||||
instance[key] = dictitems[key]
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def construct_python_object_new(self, suffix, node):
|
|
||||||
return self.construct_python_object_apply(suffix, node, newobj=True)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/none',
|
|
||||||
FullConstructor.construct_yaml_null)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/bool',
|
|
||||||
FullConstructor.construct_yaml_bool)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/str',
|
|
||||||
FullConstructor.construct_python_str)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/unicode',
|
|
||||||
FullConstructor.construct_python_unicode)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/bytes',
|
|
||||||
FullConstructor.construct_python_bytes)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/int',
|
|
||||||
FullConstructor.construct_yaml_int)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/long',
|
|
||||||
FullConstructor.construct_python_long)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/float',
|
|
||||||
FullConstructor.construct_yaml_float)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/complex',
|
|
||||||
FullConstructor.construct_python_complex)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/list',
|
|
||||||
FullConstructor.construct_yaml_seq)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/tuple',
|
|
||||||
FullConstructor.construct_python_tuple)
|
|
||||||
|
|
||||||
FullConstructor.add_constructor(
|
|
||||||
'tag:yaml.org,2002:python/dict',
|
|
||||||
FullConstructor.construct_yaml_map)
|
|
||||||
|
|
||||||
FullConstructor.add_multi_constructor(
|
|
||||||
'tag:yaml.org,2002:python/name:',
|
|
||||||
FullConstructor.construct_python_name)
|
|
||||||
|
|
||||||
FullConstructor.add_multi_constructor(
|
|
||||||
'tag:yaml.org,2002:python/module:',
|
|
||||||
FullConstructor.construct_python_module)
|
|
||||||
|
|
||||||
FullConstructor.add_multi_constructor(
|
|
||||||
'tag:yaml.org,2002:python/object:',
|
|
||||||
FullConstructor.construct_python_object)
|
|
||||||
|
|
||||||
FullConstructor.add_multi_constructor(
|
|
||||||
'tag:yaml.org,2002:python/object/new:',
|
|
||||||
FullConstructor.construct_python_object_new)
|
|
||||||
|
|
||||||
class UnsafeConstructor(FullConstructor):
|
|
||||||
|
|
||||||
def find_python_module(self, name, mark):
|
|
||||||
return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True)
|
|
||||||
|
|
||||||
def find_python_name(self, name, mark):
|
|
||||||
return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)
|
|
||||||
|
|
||||||
def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False):
|
|
||||||
return super(UnsafeConstructor, self).make_python_instance(
|
|
||||||
suffix, node, args, kwds, newobj, unsafe=True)
|
|
||||||
|
|
||||||
def set_python_instance_state(self, instance, state):
|
|
||||||
return super(UnsafeConstructor, self).set_python_instance_state(
|
|
||||||
instance, state, unsafe=True)
|
|
||||||
|
|
||||||
UnsafeConstructor.add_multi_constructor(
|
|
||||||
'tag:yaml.org,2002:python/object/apply:',
|
|
||||||
UnsafeConstructor.construct_python_object_apply)
|
|
||||||
|
|
||||||
# Constructor is same as UnsafeConstructor. Need to leave this in place in case
|
|
||||||
# people have extended it directly.
|
|
||||||
class Constructor(UnsafeConstructor):
|
|
||||||
pass
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
|
|
||||||
__all__ = [
|
|
||||||
'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader',
|
|
||||||
'CBaseDumper', 'CSafeDumper', 'CDumper'
|
|
||||||
]
|
|
||||||
|
|
||||||
from _yaml import CParser, CEmitter
|
|
||||||
|
|
||||||
from .constructor import *
|
|
||||||
|
|
||||||
from .serializer import *
|
|
||||||
from .representer import *
|
|
||||||
|
|
||||||
from .resolver import *
|
|
||||||
|
|
||||||
class CBaseLoader(CParser, BaseConstructor, BaseResolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
CParser.__init__(self, stream)
|
|
||||||
BaseConstructor.__init__(self)
|
|
||||||
BaseResolver.__init__(self)
|
|
||||||
|
|
||||||
class CSafeLoader(CParser, SafeConstructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
CParser.__init__(self, stream)
|
|
||||||
SafeConstructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class CFullLoader(CParser, FullConstructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
CParser.__init__(self, stream)
|
|
||||||
FullConstructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class CUnsafeLoader(CParser, UnsafeConstructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
CParser.__init__(self, stream)
|
|
||||||
UnsafeConstructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class CLoader(CParser, Constructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
CParser.__init__(self, stream)
|
|
||||||
Constructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver):
|
|
||||||
|
|
||||||
def __init__(self, stream,
|
|
||||||
default_style=None, default_flow_style=False,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None, sort_keys=True):
|
|
||||||
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)
|
|
||||||
Representer.__init__(self, default_style=default_style,
|
|
||||||
default_flow_style=default_flow_style, sort_keys=sort_keys)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class CSafeDumper(CEmitter, SafeRepresenter, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream,
|
|
||||||
default_style=None, default_flow_style=False,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None, sort_keys=True):
|
|
||||||
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)
|
|
||||||
SafeRepresenter.__init__(self, default_style=default_style,
|
|
||||||
default_flow_style=default_flow_style, sort_keys=sort_keys)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class CDumper(CEmitter, Serializer, Representer, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream,
|
|
||||||
default_style=None, default_flow_style=False,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None, sort_keys=True):
|
|
||||||
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)
|
|
||||||
Representer.__init__(self, default_style=default_style,
|
|
||||||
default_flow_style=default_flow_style, sort_keys=sort_keys)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
|
|
||||||
__all__ = ['BaseDumper', 'SafeDumper', 'Dumper']
|
|
||||||
|
|
||||||
from .emitter import *
|
|
||||||
from .serializer import *
|
|
||||||
from .representer import *
|
|
||||||
from .resolver import *
|
|
||||||
|
|
||||||
class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver):
|
|
||||||
|
|
||||||
def __init__(self, stream,
|
|
||||||
default_style=None, default_flow_style=False,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None, sort_keys=True):
|
|
||||||
Emitter.__init__(self, stream, canonical=canonical,
|
|
||||||
indent=indent, width=width,
|
|
||||||
allow_unicode=allow_unicode, line_break=line_break)
|
|
||||||
Serializer.__init__(self, encoding=encoding,
|
|
||||||
explicit_start=explicit_start, explicit_end=explicit_end,
|
|
||||||
version=version, tags=tags)
|
|
||||||
Representer.__init__(self, default_style=default_style,
|
|
||||||
default_flow_style=default_flow_style, sort_keys=sort_keys)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream,
|
|
||||||
default_style=None, default_flow_style=False,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None, sort_keys=True):
|
|
||||||
Emitter.__init__(self, stream, canonical=canonical,
|
|
||||||
indent=indent, width=width,
|
|
||||||
allow_unicode=allow_unicode, line_break=line_break)
|
|
||||||
Serializer.__init__(self, encoding=encoding,
|
|
||||||
explicit_start=explicit_start, explicit_end=explicit_end,
|
|
||||||
version=version, tags=tags)
|
|
||||||
SafeRepresenter.__init__(self, default_style=default_style,
|
|
||||||
default_flow_style=default_flow_style, sort_keys=sort_keys)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class Dumper(Emitter, Serializer, Representer, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream,
|
|
||||||
default_style=None, default_flow_style=False,
|
|
||||||
canonical=None, indent=None, width=None,
|
|
||||||
allow_unicode=None, line_break=None,
|
|
||||||
encoding=None, explicit_start=None, explicit_end=None,
|
|
||||||
version=None, tags=None, sort_keys=True):
|
|
||||||
Emitter.__init__(self, stream, canonical=canonical,
|
|
||||||
indent=indent, width=width,
|
|
||||||
allow_unicode=allow_unicode, line_break=line_break)
|
|
||||||
Serializer.__init__(self, encoding=encoding,
|
|
||||||
explicit_start=explicit_start, explicit_end=explicit_end,
|
|
||||||
version=version, tags=tags)
|
|
||||||
Representer.__init__(self, default_style=default_style,
|
|
||||||
default_flow_style=default_flow_style, sort_keys=sort_keys)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
1137
src/yaml/emitter.py
1137
src/yaml/emitter.py
File diff suppressed because it is too large
Load Diff
@ -1,75 +0,0 @@
|
|||||||
|
|
||||||
__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError']
|
|
||||||
|
|
||||||
class Mark:
|
|
||||||
|
|
||||||
def __init__(self, name, index, line, column, buffer, pointer):
|
|
||||||
self.name = name
|
|
||||||
self.index = index
|
|
||||||
self.line = line
|
|
||||||
self.column = column
|
|
||||||
self.buffer = buffer
|
|
||||||
self.pointer = pointer
|
|
||||||
|
|
||||||
def get_snippet(self, indent=4, max_length=75):
|
|
||||||
if self.buffer is None:
|
|
||||||
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]
|
|
||||||
return ' '*indent + head + snippet + tail + '\n' \
|
|
||||||
+ ' '*(indent+self.pointer-start+len(head)) + '^'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
snippet = self.get_snippet()
|
|
||||||
where = " in \"%s\", line %d, column %d" \
|
|
||||||
% (self.name, self.line+1, self.column+1)
|
|
||||||
if snippet is not None:
|
|
||||||
where += ":\n"+snippet
|
|
||||||
return where
|
|
||||||
|
|
||||||
class YAMLError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MarkedYAMLError(YAMLError):
|
|
||||||
|
|
||||||
def __init__(self, context=None, context_mark=None,
|
|
||||||
problem=None, problem_mark=None, note=None):
|
|
||||||
self.context = context
|
|
||||||
self.context_mark = context_mark
|
|
||||||
self.problem = problem
|
|
||||||
self.problem_mark = problem_mark
|
|
||||||
self.note = note
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
lines = []
|
|
||||||
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:
|
|
||||||
lines.append(self.note)
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
|
|
||||||
# Abstract classes.
|
|
||||||
|
|
||||||
class Event(object):
|
|
||||||
def __init__(self, start_mark=None, end_mark=None):
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
def __repr__(self):
|
|
||||||
attributes = [key for key in ['anchor', 'tag', 'implicit', 'value']
|
|
||||||
if hasattr(self, key)]
|
|
||||||
arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
|
|
||||||
for key in attributes])
|
|
||||||
return '%s(%s)' % (self.__class__.__name__, arguments)
|
|
||||||
|
|
||||||
class NodeEvent(Event):
|
|
||||||
def __init__(self, anchor, start_mark=None, end_mark=None):
|
|
||||||
self.anchor = anchor
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
|
|
||||||
class CollectionStartEvent(NodeEvent):
|
|
||||||
def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None,
|
|
||||||
flow_style=None):
|
|
||||||
self.anchor = anchor
|
|
||||||
self.tag = tag
|
|
||||||
self.implicit = implicit
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.flow_style = flow_style
|
|
||||||
|
|
||||||
class CollectionEndEvent(Event):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Implementations.
|
|
||||||
|
|
||||||
class StreamStartEvent(Event):
|
|
||||||
def __init__(self, start_mark=None, end_mark=None, encoding=None):
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.encoding = encoding
|
|
||||||
|
|
||||||
class StreamEndEvent(Event):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DocumentStartEvent(Event):
|
|
||||||
def __init__(self, start_mark=None, end_mark=None,
|
|
||||||
explicit=None, version=None, tags=None):
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.explicit = explicit
|
|
||||||
self.version = version
|
|
||||||
self.tags = tags
|
|
||||||
|
|
||||||
class DocumentEndEvent(Event):
|
|
||||||
def __init__(self, start_mark=None, end_mark=None,
|
|
||||||
explicit=None):
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.explicit = explicit
|
|
||||||
|
|
||||||
class AliasEvent(NodeEvent):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ScalarEvent(NodeEvent):
|
|
||||||
def __init__(self, anchor, tag, implicit, value,
|
|
||||||
start_mark=None, end_mark=None, style=None):
|
|
||||||
self.anchor = anchor
|
|
||||||
self.tag = tag
|
|
||||||
self.implicit = implicit
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.style = style
|
|
||||||
|
|
||||||
class SequenceStartEvent(CollectionStartEvent):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SequenceEndEvent(CollectionEndEvent):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MappingStartEvent(CollectionStartEvent):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MappingEndEvent(CollectionEndEvent):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
|
|
||||||
__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader']
|
|
||||||
|
|
||||||
from .reader import *
|
|
||||||
from .scanner import *
|
|
||||||
from .parser import *
|
|
||||||
from .composer import *
|
|
||||||
from .constructor import *
|
|
||||||
from .resolver import *
|
|
||||||
|
|
||||||
class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
Reader.__init__(self, stream)
|
|
||||||
Scanner.__init__(self)
|
|
||||||
Parser.__init__(self)
|
|
||||||
Composer.__init__(self)
|
|
||||||
BaseConstructor.__init__(self)
|
|
||||||
BaseResolver.__init__(self)
|
|
||||||
|
|
||||||
class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
Reader.__init__(self, stream)
|
|
||||||
Scanner.__init__(self)
|
|
||||||
Parser.__init__(self)
|
|
||||||
Composer.__init__(self)
|
|
||||||
FullConstructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
Reader.__init__(self, stream)
|
|
||||||
Scanner.__init__(self)
|
|
||||||
Parser.__init__(self)
|
|
||||||
Composer.__init__(self)
|
|
||||||
SafeConstructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
Reader.__init__(self, stream)
|
|
||||||
Scanner.__init__(self)
|
|
||||||
Parser.__init__(self)
|
|
||||||
Composer.__init__(self)
|
|
||||||
Constructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
|
|
||||||
# UnsafeLoader is the same as Loader (which is and was always unsafe on
|
|
||||||
# untrusted input). Use of either Loader or UnsafeLoader should be rare, since
|
|
||||||
# FullLoad should be able to load almost all YAML safely. Loader is left intact
|
|
||||||
# to ensure backwards compatibility.
|
|
||||||
class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
Reader.__init__(self, stream)
|
|
||||||
Scanner.__init__(self)
|
|
||||||
Parser.__init__(self)
|
|
||||||
Composer.__init__(self)
|
|
||||||
Constructor.__init__(self)
|
|
||||||
Resolver.__init__(self)
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
class Node(object):
|
|
||||||
def __init__(self, tag, value, start_mark, end_mark):
|
|
||||||
self.tag = tag
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
def __repr__(self):
|
|
||||||
value = self.value
|
|
||||||
#if isinstance(value, list):
|
|
||||||
# if len(value) == 0:
|
|
||||||
# value = '<empty>'
|
|
||||||
# elif len(value) == 1:
|
|
||||||
# value = '<1 item>'
|
|
||||||
# else:
|
|
||||||
# value = '<%d items>' % len(value)
|
|
||||||
#else:
|
|
||||||
# if len(value) > 75:
|
|
||||||
# value = repr(value[:70]+u' ... ')
|
|
||||||
# else:
|
|
||||||
# value = repr(value)
|
|
||||||
value = repr(value)
|
|
||||||
return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value)
|
|
||||||
|
|
||||||
class ScalarNode(Node):
|
|
||||||
id = 'scalar'
|
|
||||||
def __init__(self, tag, value,
|
|
||||||
start_mark=None, end_mark=None, style=None):
|
|
||||||
self.tag = tag
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.style = style
|
|
||||||
|
|
||||||
class CollectionNode(Node):
|
|
||||||
def __init__(self, tag, value,
|
|
||||||
start_mark=None, end_mark=None, flow_style=None):
|
|
||||||
self.tag = tag
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.flow_style = flow_style
|
|
||||||
|
|
||||||
class SequenceNode(CollectionNode):
|
|
||||||
id = 'sequence'
|
|
||||||
|
|
||||||
class MappingNode(CollectionNode):
|
|
||||||
id = 'mapping'
|
|
||||||
|
|
||||||
@ -1,589 +0,0 @@
|
|||||||
|
|
||||||
# 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 }
|
|
||||||
|
|
||||||
__all__ = ['Parser', 'ParserError']
|
|
||||||
|
|
||||||
from .error import MarkedYAMLError
|
|
||||||
from .tokens import *
|
|
||||||
from .events import *
|
|
||||||
from .scanner import *
|
|
||||||
|
|
||||||
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):
|
|
||||||
self.current_event = None
|
|
||||||
self.yaml_version = None
|
|
||||||
self.tag_handles = {}
|
|
||||||
self.states = []
|
|
||||||
self.marks = []
|
|
||||||
self.state = self.parse_stream_start
|
|
||||||
|
|
||||||
def dispose(self):
|
|
||||||
# Reset the state attributes (to clear self-references)
|
|
||||||
self.states = []
|
|
||||||
self.state = None
|
|
||||||
|
|
||||||
def check_event(self, *choices):
|
|
||||||
# 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):
|
|
||||||
# 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):
|
|
||||||
# Get the next event and proceed further.
|
|
||||||
if self.current_event is None:
|
|
||||||
if self.state:
|
|
||||||
self.current_event = self.state()
|
|
||||||
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):
|
|
||||||
|
|
||||||
# Parse the stream start.
|
|
||||||
token = self.get_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):
|
|
||||||
|
|
||||||
# Parse an implicit document.
|
|
||||||
if not self.check_token(DirectiveToken, DocumentStartToken,
|
|
||||||
StreamEndToken):
|
|
||||||
self.tag_handles = self.DEFAULT_TAGS
|
|
||||||
token = self.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):
|
|
||||||
|
|
||||||
# Parse any extra document end indicators.
|
|
||||||
while self.check_token(DocumentEndToken):
|
|
||||||
self.get_token()
|
|
||||||
|
|
||||||
# Parse an explicit document.
|
|
||||||
if not self.check_token(StreamEndToken):
|
|
||||||
token = self.peek_token()
|
|
||||||
start_mark = token.start_mark
|
|
||||||
version, tags = self.process_directives()
|
|
||||||
if not self.check_token(DocumentStartToken):
|
|
||||||
raise ParserError(None, None,
|
|
||||||
"expected '<document start>', but found %r"
|
|
||||||
% self.peek_token().id,
|
|
||||||
self.peek_token().start_mark)
|
|
||||||
token = self.get_token()
|
|
||||||
end_mark = token.end_mark
|
|
||||||
event = DocumentStartEvent(start_mark, end_mark,
|
|
||||||
explicit=True, version=version, tags=tags)
|
|
||||||
self.states.append(self.parse_document_end)
|
|
||||||
self.state = self.parse_document_content
|
|
||||||
else:
|
|
||||||
# Parse the end of the stream.
|
|
||||||
token = self.get_token()
|
|
||||||
event = StreamEndEvent(token.start_mark, token.end_mark)
|
|
||||||
assert not self.states
|
|
||||||
assert not self.marks
|
|
||||||
self.state = None
|
|
||||||
return event
|
|
||||||
|
|
||||||
def parse_document_end(self):
|
|
||||||
|
|
||||||
# Parse the document end.
|
|
||||||
token = self.peek_token()
|
|
||||||
start_mark = end_mark = token.start_mark
|
|
||||||
explicit = False
|
|
||||||
if self.check_token(DocumentEndToken):
|
|
||||||
token = self.get_token()
|
|
||||||
end_mark = token.end_mark
|
|
||||||
explicit = True
|
|
||||||
event = DocumentEndEvent(start_mark, end_mark,
|
|
||||||
explicit=explicit)
|
|
||||||
|
|
||||||
# Prepare the next state.
|
|
||||||
self.state = self.parse_document_start
|
|
||||||
|
|
||||||
return event
|
|
||||||
|
|
||||||
def parse_document_content(self):
|
|
||||||
if self.check_token(DirectiveToken,
|
|
||||||
DocumentStartToken, DocumentEndToken, StreamEndToken):
|
|
||||||
event = self.process_empty_scalar(self.peek_token().start_mark)
|
|
||||||
self.state = self.states.pop()
|
|
||||||
return event
|
|
||||||
else:
|
|
||||||
return self.parse_block_node()
|
|
||||||
|
|
||||||
def process_directives(self):
|
|
||||||
self.yaml_version = None
|
|
||||||
self.tag_handles = {}
|
|
||||||
while self.check_token(DirectiveToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if token.name == 'YAML':
|
|
||||||
if self.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)
|
|
||||||
self.yaml_version = token.value
|
|
||||||
elif token.name == 'TAG':
|
|
||||||
handle, prefix = token.value
|
|
||||||
if handle in self.tag_handles:
|
|
||||||
raise ParserError(None, None,
|
|
||||||
"duplicate tag handle %r" % handle,
|
|
||||||
token.start_mark)
|
|
||||||
self.tag_handles[handle] = prefix
|
|
||||||
if self.tag_handles:
|
|
||||||
value = self.yaml_version, self.tag_handles.copy()
|
|
||||||
else:
|
|
||||||
value = self.yaml_version, None
|
|
||||||
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):
|
|
||||||
return self.parse_node(block=True)
|
|
||||||
|
|
||||||
def parse_flow_node(self):
|
|
||||||
return self.parse_node()
|
|
||||||
|
|
||||||
def parse_block_node_or_indentless_sequence(self):
|
|
||||||
return self.parse_node(block=True, indentless_sequence=True)
|
|
||||||
|
|
||||||
def parse_node(self, block=False, indentless_sequence=False):
|
|
||||||
if self.check_token(AliasToken):
|
|
||||||
token = self.get_token()
|
|
||||||
event = AliasEvent(token.value, token.start_mark, token.end_mark)
|
|
||||||
self.state = self.states.pop()
|
|
||||||
else:
|
|
||||||
anchor = None
|
|
||||||
tag = None
|
|
||||||
start_mark = end_mark = tag_mark = None
|
|
||||||
if self.check_token(AnchorToken):
|
|
||||||
token = self.get_token()
|
|
||||||
start_mark = token.start_mark
|
|
||||||
end_mark = token.end_mark
|
|
||||||
anchor = token.value
|
|
||||||
if self.check_token(TagToken):
|
|
||||||
token = self.get_token()
|
|
||||||
tag_mark = token.start_mark
|
|
||||||
end_mark = token.end_mark
|
|
||||||
tag = token.value
|
|
||||||
elif self.check_token(TagToken):
|
|
||||||
token = self.get_token()
|
|
||||||
start_mark = tag_mark = token.start_mark
|
|
||||||
end_mark = token.end_mark
|
|
||||||
tag = token.value
|
|
||||||
if self.check_token(AnchorToken):
|
|
||||||
token = self.get_token()
|
|
||||||
end_mark = token.end_mark
|
|
||||||
anchor = token.value
|
|
||||||
if tag is not None:
|
|
||||||
handle, suffix = tag
|
|
||||||
if handle is not None:
|
|
||||||
if handle not in self.tag_handles:
|
|
||||||
raise ParserError("while parsing a node", start_mark,
|
|
||||||
"found undefined tag handle %r" % handle,
|
|
||||||
tag_mark)
|
|
||||||
tag = self.tag_handles[handle]+suffix
|
|
||||||
else:
|
|
||||||
tag = suffix
|
|
||||||
#if tag == '!':
|
|
||||||
# raise ParserError("while parsing a node", start_mark,
|
|
||||||
# "found non-specific tag '!'", tag_mark,
|
|
||||||
# "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.")
|
|
||||||
if start_mark is None:
|
|
||||||
start_mark = end_mark = self.peek_token().start_mark
|
|
||||||
event = None
|
|
||||||
implicit = (tag is None or tag == '!')
|
|
||||||
if indentless_sequence and self.check_token(BlockEntryToken):
|
|
||||||
end_mark = self.peek_token().end_mark
|
|
||||||
event = SequenceStartEvent(anchor, tag, implicit,
|
|
||||||
start_mark, end_mark)
|
|
||||||
self.state = self.parse_indentless_sequence_entry
|
|
||||||
else:
|
|
||||||
if self.check_token(ScalarToken):
|
|
||||||
token = self.get_token()
|
|
||||||
end_mark = token.end_mark
|
|
||||||
if (token.plain and tag is None) or tag == '!':
|
|
||||||
implicit = (True, False)
|
|
||||||
elif tag is None:
|
|
||||||
implicit = (False, True)
|
|
||||||
else:
|
|
||||||
implicit = (False, False)
|
|
||||||
event = ScalarEvent(anchor, tag, implicit, token.value,
|
|
||||||
start_mark, end_mark, style=token.style)
|
|
||||||
self.state = self.states.pop()
|
|
||||||
elif self.check_token(FlowSequenceStartToken):
|
|
||||||
end_mark = self.peek_token().end_mark
|
|
||||||
event = SequenceStartEvent(anchor, tag, implicit,
|
|
||||||
start_mark, end_mark, flow_style=True)
|
|
||||||
self.state = self.parse_flow_sequence_first_entry
|
|
||||||
elif self.check_token(FlowMappingStartToken):
|
|
||||||
end_mark = self.peek_token().end_mark
|
|
||||||
event = MappingStartEvent(anchor, tag, implicit,
|
|
||||||
start_mark, end_mark, flow_style=True)
|
|
||||||
self.state = self.parse_flow_mapping_first_key
|
|
||||||
elif block and self.check_token(BlockSequenceStartToken):
|
|
||||||
end_mark = self.peek_token().start_mark
|
|
||||||
event = SequenceStartEvent(anchor, tag, implicit,
|
|
||||||
start_mark, end_mark, flow_style=False)
|
|
||||||
self.state = self.parse_block_sequence_first_entry
|
|
||||||
elif block and self.check_token(BlockMappingStartToken):
|
|
||||||
end_mark = self.peek_token().start_mark
|
|
||||||
event = MappingStartEvent(anchor, tag, implicit,
|
|
||||||
start_mark, end_mark, flow_style=False)
|
|
||||||
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.peek_token()
|
|
||||||
raise ParserError("while parsing a %s node" % node, start_mark,
|
|
||||||
"expected the node content, but found %r" % token.id,
|
|
||||||
token.start_mark)
|
|
||||||
return event
|
|
||||||
|
|
||||||
# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
|
|
||||||
|
|
||||||
def parse_block_sequence_first_entry(self):
|
|
||||||
token = self.get_token()
|
|
||||||
self.marks.append(token.start_mark)
|
|
||||||
return self.parse_block_sequence_entry()
|
|
||||||
|
|
||||||
def parse_block_sequence_entry(self):
|
|
||||||
if self.check_token(BlockEntryToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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.check_token(BlockEndToken):
|
|
||||||
token = self.peek_token()
|
|
||||||
raise ParserError("while parsing a block collection", self.marks[-1],
|
|
||||||
"expected <block end>, but found %r" % token.id, token.start_mark)
|
|
||||||
token = self.get_token()
|
|
||||||
event = SequenceEndEvent(token.start_mark, token.end_mark)
|
|
||||||
self.state = self.states.pop()
|
|
||||||
self.marks.pop()
|
|
||||||
return event
|
|
||||||
|
|
||||||
# indentless_sequence ::= (BLOCK-ENTRY block_node?)+
|
|
||||||
|
|
||||||
def parse_indentless_sequence_entry(self):
|
|
||||||
if self.check_token(BlockEntryToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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.peek_token()
|
|
||||||
event = SequenceEndEvent(token.start_mark, token.start_mark)
|
|
||||||
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):
|
|
||||||
token = self.get_token()
|
|
||||||
self.marks.append(token.start_mark)
|
|
||||||
return self.parse_block_mapping_key()
|
|
||||||
|
|
||||||
def parse_block_mapping_key(self):
|
|
||||||
if self.check_token(KeyToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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 not self.check_token(BlockEndToken):
|
|
||||||
token = self.peek_token()
|
|
||||||
raise ParserError("while parsing a block mapping", self.marks[-1],
|
|
||||||
"expected <block end>, but found %r" % token.id, token.start_mark)
|
|
||||||
token = self.get_token()
|
|
||||||
event = MappingEndEvent(token.start_mark, token.end_mark)
|
|
||||||
self.state = self.states.pop()
|
|
||||||
self.marks.pop()
|
|
||||||
return event
|
|
||||||
|
|
||||||
def parse_block_mapping_value(self):
|
|
||||||
if self.check_token(ValueToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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
|
|
||||||
return self.process_empty_scalar(token.end_mark)
|
|
||||||
else:
|
|
||||||
self.state = self.parse_block_mapping_key
|
|
||||||
token = self.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):
|
|
||||||
token = self.get_token()
|
|
||||||
self.marks.append(token.start_mark)
|
|
||||||
return self.parse_flow_sequence_entry(first=True)
|
|
||||||
|
|
||||||
def parse_flow_sequence_entry(self, first=False):
|
|
||||||
if not self.check_token(FlowSequenceEndToken):
|
|
||||||
if not first:
|
|
||||||
if self.check_token(FlowEntryToken):
|
|
||||||
self.get_token()
|
|
||||||
else:
|
|
||||||
token = self.peek_token()
|
|
||||||
raise ParserError("while parsing a flow sequence", self.marks[-1],
|
|
||||||
"expected ',' or ']', but got %r" % token.id, token.start_mark)
|
|
||||||
|
|
||||||
if self.check_token(KeyToken):
|
|
||||||
token = self.peek_token()
|
|
||||||
event = 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.check_token(FlowSequenceEndToken):
|
|
||||||
self.states.append(self.parse_flow_sequence_entry)
|
|
||||||
return self.parse_flow_node()
|
|
||||||
token = self.get_token()
|
|
||||||
event = SequenceEndEvent(token.start_mark, token.end_mark)
|
|
||||||
self.state = self.states.pop()
|
|
||||||
self.marks.pop()
|
|
||||||
return event
|
|
||||||
|
|
||||||
def parse_flow_sequence_entry_mapping_key(self):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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):
|
|
||||||
if self.check_token(ValueToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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.peek_token()
|
|
||||||
return self.process_empty_scalar(token.start_mark)
|
|
||||||
|
|
||||||
def parse_flow_sequence_entry_mapping_end(self):
|
|
||||||
self.state = self.parse_flow_sequence_entry
|
|
||||||
token = self.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):
|
|
||||||
token = self.get_token()
|
|
||||||
self.marks.append(token.start_mark)
|
|
||||||
return self.parse_flow_mapping_key(first=True)
|
|
||||||
|
|
||||||
def parse_flow_mapping_key(self, first=False):
|
|
||||||
if not self.check_token(FlowMappingEndToken):
|
|
||||||
if not first:
|
|
||||||
if self.check_token(FlowEntryToken):
|
|
||||||
self.get_token()
|
|
||||||
else:
|
|
||||||
token = self.peek_token()
|
|
||||||
raise ParserError("while parsing a flow mapping", self.marks[-1],
|
|
||||||
"expected ',' or '}', but got %r" % token.id, token.start_mark)
|
|
||||||
if self.check_token(KeyToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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 not self.check_token(FlowMappingEndToken):
|
|
||||||
self.states.append(self.parse_flow_mapping_empty_value)
|
|
||||||
return self.parse_flow_node()
|
|
||||||
token = self.get_token()
|
|
||||||
event = MappingEndEvent(token.start_mark, token.end_mark)
|
|
||||||
self.state = self.states.pop()
|
|
||||||
self.marks.pop()
|
|
||||||
return event
|
|
||||||
|
|
||||||
def parse_flow_mapping_value(self):
|
|
||||||
if self.check_token(ValueToken):
|
|
||||||
token = self.get_token()
|
|
||||||
if not self.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.peek_token()
|
|
||||||
return self.process_empty_scalar(token.start_mark)
|
|
||||||
|
|
||||||
def parse_flow_mapping_empty_value(self):
|
|
||||||
self.state = self.parse_flow_mapping_key
|
|
||||||
return self.process_empty_scalar(self.peek_token().start_mark)
|
|
||||||
|
|
||||||
def process_empty_scalar(self, mark):
|
|
||||||
return ScalarEvent(None, None, (True, False), '', mark, mark)
|
|
||||||
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
__all__ = ['Reader', 'ReaderError']
|
|
||||||
|
|
||||||
from .error import YAMLError, Mark
|
|
||||||
|
|
||||||
import codecs, re
|
|
||||||
|
|
||||||
class ReaderError(YAMLError):
|
|
||||||
|
|
||||||
def __init__(self, name, position, character, encoding, reason):
|
|
||||||
self.name = name
|
|
||||||
self.character = character
|
|
||||||
self.position = position
|
|
||||||
self.encoding = encoding
|
|
||||||
self.reason = reason
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if isinstance(self.character, bytes):
|
|
||||||
return "'%s' codec can't decode byte #x%02x: %s\n" \
|
|
||||||
" in \"%s\", position %d" \
|
|
||||||
% (self.encoding, ord(self.character), self.reason,
|
|
||||||
self.name, self.position)
|
|
||||||
else:
|
|
||||||
return "unacceptable character #x%04x: %s\n" \
|
|
||||||
" in \"%s\", position %d" \
|
|
||||||
% (self.character, self.reason,
|
|
||||||
self.name, self.position)
|
|
||||||
|
|
||||||
class Reader(object):
|
|
||||||
# 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):
|
|
||||||
self.name = None
|
|
||||||
self.stream = None
|
|
||||||
self.stream_pointer = 0
|
|
||||||
self.eof = True
|
|
||||||
self.buffer = ''
|
|
||||||
self.pointer = 0
|
|
||||||
self.raw_buffer = None
|
|
||||||
self.raw_decode = None
|
|
||||||
self.encoding = None
|
|
||||||
self.index = 0
|
|
||||||
self.line = 0
|
|
||||||
self.column = 0
|
|
||||||
if isinstance(stream, str):
|
|
||||||
self.name = "<unicode string>"
|
|
||||||
self.check_printable(stream)
|
|
||||||
self.buffer = stream+'\0'
|
|
||||||
elif isinstance(stream, bytes):
|
|
||||||
self.name = "<byte string>"
|
|
||||||
self.raw_buffer = stream
|
|
||||||
self.determine_encoding()
|
|
||||||
else:
|
|
||||||
self.stream = stream
|
|
||||||
self.name = getattr(stream, 'name', "<file>")
|
|
||||||
self.eof = False
|
|
||||||
self.raw_buffer = None
|
|
||||||
self.determine_encoding()
|
|
||||||
|
|
||||||
def peek(self, index=0):
|
|
||||||
try:
|
|
||||||
return self.buffer[self.pointer+index]
|
|
||||||
except IndexError:
|
|
||||||
self.update(index+1)
|
|
||||||
return self.buffer[self.pointer+index]
|
|
||||||
|
|
||||||
def prefix(self, length=1):
|
|
||||||
if self.pointer+length >= len(self.buffer):
|
|
||||||
self.update(length)
|
|
||||||
return self.buffer[self.pointer:self.pointer+length]
|
|
||||||
|
|
||||||
def forward(self, length=1):
|
|
||||||
if self.pointer+length+1 >= len(self.buffer):
|
|
||||||
self.update(length+1)
|
|
||||||
while length:
|
|
||||||
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 get_mark(self):
|
|
||||||
if self.stream is None:
|
|
||||||
return Mark(self.name, self.index, self.line, self.column,
|
|
||||||
self.buffer, self.pointer)
|
|
||||||
else:
|
|
||||||
return Mark(self.name, self.index, self.line, self.column,
|
|
||||||
None, None)
|
|
||||||
|
|
||||||
def determine_encoding(self):
|
|
||||||
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
|
|
||||||
self.encoding = 'utf-16-le'
|
|
||||||
elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE):
|
|
||||||
self.raw_decode = codecs.utf_16_be_decode
|
|
||||||
self.encoding = 'utf-16-be'
|
|
||||||
else:
|
|
||||||
self.raw_decode = codecs.utf_8_decode
|
|
||||||
self.encoding = 'utf-8'
|
|
||||||
self.update(1)
|
|
||||||
|
|
||||||
NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]')
|
|
||||||
def check_printable(self, data):
|
|
||||||
match = self.NON_PRINTABLE.search(data)
|
|
||||||
if match:
|
|
||||||
character = match.group()
|
|
||||||
position = self.index+(len(self.buffer)-self.pointer)+match.start()
|
|
||||||
raise ReaderError(self.name, position, ord(character),
|
|
||||||
'unicode', "special characters are not allowed")
|
|
||||||
|
|
||||||
def update(self, length):
|
|
||||||
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
|
|
||||||
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=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
|
|
||||||
@ -1,389 +0,0 @@
|
|||||||
|
|
||||||
__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
|
|
||||||
'RepresenterError']
|
|
||||||
|
|
||||||
from .error import *
|
|
||||||
from .nodes import *
|
|
||||||
|
|
||||||
import datetime, copyreg, types, base64, collections
|
|
||||||
|
|
||||||
class RepresenterError(YAMLError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class BaseRepresenter:
|
|
||||||
|
|
||||||
yaml_representers = {}
|
|
||||||
yaml_multi_representers = {}
|
|
||||||
|
|
||||||
def __init__(self, default_style=None, default_flow_style=False, sort_keys=True):
|
|
||||||
self.default_style = default_style
|
|
||||||
self.sort_keys = sort_keys
|
|
||||||
self.default_flow_style = default_flow_style
|
|
||||||
self.represented_objects = {}
|
|
||||||
self.object_keeper = []
|
|
||||||
self.alias_key = None
|
|
||||||
|
|
||||||
def represent(self, data):
|
|
||||||
node = self.represent_data(data)
|
|
||||||
self.serialize(node)
|
|
||||||
self.represented_objects = {}
|
|
||||||
self.object_keeper = []
|
|
||||||
self.alias_key = None
|
|
||||||
|
|
||||||
def represent_data(self, data):
|
|
||||||
if self.ignore_aliases(data):
|
|
||||||
self.alias_key = None
|
|
||||||
else:
|
|
||||||
self.alias_key = id(data)
|
|
||||||
if self.alias_key is not None:
|
|
||||||
if self.alias_key in self.represented_objects:
|
|
||||||
node = self.represented_objects[self.alias_key]
|
|
||||||
#if node is None:
|
|
||||||
# raise RepresenterError("recursive objects are not allowed: %r" % data)
|
|
||||||
return node
|
|
||||||
#self.represented_objects[alias_key] = None
|
|
||||||
self.object_keeper.append(data)
|
|
||||||
data_types = type(data).__mro__
|
|
||||||
if data_types[0] in self.yaml_representers:
|
|
||||||
node = self.yaml_representers[data_types[0]](self, data)
|
|
||||||
else:
|
|
||||||
for data_type in data_types:
|
|
||||||
if data_type in self.yaml_multi_representers:
|
|
||||||
node = self.yaml_multi_representers[data_type](self, data)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if None in self.yaml_multi_representers:
|
|
||||||
node = self.yaml_multi_representers[None](self, data)
|
|
||||||
elif None in self.yaml_representers:
|
|
||||||
node = self.yaml_representers[None](self, data)
|
|
||||||
else:
|
|
||||||
node = ScalarNode(None, str(data))
|
|
||||||
#if alias_key is not None:
|
|
||||||
# self.represented_objects[alias_key] = node
|
|
||||||
return node
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_representer(cls, data_type, representer):
|
|
||||||
if not 'yaml_representers' in cls.__dict__:
|
|
||||||
cls.yaml_representers = cls.yaml_representers.copy()
|
|
||||||
cls.yaml_representers[data_type] = representer
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_multi_representer(cls, data_type, representer):
|
|
||||||
if not 'yaml_multi_representers' in cls.__dict__:
|
|
||||||
cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
|
|
||||||
cls.yaml_multi_representers[data_type] = representer
|
|
||||||
|
|
||||||
def represent_scalar(self, tag, value, style=None):
|
|
||||||
if style is None:
|
|
||||||
style = self.default_style
|
|
||||||
node = ScalarNode(tag, value, style=style)
|
|
||||||
if self.alias_key is not None:
|
|
||||||
self.represented_objects[self.alias_key] = node
|
|
||||||
return node
|
|
||||||
|
|
||||||
def represent_sequence(self, tag, sequence, flow_style=None):
|
|
||||||
value = []
|
|
||||||
node = SequenceNode(tag, value, flow_style=flow_style)
|
|
||||||
if self.alias_key is not None:
|
|
||||||
self.represented_objects[self.alias_key] = node
|
|
||||||
best_style = True
|
|
||||||
for item in sequence:
|
|
||||||
node_item = self.represent_data(item)
|
|
||||||
if not (isinstance(node_item, ScalarNode) and not node_item.style):
|
|
||||||
best_style = False
|
|
||||||
value.append(node_item)
|
|
||||||
if flow_style is None:
|
|
||||||
if self.default_flow_style is not None:
|
|
||||||
node.flow_style = self.default_flow_style
|
|
||||||
else:
|
|
||||||
node.flow_style = best_style
|
|
||||||
return node
|
|
||||||
|
|
||||||
def represent_mapping(self, tag, mapping, flow_style=None):
|
|
||||||
value = []
|
|
||||||
node = MappingNode(tag, value, flow_style=flow_style)
|
|
||||||
if self.alias_key is not None:
|
|
||||||
self.represented_objects[self.alias_key] = node
|
|
||||||
best_style = True
|
|
||||||
if hasattr(mapping, 'items'):
|
|
||||||
mapping = list(mapping.items())
|
|
||||||
if self.sort_keys:
|
|
||||||
try:
|
|
||||||
mapping = sorted(mapping)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
for item_key, item_value in mapping:
|
|
||||||
node_key = self.represent_data(item_key)
|
|
||||||
node_value = self.represent_data(item_value)
|
|
||||||
if not (isinstance(node_key, ScalarNode) and not node_key.style):
|
|
||||||
best_style = False
|
|
||||||
if not (isinstance(node_value, ScalarNode) and not node_value.style):
|
|
||||||
best_style = False
|
|
||||||
value.append((node_key, node_value))
|
|
||||||
if flow_style is None:
|
|
||||||
if self.default_flow_style is not None:
|
|
||||||
node.flow_style = self.default_flow_style
|
|
||||||
else:
|
|
||||||
node.flow_style = best_style
|
|
||||||
return node
|
|
||||||
|
|
||||||
def ignore_aliases(self, data):
|
|
||||||
return False
|
|
||||||
|
|
||||||
class SafeRepresenter(BaseRepresenter):
|
|
||||||
|
|
||||||
def ignore_aliases(self, data):
|
|
||||||
if data is None:
|
|
||||||
return True
|
|
||||||
if isinstance(data, tuple) and data == ():
|
|
||||||
return True
|
|
||||||
if isinstance(data, (str, bytes, bool, int, float)):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def represent_none(self, data):
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:null', 'null')
|
|
||||||
|
|
||||||
def represent_str(self, data):
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:str', data)
|
|
||||||
|
|
||||||
def represent_binary(self, data):
|
|
||||||
if hasattr(base64, 'encodebytes'):
|
|
||||||
data = base64.encodebytes(data).decode('ascii')
|
|
||||||
else:
|
|
||||||
data = base64.encodestring(data).decode('ascii')
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|')
|
|
||||||
|
|
||||||
def represent_bool(self, data):
|
|
||||||
if data:
|
|
||||||
value = 'true'
|
|
||||||
else:
|
|
||||||
value = 'false'
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:bool', value)
|
|
||||||
|
|
||||||
def represent_int(self, data):
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:int', str(data))
|
|
||||||
|
|
||||||
inf_value = 1e300
|
|
||||||
while repr(inf_value) != repr(inf_value*inf_value):
|
|
||||||
inf_value *= inf_value
|
|
||||||
|
|
||||||
def represent_float(self, data):
|
|
||||||
if data != data or (data == 0.0 and data == 1.0):
|
|
||||||
value = '.nan'
|
|
||||||
elif data == self.inf_value:
|
|
||||||
value = '.inf'
|
|
||||||
elif data == -self.inf_value:
|
|
||||||
value = '-.inf'
|
|
||||||
else:
|
|
||||||
value = repr(data).lower()
|
|
||||||
# Note that in some cases `repr(data)` represents a float number
|
|
||||||
# without the decimal parts. For instance:
|
|
||||||
# >>> repr(1e17)
|
|
||||||
# '1e17'
|
|
||||||
# Unfortunately, this is not a valid float representation according
|
|
||||||
# to the definition of the `!!float` tag. We fix this by adding
|
|
||||||
# '.0' before the 'e' symbol.
|
|
||||||
if '.' not in value and 'e' in value:
|
|
||||||
value = value.replace('e', '.0e', 1)
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:float', value)
|
|
||||||
|
|
||||||
def represent_list(self, data):
|
|
||||||
#pairs = (len(data) > 0 and isinstance(data, list))
|
|
||||||
#if pairs:
|
|
||||||
# for item in data:
|
|
||||||
# if not isinstance(item, tuple) or len(item) != 2:
|
|
||||||
# pairs = False
|
|
||||||
# break
|
|
||||||
#if not pairs:
|
|
||||||
return self.represent_sequence('tag:yaml.org,2002:seq', data)
|
|
||||||
#value = []
|
|
||||||
#for item_key, item_value in data:
|
|
||||||
# value.append(self.represent_mapping(u'tag:yaml.org,2002:map',
|
|
||||||
# [(item_key, item_value)]))
|
|
||||||
#return SequenceNode(u'tag:yaml.org,2002:pairs', value)
|
|
||||||
|
|
||||||
def represent_dict(self, data):
|
|
||||||
return self.represent_mapping('tag:yaml.org,2002:map', data)
|
|
||||||
|
|
||||||
def represent_set(self, data):
|
|
||||||
value = {}
|
|
||||||
for key in data:
|
|
||||||
value[key] = None
|
|
||||||
return self.represent_mapping('tag:yaml.org,2002:set', value)
|
|
||||||
|
|
||||||
def represent_date(self, data):
|
|
||||||
value = data.isoformat()
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:timestamp', value)
|
|
||||||
|
|
||||||
def represent_datetime(self, data):
|
|
||||||
value = data.isoformat(' ')
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:timestamp', value)
|
|
||||||
|
|
||||||
def represent_yaml_object(self, tag, data, cls, flow_style=None):
|
|
||||||
if hasattr(data, '__getstate__'):
|
|
||||||
state = data.__getstate__()
|
|
||||||
else:
|
|
||||||
state = data.__dict__.copy()
|
|
||||||
return self.represent_mapping(tag, state, flow_style=flow_style)
|
|
||||||
|
|
||||||
def represent_undefined(self, data):
|
|
||||||
raise RepresenterError("cannot represent an object", data)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(type(None),
|
|
||||||
SafeRepresenter.represent_none)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(str,
|
|
||||||
SafeRepresenter.represent_str)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(bytes,
|
|
||||||
SafeRepresenter.represent_binary)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(bool,
|
|
||||||
SafeRepresenter.represent_bool)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(int,
|
|
||||||
SafeRepresenter.represent_int)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(float,
|
|
||||||
SafeRepresenter.represent_float)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(list,
|
|
||||||
SafeRepresenter.represent_list)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(tuple,
|
|
||||||
SafeRepresenter.represent_list)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(dict,
|
|
||||||
SafeRepresenter.represent_dict)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(set,
|
|
||||||
SafeRepresenter.represent_set)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(datetime.date,
|
|
||||||
SafeRepresenter.represent_date)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(datetime.datetime,
|
|
||||||
SafeRepresenter.represent_datetime)
|
|
||||||
|
|
||||||
SafeRepresenter.add_representer(None,
|
|
||||||
SafeRepresenter.represent_undefined)
|
|
||||||
|
|
||||||
class Representer(SafeRepresenter):
|
|
||||||
|
|
||||||
def represent_complex(self, data):
|
|
||||||
if data.imag == 0.0:
|
|
||||||
data = '%r' % data.real
|
|
||||||
elif data.real == 0.0:
|
|
||||||
data = '%rj' % data.imag
|
|
||||||
elif data.imag > 0:
|
|
||||||
data = '%r+%rj' % (data.real, data.imag)
|
|
||||||
else:
|
|
||||||
data = '%r%rj' % (data.real, data.imag)
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:python/complex', data)
|
|
||||||
|
|
||||||
def represent_tuple(self, data):
|
|
||||||
return self.represent_sequence('tag:yaml.org,2002:python/tuple', data)
|
|
||||||
|
|
||||||
def represent_name(self, data):
|
|
||||||
name = '%s.%s' % (data.__module__, data.__name__)
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '')
|
|
||||||
|
|
||||||
def represent_module(self, data):
|
|
||||||
return self.represent_scalar(
|
|
||||||
'tag:yaml.org,2002:python/module:'+data.__name__, '')
|
|
||||||
|
|
||||||
def represent_object(self, data):
|
|
||||||
# We use __reduce__ API to save the data. data.__reduce__ returns
|
|
||||||
# a tuple of length 2-5:
|
|
||||||
# (function, args, state, listitems, dictitems)
|
|
||||||
|
|
||||||
# For reconstructing, we calls function(*args), then set its state,
|
|
||||||
# listitems, and dictitems if they are not None.
|
|
||||||
|
|
||||||
# A special case is when function.__name__ == '__newobj__'. In this
|
|
||||||
# case we create the object with args[0].__new__(*args).
|
|
||||||
|
|
||||||
# Another special case is when __reduce__ returns a string - we don't
|
|
||||||
# support it.
|
|
||||||
|
|
||||||
# We produce a !!python/object, !!python/object/new or
|
|
||||||
# !!python/object/apply node.
|
|
||||||
|
|
||||||
cls = type(data)
|
|
||||||
if cls in copyreg.dispatch_table:
|
|
||||||
reduce = copyreg.dispatch_table[cls](data)
|
|
||||||
elif hasattr(data, '__reduce_ex__'):
|
|
||||||
reduce = data.__reduce_ex__(2)
|
|
||||||
elif hasattr(data, '__reduce__'):
|
|
||||||
reduce = data.__reduce__()
|
|
||||||
else:
|
|
||||||
raise RepresenterError("cannot represent an object", data)
|
|
||||||
reduce = (list(reduce)+[None]*5)[:5]
|
|
||||||
function, args, state, listitems, dictitems = reduce
|
|
||||||
args = list(args)
|
|
||||||
if state is None:
|
|
||||||
state = {}
|
|
||||||
if listitems is not None:
|
|
||||||
listitems = list(listitems)
|
|
||||||
if dictitems is not None:
|
|
||||||
dictitems = dict(dictitems)
|
|
||||||
if function.__name__ == '__newobj__':
|
|
||||||
function = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
tag = 'tag:yaml.org,2002:python/object/new:'
|
|
||||||
newobj = True
|
|
||||||
else:
|
|
||||||
tag = 'tag:yaml.org,2002:python/object/apply:'
|
|
||||||
newobj = False
|
|
||||||
function_name = '%s.%s' % (function.__module__, function.__name__)
|
|
||||||
if not args and not listitems and not dictitems \
|
|
||||||
and isinstance(state, dict) and newobj:
|
|
||||||
return self.represent_mapping(
|
|
||||||
'tag:yaml.org,2002:python/object:'+function_name, state)
|
|
||||||
if not listitems and not dictitems \
|
|
||||||
and isinstance(state, dict) and not state:
|
|
||||||
return self.represent_sequence(tag+function_name, args)
|
|
||||||
value = {}
|
|
||||||
if args:
|
|
||||||
value['args'] = args
|
|
||||||
if state or not isinstance(state, dict):
|
|
||||||
value['state'] = state
|
|
||||||
if listitems:
|
|
||||||
value['listitems'] = listitems
|
|
||||||
if dictitems:
|
|
||||||
value['dictitems'] = dictitems
|
|
||||||
return self.represent_mapping(tag+function_name, value)
|
|
||||||
|
|
||||||
def represent_ordered_dict(self, data):
|
|
||||||
# Provide uniform representation across different Python versions.
|
|
||||||
data_type = type(data)
|
|
||||||
tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \
|
|
||||||
% (data_type.__module__, data_type.__name__)
|
|
||||||
items = [[key, value] for key, value in data.items()]
|
|
||||||
return self.represent_sequence(tag, [items])
|
|
||||||
|
|
||||||
Representer.add_representer(complex,
|
|
||||||
Representer.represent_complex)
|
|
||||||
|
|
||||||
Representer.add_representer(tuple,
|
|
||||||
Representer.represent_tuple)
|
|
||||||
|
|
||||||
Representer.add_representer(type,
|
|
||||||
Representer.represent_name)
|
|
||||||
|
|
||||||
Representer.add_representer(collections.OrderedDict,
|
|
||||||
Representer.represent_ordered_dict)
|
|
||||||
|
|
||||||
Representer.add_representer(types.FunctionType,
|
|
||||||
Representer.represent_name)
|
|
||||||
|
|
||||||
Representer.add_representer(types.BuiltinFunctionType,
|
|
||||||
Representer.represent_name)
|
|
||||||
|
|
||||||
Representer.add_representer(types.ModuleType,
|
|
||||||
Representer.represent_module)
|
|
||||||
|
|
||||||
Representer.add_multi_representer(object,
|
|
||||||
Representer.represent_object)
|
|
||||||
|
|
||||||
@ -1,227 +0,0 @@
|
|||||||
|
|
||||||
__all__ = ['BaseResolver', 'Resolver']
|
|
||||||
|
|
||||||
from .error import *
|
|
||||||
from .nodes import *
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
class ResolverError(YAMLError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class BaseResolver:
|
|
||||||
|
|
||||||
DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
|
|
||||||
DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq'
|
|
||||||
DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map'
|
|
||||||
|
|
||||||
yaml_implicit_resolvers = {}
|
|
||||||
yaml_path_resolvers = {}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.resolver_exact_paths = []
|
|
||||||
self.resolver_prefix_paths = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_implicit_resolver(cls, tag, regexp, first):
|
|
||||||
if not 'yaml_implicit_resolvers' in cls.__dict__:
|
|
||||||
implicit_resolvers = {}
|
|
||||||
for key in cls.yaml_implicit_resolvers:
|
|
||||||
implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:]
|
|
||||||
cls.yaml_implicit_resolvers = implicit_resolvers
|
|
||||||
if first is None:
|
|
||||||
first = [None]
|
|
||||||
for ch in first:
|
|
||||||
cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_path_resolver(cls, tag, path, kind=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 not 'yaml_path_resolvers' in cls.__dict__:
|
|
||||||
cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy()
|
|
||||||
new_path = []
|
|
||||||
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("Invalid path element: %s" % element)
|
|
||||||
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("Invalid node checker: %s" % node_check)
|
|
||||||
if not isinstance(index_check, (str, int)) \
|
|
||||||
and index_check is not None:
|
|
||||||
raise ResolverError("Invalid index checker: %s" % index_check)
|
|
||||||
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("Invalid node kind: %s" % kind)
|
|
||||||
cls.yaml_path_resolvers[tuple(new_path), kind] = tag
|
|
||||||
|
|
||||||
def descend_resolver(self, current_node, current_index):
|
|
||||||
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):
|
|
||||||
if not self.yaml_path_resolvers:
|
|
||||||
return
|
|
||||||
self.resolver_exact_paths.pop()
|
|
||||||
self.resolver_prefix_paths.pop()
|
|
||||||
|
|
||||||
def check_resolver_prefix(self, depth, path, kind,
|
|
||||||
current_node, current_index):
|
|
||||||
node_check, index_check = path[depth-1]
|
|
||||||
if isinstance(node_check, str):
|
|
||||||
if current_node.tag != node_check:
|
|
||||||
return
|
|
||||||
elif node_check is not None:
|
|
||||||
if not isinstance(current_node, node_check):
|
|
||||||
return
|
|
||||||
if index_check is True and current_index is not None:
|
|
||||||
return
|
|
||||||
if (index_check is False or index_check is None) \
|
|
||||||
and current_index is None:
|
|
||||||
return
|
|
||||||
if isinstance(index_check, str):
|
|
||||||
if not (isinstance(current_index, ScalarNode)
|
|
||||||
and index_check == current_index.value):
|
|
||||||
return
|
|
||||||
elif isinstance(index_check, int) and not isinstance(index_check, bool):
|
|
||||||
if index_check != current_index:
|
|
||||||
return
|
|
||||||
return True
|
|
||||||
|
|
||||||
def resolve(self, kind, value, implicit):
|
|
||||||
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
|
|
||||||
implicit = implicit[1]
|
|
||||||
if self.yaml_path_resolvers:
|
|
||||||
exact_paths = self.resolver_exact_paths[-1]
|
|
||||||
if kind in exact_paths:
|
|
||||||
return exact_paths[kind]
|
|
||||||
if None in exact_paths:
|
|
||||||
return 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
|
|
||||||
|
|
||||||
class Resolver(BaseResolver):
|
|
||||||
pass
|
|
||||||
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:bool',
|
|
||||||
re.compile(r'''^(?:yes|Yes|YES|no|No|NO
|
|
||||||
|true|True|TRUE|false|False|FALSE
|
|
||||||
|on|On|ON|off|Off|OFF)$''', re.X),
|
|
||||||
list('yYnNtTfFoO'))
|
|
||||||
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:float',
|
|
||||||
re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)?
|
|
||||||
|\.[0-9_]+(?:[eE][-+][0-9]+)?
|
|
||||||
|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
|
|
||||||
|[-+]?\.(?:inf|Inf|INF)
|
|
||||||
|\.(?:nan|NaN|NAN))$''', re.X),
|
|
||||||
list('-+0123456789.'))
|
|
||||||
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:int',
|
|
||||||
re.compile(r'''^(?:[-+]?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),
|
|
||||||
list('-+0123456789'))
|
|
||||||
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:merge',
|
|
||||||
re.compile(r'^(?:<<)$'),
|
|
||||||
['<'])
|
|
||||||
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:null',
|
|
||||||
re.compile(r'''^(?: ~
|
|
||||||
|null|Null|NULL
|
|
||||||
| )$''', re.X),
|
|
||||||
['~', 'n', 'N', ''])
|
|
||||||
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:timestamp',
|
|
||||||
re.compile(r'''^(?:[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'))
|
|
||||||
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:value',
|
|
||||||
re.compile(r'^(?:=)$'),
|
|
||||||
['='])
|
|
||||||
|
|
||||||
# The following resolver is only for documentation purposes. It cannot work
|
|
||||||
# because plain scalars cannot start with '!', '&', or '*'.
|
|
||||||
Resolver.add_implicit_resolver(
|
|
||||||
'tag:yaml.org,2002:yaml',
|
|
||||||
re.compile(r'^(?:!|&|\*)$'),
|
|
||||||
list('!&*'))
|
|
||||||
|
|
||||||
1435
src/yaml/scanner.py
1435
src/yaml/scanner.py
File diff suppressed because it is too large
Load Diff
@ -1,111 +0,0 @@
|
|||||||
|
|
||||||
__all__ = ['Serializer', 'SerializerError']
|
|
||||||
|
|
||||||
from .error import YAMLError
|
|
||||||
from .events import *
|
|
||||||
from .nodes import *
|
|
||||||
|
|
||||||
class SerializerError(YAMLError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Serializer:
|
|
||||||
|
|
||||||
ANCHOR_TEMPLATE = 'id%03d'
|
|
||||||
|
|
||||||
def __init__(self, encoding=None,
|
|
||||||
explicit_start=None, explicit_end=None, version=None, tags=None):
|
|
||||||
self.use_encoding = encoding
|
|
||||||
self.use_explicit_start = explicit_start
|
|
||||||
self.use_explicit_end = explicit_end
|
|
||||||
self.use_version = version
|
|
||||||
self.use_tags = tags
|
|
||||||
self.serialized_nodes = {}
|
|
||||||
self.anchors = {}
|
|
||||||
self.last_anchor_id = 0
|
|
||||||
self.closed = None
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
if self.closed is None:
|
|
||||||
self.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):
|
|
||||||
if self.closed is None:
|
|
||||||
raise SerializerError("serializer is not opened")
|
|
||||||
elif not self.closed:
|
|
||||||
self.emit(StreamEndEvent())
|
|
||||||
self.closed = True
|
|
||||||
|
|
||||||
#def __del__(self):
|
|
||||||
# self.close()
|
|
||||||
|
|
||||||
def serialize(self, node):
|
|
||||||
if self.closed is None:
|
|
||||||
raise SerializerError("serializer is not opened")
|
|
||||||
elif self.closed:
|
|
||||||
raise SerializerError("serializer is closed")
|
|
||||||
self.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.emit(DocumentEndEvent(explicit=self.use_explicit_end))
|
|
||||||
self.serialized_nodes = {}
|
|
||||||
self.anchors = {}
|
|
||||||
self.last_anchor_id = 0
|
|
||||||
|
|
||||||
def anchor_node(self, node):
|
|
||||||
if node in self.anchors:
|
|
||||||
if self.anchors[node] is None:
|
|
||||||
self.anchors[node] = self.generate_anchor(node)
|
|
||||||
else:
|
|
||||||
self.anchors[node] = None
|
|
||||||
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):
|
|
||||||
self.last_anchor_id += 1
|
|
||||||
return self.ANCHOR_TEMPLATE % self.last_anchor_id
|
|
||||||
|
|
||||||
def serialize_node(self, node, parent, index):
|
|
||||||
alias = self.anchors[node]
|
|
||||||
if node in self.serialized_nodes:
|
|
||||||
self.emit(AliasEvent(alias))
|
|
||||||
else:
|
|
||||||
self.serialized_nodes[node] = True
|
|
||||||
self.descend_resolver(parent, index)
|
|
||||||
if isinstance(node, ScalarNode):
|
|
||||||
detected_tag = self.resolve(ScalarNode, node.value, (True, False))
|
|
||||||
default_tag = self.resolve(ScalarNode, node.value, (False, True))
|
|
||||||
implicit = (node.tag == detected_tag), (node.tag == default_tag)
|
|
||||||
self.emit(ScalarEvent(alias, node.tag, implicit, node.value,
|
|
||||||
style=node.style))
|
|
||||||
elif isinstance(node, SequenceNode):
|
|
||||||
implicit = (node.tag
|
|
||||||
== self.resolve(SequenceNode, node.value, True))
|
|
||||||
self.emit(SequenceStartEvent(alias, node.tag, implicit,
|
|
||||||
flow_style=node.flow_style))
|
|
||||||
index = 0
|
|
||||||
for item in node.value:
|
|
||||||
self.serialize_node(item, node, index)
|
|
||||||
index += 1
|
|
||||||
self.emit(SequenceEndEvent())
|
|
||||||
elif isinstance(node, MappingNode):
|
|
||||||
implicit = (node.tag
|
|
||||||
== self.resolve(MappingNode, node.value, True))
|
|
||||||
self.emit(MappingStartEvent(alias, node.tag, implicit,
|
|
||||||
flow_style=node.flow_style))
|
|
||||||
for key, value in node.value:
|
|
||||||
self.serialize_node(key, node, None)
|
|
||||||
self.serialize_node(value, node, key)
|
|
||||||
self.emit(MappingEndEvent())
|
|
||||||
self.ascend_resolver()
|
|
||||||
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
|
|
||||||
class Token(object):
|
|
||||||
def __init__(self, start_mark, end_mark):
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
def __repr__(self):
|
|
||||||
attributes = [key for key in self.__dict__
|
|
||||||
if not key.endswith('_mark')]
|
|
||||||
attributes.sort()
|
|
||||||
arguments = ', '.join(['%s=%r' % (key, getattr(self, key))
|
|
||||||
for key in attributes])
|
|
||||||
return '%s(%s)' % (self.__class__.__name__, arguments)
|
|
||||||
|
|
||||||
#class BOMToken(Token):
|
|
||||||
# id = '<byte order mark>'
|
|
||||||
|
|
||||||
class DirectiveToken(Token):
|
|
||||||
id = '<directive>'
|
|
||||||
def __init__(self, name, value, start_mark, end_mark):
|
|
||||||
self.name = name
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
|
|
||||||
class DocumentStartToken(Token):
|
|
||||||
id = '<document start>'
|
|
||||||
|
|
||||||
class DocumentEndToken(Token):
|
|
||||||
id = '<document end>'
|
|
||||||
|
|
||||||
class StreamStartToken(Token):
|
|
||||||
id = '<stream start>'
|
|
||||||
def __init__(self, start_mark=None, end_mark=None,
|
|
||||||
encoding=None):
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.encoding = encoding
|
|
||||||
|
|
||||||
class StreamEndToken(Token):
|
|
||||||
id = '<stream end>'
|
|
||||||
|
|
||||||
class BlockSequenceStartToken(Token):
|
|
||||||
id = '<block sequence start>'
|
|
||||||
|
|
||||||
class BlockMappingStartToken(Token):
|
|
||||||
id = '<block mapping start>'
|
|
||||||
|
|
||||||
class BlockEndToken(Token):
|
|
||||||
id = '<block end>'
|
|
||||||
|
|
||||||
class FlowSequenceStartToken(Token):
|
|
||||||
id = '['
|
|
||||||
|
|
||||||
class FlowMappingStartToken(Token):
|
|
||||||
id = '{'
|
|
||||||
|
|
||||||
class FlowSequenceEndToken(Token):
|
|
||||||
id = ']'
|
|
||||||
|
|
||||||
class FlowMappingEndToken(Token):
|
|
||||||
id = '}'
|
|
||||||
|
|
||||||
class KeyToken(Token):
|
|
||||||
id = '?'
|
|
||||||
|
|
||||||
class ValueToken(Token):
|
|
||||||
id = ':'
|
|
||||||
|
|
||||||
class BlockEntryToken(Token):
|
|
||||||
id = '-'
|
|
||||||
|
|
||||||
class FlowEntryToken(Token):
|
|
||||||
id = ','
|
|
||||||
|
|
||||||
class AliasToken(Token):
|
|
||||||
id = '<alias>'
|
|
||||||
def __init__(self, value, start_mark, end_mark):
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
|
|
||||||
class AnchorToken(Token):
|
|
||||||
id = '<anchor>'
|
|
||||||
def __init__(self, value, start_mark, end_mark):
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
|
|
||||||
class TagToken(Token):
|
|
||||||
id = '<tag>'
|
|
||||||
def __init__(self, value, start_mark, end_mark):
|
|
||||||
self.value = value
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
|
|
||||||
class ScalarToken(Token):
|
|
||||||
id = '<scalar>'
|
|
||||||
def __init__(self, value, plain, start_mark, end_mark, style=None):
|
|
||||||
self.value = value
|
|
||||||
self.plain = plain
|
|
||||||
self.start_mark = start_mark
|
|
||||||
self.end_mark = end_mark
|
|
||||||
self.style = style
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user