1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 String formatting and string-based format utilities.
21 from types import MethodType
23 from ruamel import yaml # @UnresolvedImport
25 from .collections import FrozenList, FrozenDict, StrictList, StrictDict, OrderedDict
28 PLURALIZE_EXCEPTIONS = {}
31 # Add our types to ruamel.yaml (for round trips)
32 yaml.representer.RoundTripRepresenter.add_representer(
33 FrozenList, yaml.representer.RoundTripRepresenter.represent_list)
34 yaml.representer.RoundTripRepresenter.add_representer(
35 FrozenDict, yaml.representer.RoundTripRepresenter.represent_dict)
36 yaml.representer.RoundTripRepresenter.add_representer(
37 StrictList, yaml.representer.RoundTripRepresenter.represent_list)
38 yaml.representer.RoundTripRepresenter.add_representer(
39 StrictDict, yaml.representer.RoundTripRepresenter.represent_dict)
41 # Without this, ruamel.yaml will output "!!omap" types, which is
42 # technically correct but unnecessarily verbose for our uses
43 yaml.representer.RoundTripRepresenter.add_representer(
44 OrderedDict, yaml.representer.RoundTripRepresenter.represent_dict)
47 class JsonAsRawEncoder(json.JSONEncoder):
49 A :class:`JSONEncoder` that will use the ``as_raw`` property of objects if available.
51 def raw_encoder_default(self, obj):
55 if hasattr(obj, 'as_raw'):
58 return super(JsonAsRawEncoder, self).default(obj)
60 def __init__(self, *args, **kwargs):
61 kwargs['default'] = self.raw_encoder_default
62 super(JsonAsRawEncoder, self).__init__(*args, **kwargs)
65 class YamlAsRawDumper(yaml.dumper.RoundTripDumper): # pylint: disable=too-many-ancestors
67 A :class:`RoundTripDumper` that will use the ``as_raw`` property of objects if available.
70 def represent_data(self, data):
71 if hasattr(data, 'as_raw'):
73 return super(YamlAsRawDumper, self).represent_data(data)
76 def decode_list(data):
79 if isinstance(item, unicode):
80 item = item.encode('utf-8')
81 elif isinstance(item, list):
82 item = decode_list(item)
83 elif isinstance(item, dict):
84 item = decode_dict(item)
85 decoded_list.append(item)
89 def decode_dict(data):
91 for key, value in data.iteritems():
92 if isinstance(key, unicode):
93 key = key.encode('utf-8')
94 if isinstance(value, unicode):
95 value = value.encode('utf-8')
96 elif isinstance(value, list):
97 value = decode_list(value)
98 elif isinstance(value, dict):
99 value = decode_dict(value)
100 decoded_dict[key] = value
106 Like :class:`str` coercion, but makes sure that Unicode strings are properly encoded, and will
107 never return ``None``.
112 except UnicodeEncodeError:
113 return unicode(value).encode('utf8')
116 def safe_repr(value):
118 Like :func:`repr`, but calls :func:`as_raw` and :func:`as_agnostic` first.
121 return repr(as_agnostic(as_raw(value)))
124 def string_list_as_string(strings):
126 Nice representation of a list of strings.
131 return ', '.join('"{0}"'.format(safe_str(v)) for v in strings)
135 plural = PLURALIZE_EXCEPTIONS.get(noun)
136 if plural is not None:
138 elif noun.endswith('s'):
139 return '{0}es'.format(noun)
140 elif noun.endswith('y'):
141 return '{0}ies'.format(noun[:-1])
143 return '{0}s'.format(noun)
148 Converts values using their ``as_raw`` property, if it exists, recursively.
151 if hasattr(value, 'as_raw'):
153 if isinstance(value, MethodType):
154 # Old-style Python classes don't support properties
156 elif isinstance(value, list):
158 for i, v in enumerate(value):
160 elif isinstance(value, dict):
162 for k, v in value.iteritems():
167 def as_raw_list(value):
169 Assuming value is a list, converts its values using :func:`as_raw`.
174 if isinstance(value, dict):
175 value = value.itervalues()
176 return [as_raw(v) for v in value]
179 def as_raw_dict(value):
181 Assuming value is a dict, converts its values using :func:`as_raw`. The keys are left as is.
187 (k, as_raw(v)) for k, v in value.iteritems()))
190 def as_agnostic(value):
192 Converts subclasses of list and dict to standard lists and dicts, and Unicode strings to
193 non-Unicode if possible, recursively.
195 Useful for creating human-readable output of structures.
198 if isinstance(value, unicode):
201 except UnicodeEncodeError:
203 elif isinstance(value, list):
205 elif isinstance(value, dict):
208 if isinstance(value, list):
209 for i, _ in enumerate(value):
210 value[i] = as_agnostic(value[i])
211 elif isinstance(value, dict):
212 for k, v in value.iteritems():
213 value[k] = as_agnostic(v)
218 def json_dumps(value, indent=2):
220 JSON dumps that supports Unicode and the ``as_raw`` property of objects if available.
223 return json.dumps(value, indent=indent, ensure_ascii=False, cls=JsonAsRawEncoder)
226 def yaml_dumps(value, indent=2):
228 YAML dumps that supports Unicode and the ``as_raw`` property of objects if available.
231 return yaml.dump(value, indent=indent, allow_unicode=True, Dumper=YamlAsRawDumper)
234 def yaml_loads(value):
235 return yaml.load(value, Loader=yaml.SafeLoader)