| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You 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. |
| |
| """ |
| String formatting and string-based format utilities. |
| """ |
| |
| import json |
| from types import MethodType |
| |
| from ruamel import yaml # @UnresolvedImport |
| |
| from .collections import FrozenList, FrozenDict, StrictList, StrictDict, OrderedDict |
| |
| |
| PLURALIZE_EXCEPTIONS = {} |
| |
| |
| # Add our types to ruamel.yaml (for round trips) |
| yaml.representer.RoundTripRepresenter.add_representer( |
| FrozenList, yaml.representer.RoundTripRepresenter.represent_list) |
| yaml.representer.RoundTripRepresenter.add_representer( |
| FrozenDict, yaml.representer.RoundTripRepresenter.represent_dict) |
| yaml.representer.RoundTripRepresenter.add_representer( |
| StrictList, yaml.representer.RoundTripRepresenter.represent_list) |
| yaml.representer.RoundTripRepresenter.add_representer( |
| StrictDict, yaml.representer.RoundTripRepresenter.represent_dict) |
| |
| # Without this, ruamel.yaml will output "!!omap" types, which is |
| # technically correct but unnecessarily verbose for our uses |
| yaml.representer.RoundTripRepresenter.add_representer( |
| OrderedDict, yaml.representer.RoundTripRepresenter.represent_dict) |
| |
| |
| class JsonAsRawEncoder(json.JSONEncoder): |
| """ |
| A :class:`JSONEncoder` that will use the ``as_raw`` property of objects if available. |
| """ |
| def raw_encoder_default(self, obj): |
| try: |
| return iter(obj) |
| except TypeError: |
| if hasattr(obj, 'as_raw'): |
| return as_raw(obj) |
| return str(obj) |
| return super(JsonAsRawEncoder, self).default(obj) |
| |
| def __init__(self, *args, **kwargs): |
| kwargs['default'] = self.raw_encoder_default |
| super(JsonAsRawEncoder, self).__init__(*args, **kwargs) |
| |
| |
| class YamlAsRawDumper(yaml.dumper.RoundTripDumper): # pylint: disable=too-many-ancestors |
| """ |
| A :class:`RoundTripDumper` that will use the ``as_raw`` property of objects if available. |
| """ |
| |
| def represent_data(self, data): |
| if hasattr(data, 'as_raw'): |
| data = as_raw(data) |
| return super(YamlAsRawDumper, self).represent_data(data) |
| |
| |
| def decode_list(data): |
| decoded_list = [] |
| for item in data: |
| if isinstance(item, unicode): |
| item = item.encode('utf-8') |
| elif isinstance(item, list): |
| item = decode_list(item) |
| elif isinstance(item, dict): |
| item = decode_dict(item) |
| decoded_list.append(item) |
| return decoded_list |
| |
| |
| def decode_dict(data): |
| decoded_dict = {} |
| for key, value in data.iteritems(): |
| if isinstance(key, unicode): |
| key = key.encode('utf-8') |
| if isinstance(value, unicode): |
| value = value.encode('utf-8') |
| elif isinstance(value, list): |
| value = decode_list(value) |
| elif isinstance(value, dict): |
| value = decode_dict(value) |
| decoded_dict[key] = value |
| return decoded_dict |
| |
| |
| def safe_str(value): |
| """ |
| Like :class:`str` coercion, but makes sure that Unicode strings are properly encoded, and will |
| never return ``None``. |
| """ |
| |
| try: |
| return str(value) |
| except UnicodeEncodeError: |
| return unicode(value).encode('utf8') |
| |
| |
| def safe_repr(value): |
| """ |
| Like :func:`repr`, but calls :func:`as_raw` and :func:`as_agnostic` first. |
| """ |
| |
| return repr(as_agnostic(as_raw(value))) |
| |
| |
| def string_list_as_string(strings): |
| """ |
| Nice representation of a list of strings. |
| """ |
| |
| if not strings: |
| return 'none' |
| return ', '.join('"{0}"'.format(safe_str(v)) for v in strings) |
| |
| |
| def pluralize(noun): |
| plural = PLURALIZE_EXCEPTIONS.get(noun) |
| if plural is not None: |
| return plural |
| elif noun.endswith('s'): |
| return '{0}es'.format(noun) |
| elif noun.endswith('y'): |
| return '{0}ies'.format(noun[:-1]) |
| else: |
| return '{0}s'.format(noun) |
| |
| |
| def as_raw(value): |
| """ |
| Converts values using their ``as_raw`` property, if it exists, recursively. |
| """ |
| |
| if hasattr(value, 'as_raw'): |
| value = value.as_raw |
| if isinstance(value, MethodType): |
| # Old-style Python classes don't support properties |
| value = value() |
| elif isinstance(value, list): |
| value = list(value) |
| for i, v in enumerate(value): |
| value[i] = as_raw(v) |
| elif isinstance(value, dict): |
| value = dict(value) |
| for k, v in value.iteritems(): |
| value[k] = as_raw(v) |
| return value |
| |
| |
| def as_raw_list(value): |
| """ |
| Assuming value is a list, converts its values using :func:`as_raw`. |
| """ |
| |
| if value is None: |
| return [] |
| if isinstance(value, dict): |
| value = value.itervalues() |
| return [as_raw(v) for v in value] |
| |
| |
| def as_raw_dict(value): |
| """ |
| Assuming value is a dict, converts its values using :func:`as_raw`. The keys are left as is. |
| """ |
| |
| if value is None: |
| return OrderedDict() |
| return OrderedDict(( |
| (k, as_raw(v)) for k, v in value.iteritems())) |
| |
| |
| def as_agnostic(value): |
| """ |
| Converts subclasses of list and dict to standard lists and dicts, and Unicode strings to |
| non-Unicode if possible, recursively. |
| |
| Useful for creating human-readable output of structures. |
| """ |
| |
| if isinstance(value, unicode): |
| try: |
| value = str(value) |
| except UnicodeEncodeError: |
| pass |
| elif isinstance(value, list): |
| value = list(value) |
| elif isinstance(value, dict): |
| value = dict(value) |
| |
| if isinstance(value, list): |
| for i, _ in enumerate(value): |
| value[i] = as_agnostic(value[i]) |
| elif isinstance(value, dict): |
| for k, v in value.iteritems(): |
| value[k] = as_agnostic(v) |
| |
| return value |
| |
| |
| def json_dumps(value, indent=2): |
| """ |
| JSON dumps that supports Unicode and the ``as_raw`` property of objects if available. |
| """ |
| |
| return json.dumps(value, indent=indent, ensure_ascii=False, cls=JsonAsRawEncoder) |
| |
| |
| def yaml_dumps(value, indent=2): |
| """ |
| YAML dumps that supports Unicode and the ``as_raw`` property of objects if available. |
| """ |
| |
| return yaml.dump(value, indent=indent, allow_unicode=True, Dumper=YamlAsRawDumper) |
| |
| |
| def yaml_loads(value): |
| return yaml.load(value, Loader=yaml.SafeLoader) |