Merge "vFW and vDNS support added to azure-plugin"
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / aria / utils / formatting.py
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
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 """
17 String formatting and string-based format utilities.
18 """
19
20 import json
21 from types import MethodType
22
23 from ruamel import yaml  # @UnresolvedImport
24
25 from .collections import FrozenList, FrozenDict, StrictList, StrictDict, OrderedDict
26
27
28 PLURALIZE_EXCEPTIONS = {}
29
30
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)
40
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)
45
46
47 class JsonAsRawEncoder(json.JSONEncoder):
48     """
49     A :class:`JSONEncoder` that will use the ``as_raw`` property of objects if available.
50     """
51     def raw_encoder_default(self, obj):
52         try:
53             return iter(obj)
54         except TypeError:
55             if hasattr(obj, 'as_raw'):
56                 return as_raw(obj)
57             return str(obj)
58         return super(JsonAsRawEncoder, self).default(obj)
59
60     def __init__(self, *args, **kwargs):
61         kwargs['default'] = self.raw_encoder_default
62         super(JsonAsRawEncoder, self).__init__(*args, **kwargs)
63
64
65 class YamlAsRawDumper(yaml.dumper.RoundTripDumper):  # pylint: disable=too-many-ancestors
66     """
67     A :class:`RoundTripDumper` that will use the ``as_raw`` property of objects if available.
68     """
69
70     def represent_data(self, data):
71         if hasattr(data, 'as_raw'):
72             data = as_raw(data)
73         return super(YamlAsRawDumper, self).represent_data(data)
74
75
76 def decode_list(data):
77     decoded_list = []
78     for item in 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)
86     return decoded_list
87
88
89 def decode_dict(data):
90     decoded_dict = {}
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
101     return decoded_dict
102
103
104 def safe_str(value):
105     """
106     Like :class:`str` coercion, but makes sure that Unicode strings are properly encoded, and will
107     never return ``None``.
108     """
109
110     try:
111         return str(value)
112     except UnicodeEncodeError:
113         return unicode(value).encode('utf8')
114
115
116 def safe_repr(value):
117     """
118     Like :func:`repr`, but calls :func:`as_raw` and :func:`as_agnostic` first.
119     """
120
121     return repr(as_agnostic(as_raw(value)))
122
123
124 def string_list_as_string(strings):
125     """
126     Nice representation of a list of strings.
127     """
128
129     if not strings:
130         return 'none'
131     return ', '.join('"{0}"'.format(safe_str(v)) for v in strings)
132
133
134 def pluralize(noun):
135     plural = PLURALIZE_EXCEPTIONS.get(noun)
136     if plural is not None:
137         return plural
138     elif noun.endswith('s'):
139         return '{0}es'.format(noun)
140     elif noun.endswith('y'):
141         return '{0}ies'.format(noun[:-1])
142     else:
143         return '{0}s'.format(noun)
144
145
146 def as_raw(value):
147     """
148     Converts values using their ``as_raw`` property, if it exists, recursively.
149     """
150
151     if hasattr(value, 'as_raw'):
152         value = value.as_raw
153         if isinstance(value, MethodType):
154             # Old-style Python classes don't support properties
155             value = value()
156     elif isinstance(value, list):
157         value = list(value)
158         for i, v in enumerate(value):
159             value[i] = as_raw(v)
160     elif isinstance(value, dict):
161         value = dict(value)
162         for k, v in value.iteritems():
163             value[k] = as_raw(v)
164     return value
165
166
167 def as_raw_list(value):
168     """
169     Assuming value is a list, converts its values using :func:`as_raw`.
170     """
171
172     if value is None:
173         return []
174     if isinstance(value, dict):
175         value = value.itervalues()
176     return [as_raw(v) for v in value]
177
178
179 def as_raw_dict(value):
180     """
181     Assuming value is a dict, converts its values using :func:`as_raw`. The keys are left as is.
182     """
183
184     if value is None:
185         return OrderedDict()
186     return OrderedDict((
187         (k, as_raw(v)) for k, v in value.iteritems()))
188
189
190 def as_agnostic(value):
191     """
192     Converts subclasses of list and dict to standard lists and dicts, and Unicode strings to
193     non-Unicode if possible, recursively.
194
195     Useful for creating human-readable output of structures.
196     """
197
198     if isinstance(value, unicode):
199         try:
200             value = str(value)
201         except UnicodeEncodeError:
202             pass
203     elif isinstance(value, list):
204         value = list(value)
205     elif isinstance(value, dict):
206         value = dict(value)
207
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)
214
215     return value
216
217
218 def json_dumps(value, indent=2):
219     """
220     JSON dumps that supports Unicode and the ``as_raw`` property of objects if available.
221     """
222
223     return json.dumps(value, indent=indent, ensure_ascii=False, cls=JsonAsRawEncoder)
224
225
226 def yaml_dumps(value, indent=2):
227     """
228     YAML dumps that supports Unicode and the ``as_raw`` property of objects if available.
229     """
230
231     return yaml.dump(value, indent=indent, allow_unicode=True, Dumper=YamlAsRawDumper)
232
233
234 def yaml_loads(value):
235     return yaml.load(value, Loader=yaml.SafeLoader)