releases/openstack_releases/yamlutils.py

131 lines
4.6 KiB
Python

# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import re
import six
import yaml
import yamlordereddictloader
_LIKE_A_NUMBER = re.compile('^[0-9]+.[0-9]+$')
def _has_newline(data):
if "\n" in data or "\r" in data:
return True
return False
class PrettySafeDumper(yaml.dumper.SafeDumper):
"""Yaml dumper that tries to not alter original formats (too much)."""
BINARY_ENCODING = 'utf-8'
def represent_ordereddict(self, data):
values = []
node = yaml.nodes.MappingNode(
'tag:yaml.org,2002:map', values, flow_style=None)
for key, value in data.items():
key_item = self.represent_data(key)
value_item = self.represent_data(value)
values.append((key_item, value_item))
return node
def ignore_aliases(self, data):
# Never output alias references; always repeat the data.
return True
def represent_bool(self, data):
if data:
value = 'yes'
else:
value = 'no'
return self.represent_scalar('tag:yaml.org,2002:bool', value)
def choose_scalar_style(self):
# Avoid messing up dict keys...
if self.states[-1] == self.expect_block_mapping_simple_value:
self.event.style = 'plain'
return super(PrettySafeDumper, self).choose_scalar_style()\
if self.event.style != 'plain' else ("'" if ' ' in
self.event.value else None)
def represent_string(self, data):
if isinstance(data, six.binary_type):
data = data.decode(self.BINARY_ENCODING)
style = "plain"
if _has_newline(data):
style = "|"
elif _LIKE_A_NUMBER.match(data):
style = '"'
return yaml.representer.ScalarNode('tag:yaml.org,2002:str',
data, style=style)
def represent_undefined(self, data):
if isinstance(data, collections.OrderedDict):
return self.represent_odict(data)
else:
return super(PrettySafeDumper, self).represent_undefined(data)
# Override this method to always indent. Otherwise when a list is
# emitted, the items nested under it are not and we have inconsistent
# formatting.
# https://stackoverflow.com/questions/25108581/python-yaml-dump-bad-indentation
def increase_indent(self, flow=False, indentless=False):
return super(PrettySafeDumper, self).increase_indent(flow, False)
# NOTE(harlowja): at some point this may not be needed...
# See: http://pyyaml.org/ticket/29
PrettySafeDumper.add_representer(collections.OrderedDict,
PrettySafeDumper.represent_ordereddict)
PrettySafeDumper.add_representer(None,
PrettySafeDumper.represent_undefined)
# NOTE(dhellmann): The representer functions in the base class are
# specified by class.method-name so we have to re-register the
# representer for bool if we want to override it.
PrettySafeDumper.add_representer(bool,
PrettySafeDumper.represent_bool)
# Ensure we use our own routine here, because the style that comes by
# default is sort of wonky and messes up the values....
for str_type in [six.binary_type, six.text_type]:
PrettySafeDumper.add_representer(str_type,
PrettySafeDumper.represent_string)
def dumps(obj):
"""Dump a python object -> blob and apply our pretty styling."""
buff = six.StringIO()
yaml.dump_all([obj], buff,
explicit_start=True,
indent=2,
default_flow_style=False,
line_break="\n",
Dumper=PrettySafeDumper,
allow_unicode=True)
return buff.getvalue()
def loads(blob):
"""Load a yaml blob and retain key ordering."""
# This does use load, which is unsafe, but should be ok
# for what we are loading here in this program; we should
# be able to fix that in the future (if it matters).
return yaml.load(blob, Loader=yamlordereddictloader.Loader)