mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-16 10:00:39 +00:00
231 lines
7.3 KiB
Python
231 lines
7.3 KiB
Python
|
#
|
||
|
# Copyright (C) 2011 - 2018 Satoru SATOH <ssato @ redhat.com>
|
||
|
# License: MIT
|
||
|
#
|
||
|
# type() is used to exactly match check instead of isinstance here.
|
||
|
# pylint: disable=unidiomatic-typecheck
|
||
|
r"""YAML backend:
|
||
|
|
||
|
- Format to support: YAML, http://yaml.org
|
||
|
- Requirements:
|
||
|
|
||
|
- ruamel.yaml, https://bitbucket.org/ruamel/yaml
|
||
|
- or PyYAML (yaml), http://pyyaml.org
|
||
|
|
||
|
- Development Status :: 5 - Production/Stable
|
||
|
- Limitations:
|
||
|
|
||
|
- Resuls is not ordered even if 'ac_ordered' or 'ac_dict' was given.
|
||
|
|
||
|
- Special options:
|
||
|
|
||
|
- All keyword options of yaml.safe_load, yaml.load, yaml.safe_dump and
|
||
|
yaml.dump should work.
|
||
|
|
||
|
- Use 'ac_safe' boolean keyword option if you prefer to call yaml.safe_load
|
||
|
and yaml.safe_dump instead of yaml.load and yaml.dump. Please note that
|
||
|
this option conflicts with 'ac_dict' option and these options cannot be
|
||
|
used at the same time.
|
||
|
|
||
|
- See also: http://pyyaml.org/wiki/PyYAMLDocumentation
|
||
|
|
||
|
Changelog:
|
||
|
|
||
|
.. versionchanged:: 0.9.6
|
||
|
|
||
|
- Add support of loading primitives other than mapping objects.
|
||
|
|
||
|
.. versionchanged:: 0.9.3
|
||
|
|
||
|
- Try ruamel.yaml instead of yaml (PyYAML) if it's available.
|
||
|
|
||
|
.. versionchanged:: 0.3
|
||
|
|
||
|
- Changed special keyword option 'ac_safe' from 'safe' to avoid
|
||
|
possibility of option conflicts in the future.
|
||
|
"""
|
||
|
from __future__ import absolute_import
|
||
|
|
||
|
import yaml
|
||
|
try:
|
||
|
from yaml import CSafeLoader as Loader, CDumper as Dumper
|
||
|
except ImportError:
|
||
|
from yaml import SafeLoader as Loader, Dumper
|
||
|
|
||
|
import anyconfig.backend.base
|
||
|
import anyconfig.compat
|
||
|
import anyconfig.utils
|
||
|
|
||
|
|
||
|
_MAPPING_TAG = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
|
||
|
|
||
|
|
||
|
def filter_from_options(key, options):
|
||
|
"""
|
||
|
:param key: Key str in options
|
||
|
:param options: Mapping object
|
||
|
:return:
|
||
|
New mapping object from `options` in which the item with `key` filtered
|
||
|
|
||
|
>>> filter_from_options('a', dict(a=1, b=2))
|
||
|
{'b': 2}
|
||
|
"""
|
||
|
return anyconfig.utils.filter_options([k for k in options.keys()
|
||
|
if k != key], options)
|
||
|
|
||
|
|
||
|
def _customized_loader(container, loader=Loader, mapping_tag=_MAPPING_TAG):
|
||
|
"""
|
||
|
Create or update loader with making given callble `container` to make
|
||
|
mapping objects such as dict and OrderedDict, used to construct python
|
||
|
object from yaml mapping node internally.
|
||
|
|
||
|
:param container: Set container used internally
|
||
|
"""
|
||
|
def construct_mapping(loader, node, deep=False):
|
||
|
"""Construct python object from yaml mapping node, based on
|
||
|
:meth:`yaml.BaseConstructor.construct_mapping` in PyYAML (MIT).
|
||
|
"""
|
||
|
loader.flatten_mapping(node)
|
||
|
if not isinstance(node, yaml.MappingNode):
|
||
|
msg = "expected a mapping node, but found %s" % node.id
|
||
|
raise yaml.constructor.ConstructorError(None, None, msg,
|
||
|
node.start_mark)
|
||
|
mapping = container()
|
||
|
for key_node, value_node in node.value:
|
||
|
key = loader.construct_object(key_node, deep=deep)
|
||
|
try:
|
||
|
hash(key)
|
||
|
except TypeError as exc:
|
||
|
eargs = ("while constructing a mapping",
|
||
|
node.start_mark,
|
||
|
"found unacceptable key (%s)" % exc,
|
||
|
key_node.start_mark)
|
||
|
raise yaml.constructor.ConstructorError(*eargs)
|
||
|
value = loader.construct_object(value_node, deep=deep)
|
||
|
mapping[key] = value
|
||
|
|
||
|
return mapping
|
||
|
|
||
|
tag = "tag:yaml.org,2002:python/unicode"
|
||
|
|
||
|
def construct_ustr(loader, node):
|
||
|
"""Unicode string constructor"""
|
||
|
return loader.construct_scalar(node)
|
||
|
|
||
|
try:
|
||
|
loader.add_constructor(tag, construct_ustr)
|
||
|
except NameError:
|
||
|
pass
|
||
|
|
||
|
if type(container) != dict:
|
||
|
loader.add_constructor(mapping_tag, construct_mapping)
|
||
|
return loader
|
||
|
|
||
|
|
||
|
def _customized_dumper(container, dumper=Dumper):
|
||
|
"""
|
||
|
Coutnerpart of :func:`_customized_loader` for dumpers.
|
||
|
"""
|
||
|
def container_representer(dumper, data, mapping_tag=_MAPPING_TAG):
|
||
|
"""Container representer.
|
||
|
"""
|
||
|
return dumper.represent_mapping(mapping_tag, data.items())
|
||
|
|
||
|
def ustr_representer(dumper, data):
|
||
|
"""Unicode string representer"""
|
||
|
tag = "tag:yaml.org,2002:python/unicode"
|
||
|
return dumper.represent_scalar(tag, data)
|
||
|
|
||
|
try:
|
||
|
dumper.add_representer(unicode, ustr_representer)
|
||
|
except NameError:
|
||
|
pass
|
||
|
|
||
|
if type(container) != dict:
|
||
|
dumper.add_representer(container, container_representer)
|
||
|
return dumper
|
||
|
|
||
|
|
||
|
def yml_fnc(fname, *args, **options):
|
||
|
"""An wrapper of yaml.safe_load, yaml.load, yaml.safe_dump and yaml.dump.
|
||
|
|
||
|
:param fname:
|
||
|
"load" or "dump", not checked but it should be OK.
|
||
|
see also :func:`yml_load` and :func:`yml_dump`
|
||
|
:param args: [stream] for load or [cnf, stream] for dump
|
||
|
:param options: keyword args may contain "ac_safe" to load/dump safely
|
||
|
"""
|
||
|
key = "ac_safe"
|
||
|
fnc = getattr(yaml, r"safe_" + fname if options.get(key) else fname)
|
||
|
return fnc(*args, **filter_from_options(key, options))
|
||
|
|
||
|
|
||
|
def yml_load(stream, container, yml_fnc=yml_fnc, **options):
|
||
|
"""An wrapper of yaml.safe_load and yaml.load.
|
||
|
|
||
|
:param stream: a file or file-like object to load YAML content
|
||
|
:param container: callble to make a container object
|
||
|
|
||
|
:return: Mapping object
|
||
|
"""
|
||
|
if options.get("ac_safe", False):
|
||
|
options = {} # yaml.safe_load does not process Loader opts.
|
||
|
elif not options.get("Loader"):
|
||
|
maybe_container = options.get("ac_dict", False)
|
||
|
if maybe_container and callable(maybe_container):
|
||
|
container = maybe_container
|
||
|
|
||
|
options["Loader"] = _customized_loader(container)
|
||
|
|
||
|
ret = yml_fnc("load", stream, **filter_from_options("ac_dict", options))
|
||
|
if ret is None:
|
||
|
return container()
|
||
|
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def yml_dump(data, stream, yml_fnc=yml_fnc, **options):
|
||
|
"""An wrapper of yaml.safe_dump and yaml.dump.
|
||
|
|
||
|
:param data: Some data to dump
|
||
|
:param stream: a file or file-like object to dump YAML data
|
||
|
"""
|
||
|
_is_dict = anyconfig.utils.is_dict_like(data)
|
||
|
|
||
|
if options.get("ac_safe", False):
|
||
|
options = {}
|
||
|
elif not options.get("Dumper", False) and _is_dict:
|
||
|
# TODO: Any other way to get its constructor?
|
||
|
maybe_container = options.get("ac_dict", type(data))
|
||
|
options["Dumper"] = _customized_dumper(maybe_container)
|
||
|
|
||
|
if _is_dict:
|
||
|
# Type information and the order of items are lost on dump currently.
|
||
|
data = anyconfig.dicts.convert_to(data, ac_dict=dict)
|
||
|
options = filter_from_options("ac_dict", options)
|
||
|
|
||
|
return yml_fnc("dump", data, stream, **options)
|
||
|
|
||
|
|
||
|
class Parser(anyconfig.backend.base.StreamParser):
|
||
|
"""
|
||
|
Parser for YAML files.
|
||
|
"""
|
||
|
_type = "yaml"
|
||
|
_cid = "pyyaml"
|
||
|
_extensions = ["yaml", "yml"]
|
||
|
_load_opts = ["Loader", "ac_safe", "ac_dict"]
|
||
|
_dump_opts = ["stream", "ac_safe", "Dumper", "default_style",
|
||
|
"default_flow_style", "canonical", "indent", "width",
|
||
|
"allow_unicode", "line_break", "encoding", "explicit_start",
|
||
|
"explicit_end", "version", "tags"]
|
||
|
_ordered = True
|
||
|
_allow_primitives = True
|
||
|
_dict_opts = ["ac_dict"]
|
||
|
|
||
|
load_from_stream = anyconfig.backend.base.to_method(yml_load)
|
||
|
dump_to_stream = anyconfig.backend.base.to_method(yml_dump)
|
||
|
|
||
|
# vim:sw=4:ts=4:et:
|