1
# Copyright 2011 OpenStack Foundation.
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5
# not use this file except in compliance with the License. You may obtain
6
# 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, WITHOUT
12
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
# License for the specific language governing permissions and limitations
17
System-level utilities and helper functions.
26
from ceilometerclient.openstack.common.gettextutils import _ # noqa
29
# Used for looking up extensions of text
30
# to their 'multiplied' byte amount
38
BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)')
40
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
41
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
43
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
44
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
47
def int_from_bool_as_string(subject):
48
"""Interpret a string as a boolean and return either 1 or 0.
52
('True', 'true', 'On', 'on', '1')
54
is interpreted as a boolean True.
56
Useful for JSON-decoded stuff and config file parsing
58
return bool_from_string(subject) and 1 or 0
61
def bool_from_string(subject, strict=False):
62
"""Interpret a string as a boolean.
64
A case-insensitive match is performed such that strings matching 't',
65
'true', 'on', 'y', 'yes', or '1' are considered True and, when
66
`strict=False`, anything else is considered False.
68
Useful for JSON-decoded stuff and config file parsing.
70
If `strict=True`, unrecognized values, including None, will raise a
71
ValueError which is useful when parsing values passed in from an API call.
72
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
74
if not isinstance(subject, six.string_types):
75
subject = str(subject)
77
lowered = subject.strip().lower()
79
if lowered in TRUE_STRINGS:
81
elif lowered in FALSE_STRINGS:
84
acceptable = ', '.join(
85
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
86
msg = _("Unrecognized value '%(val)s', acceptable values are:"
87
" %(acceptable)s") % {'val': subject,
88
'acceptable': acceptable}
94
def safe_decode(text, incoming=None, errors='strict'):
95
"""Decodes incoming str using `incoming` if they're not already unicode.
97
:param incoming: Text's current encoding
98
:param errors: Errors handling policy. See here for valid
99
values http://docs.python.org/2/library/codecs.html
100
:returns: text or a unicode `incoming` encoded
101
representation of it.
102
:raises TypeError: If text is not an instance of str
104
if not isinstance(text, six.string_types):
105
raise TypeError("%s can't be decoded" % type(text))
107
if isinstance(text, six.text_type):
111
incoming = (sys.stdin.encoding or
112
sys.getdefaultencoding())
115
return text.decode(incoming, errors)
116
except UnicodeDecodeError:
117
# Note(flaper87) If we get here, it means that
118
# sys.stdin.encoding / sys.getdefaultencoding
119
# didn't return a suitable encoding to decode
120
# text. This happens mostly when global LANG
121
# var is not set correctly and there's no
122
# default encoding. In this case, most likely
123
# python will use ASCII or ANSI encoders as
124
# default encodings but they won't be capable
125
# of decoding non-ASCII characters.
127
# Also, UTF-8 is being used since it's an ASCII
129
return text.decode('utf-8', errors)
132
def safe_encode(text, incoming=None,
133
encoding='utf-8', errors='strict'):
134
"""Encodes incoming str/unicode using `encoding`.
136
If incoming is not specified, text is expected to be encoded with
137
current python's default encoding. (`sys.getdefaultencoding`)
139
:param incoming: Text's current encoding
140
:param encoding: Expected encoding for text (Default UTF-8)
141
:param errors: Errors handling policy. See here for valid
142
values http://docs.python.org/2/library/codecs.html
143
:returns: text or a bytestring `encoding` encoded
144
representation of it.
145
:raises TypeError: If text is not an instance of str
147
if not isinstance(text, six.string_types):
148
raise TypeError("%s can't be encoded" % type(text))
151
incoming = (sys.stdin.encoding or
152
sys.getdefaultencoding())
154
if isinstance(text, six.text_type):
155
return text.encode(encoding, errors)
156
elif text and encoding != incoming:
157
# Decode text before encoding it with `encoding`
158
text = safe_decode(text, incoming, errors)
159
return text.encode(encoding, errors)
164
def to_bytes(text, default=0):
165
"""Converts a string into an integer of bytes.
167
Looks at the last characters of the text to determine
168
what conversion is needed to turn the input text into a byte number.
169
Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive)
171
:param text: String input for bytes size conversion.
172
:param default: Default return value when text is blank.
175
match = BYTE_REGEX.search(text)
177
magnitude = int(match.group(1))
178
mult_key_org = match.group(2)
182
msg = _('Invalid string format: %s') % text
186
mult_key = mult_key_org.lower().replace('b', '', 1)
187
multiplier = BYTE_MULTIPLIERS.get(mult_key)
188
if multiplier is None:
189
msg = _('Unknown byte multiplier: %s') % mult_key_org
191
return magnitude * multiplier
194
def to_slug(value, incoming=None, errors="strict"):
197
Convert to lowercase, remove non-word characters, and convert spaces
200
Inspired by Django's `slugify` filter.
202
:param value: Text to slugify
203
:param incoming: Text's current encoding
204
:param errors: Errors handling policy. See here for valid
205
values http://docs.python.org/2/library/codecs.html
206
:returns: slugified unicode representation of `value`
207
:raises TypeError: If text is not an instance of str
209
value = safe_decode(value, incoming, errors)
210
# NOTE(aababilov): no need to use safe_(encode|decode) here:
211
# encodings are always "ascii", error handling is always "ignore"
212
# and types are always known (first: unicode; second: str)
213
value = unicodedata.normalize("NFKD", value).encode(
214
"ascii", "ignore").decode("ascii")
215
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
216
return SLUGIFY_HYPHENATE_RE.sub("-", value)