summaryrefslogtreecommitdiff
path: root/ceilometerclient/openstack
diff options
context:
space:
mode:
Diffstat (limited to 'ceilometerclient/openstack')
-rw-r--r--ceilometerclient/openstack/common/_i18n.py45
-rw-r--r--ceilometerclient/openstack/common/apiclient/base.py4
-rw-r--r--ceilometerclient/openstack/common/apiclient/client.py4
-rw-r--r--ceilometerclient/openstack/common/apiclient/exceptions.py2
-rw-r--r--ceilometerclient/openstack/common/cliutils.py100
-rw-r--r--ceilometerclient/openstack/common/gettextutils.py479
-rw-r--r--ceilometerclient/openstack/common/strutils.py222
7 files changed, 73 insertions, 783 deletions
diff --git a/ceilometerclient/openstack/common/_i18n.py b/ceilometerclient/openstack/common/_i18n.py
new file mode 100644
index 0000000..2d613bf
--- /dev/null
+++ b/ceilometerclient/openstack/common/_i18n.py
@@ -0,0 +1,45 @@
+# 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.
+
+"""oslo.i18n integration module.
+
+See http://docs.openstack.org/developer/oslo.i18n/usage.html
+
+"""
+
+try:
+ import oslo.i18n
+
+ # NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
+ # application name when this module is synced into the separate
+ # repository. It is OK to have more than one translation function
+ # using the same domain, since there will still only be one message
+ # catalog.
+ _translators = oslo.i18n.TranslatorFactory(domain='ceilometerclient')
+
+ # The primary translation function using the well-known name "_"
+ _ = _translators.primary
+
+ # Translators for log levels.
+ #
+ # The abbreviated names are meant to reflect the usual use of a short
+ # name like '_'. The "L" is for "log" and the other letter comes from
+ # the level.
+ _LI = _translators.log_info
+ _LW = _translators.log_warning
+ _LE = _translators.log_error
+ _LC = _translators.log_critical
+except ImportError:
+ # NOTE(dims): Support for cases where a project wants to use
+ # code from ceilometerclient-incubator, but is not ready to be internationalized
+ # (like tempest)
+ _ = _LI = _LW = _LE = _LC = lambda x: x
diff --git a/ceilometerclient/openstack/common/apiclient/base.py b/ceilometerclient/openstack/common/apiclient/base.py
index b5394f8..a090f81 100644
--- a/ceilometerclient/openstack/common/apiclient/base.py
+++ b/ceilometerclient/openstack/common/apiclient/base.py
@@ -26,12 +26,12 @@ Base utilities to build API operation managers and objects on top of.
import abc
import copy
+from oslo.utils import strutils
import six
from six.moves.urllib import parse
+from ceilometerclient.openstack.common._i18n import _
from ceilometerclient.openstack.common.apiclient import exceptions
-from ceilometerclient.openstack.common.gettextutils import _
-from ceilometerclient.openstack.common import strutils
def getid(obj):
diff --git a/ceilometerclient/openstack/common/apiclient/client.py b/ceilometerclient/openstack/common/apiclient/client.py
index bb17b0c..e17afc4 100644
--- a/ceilometerclient/openstack/common/apiclient/client.py
+++ b/ceilometerclient/openstack/common/apiclient/client.py
@@ -33,11 +33,11 @@ try:
except ImportError:
import json
+from oslo.utils import importutils
import requests
+from ceilometerclient.openstack.common._i18n import _
from ceilometerclient.openstack.common.apiclient import exceptions
-from ceilometerclient.openstack.common.gettextutils import _
-from ceilometerclient.openstack.common import importutils
_logger = logging.getLogger(__name__)
diff --git a/ceilometerclient/openstack/common/apiclient/exceptions.py b/ceilometerclient/openstack/common/apiclient/exceptions.py
index 2f68ffb..7b82ae5 100644
--- a/ceilometerclient/openstack/common/apiclient/exceptions.py
+++ b/ceilometerclient/openstack/common/apiclient/exceptions.py
@@ -25,7 +25,7 @@ import sys
import six
-from ceilometerclient.openstack.common.gettextutils import _
+from ceilometerclient.openstack.common._i18n import _
class ClientException(Exception):
diff --git a/ceilometerclient/openstack/common/cliutils.py b/ceilometerclient/openstack/common/cliutils.py
index c690028..341cd01 100644
--- a/ceilometerclient/openstack/common/cliutils.py
+++ b/ceilometerclient/openstack/common/cliutils.py
@@ -24,14 +24,21 @@ import os
import sys
import textwrap
+from oslo.utils import encodeutils
+from oslo.utils import strutils
import prettytable
import six
from six import moves
-from ceilometerclient.openstack.common.apiclient import exceptions
-from ceilometerclient.openstack.common.gettextutils import _
-from ceilometerclient.openstack.common import strutils
-from ceilometerclient.openstack.common import uuidutils
+from ceilometerclient.openstack.common._i18n import _
+
+
+class MissingArgs(Exception):
+ """Supplied arguments are not sufficient for calling a function."""
+ def __init__(self, missing):
+ self.missing = missing
+ msg = _("Missing arguments: %s") % ", ".join(missing)
+ super(MissingArgs, self).__init__(msg)
def validate_args(fn, *args, **kwargs):
@@ -64,7 +71,7 @@ def validate_args(fn, *args, **kwargs):
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
if missing:
- raise exceptions.MissingArgs(missing)
+ raise MissingArgs(missing)
def arg(*args, **kwargs):
@@ -156,7 +163,7 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
kwargs = {}
else:
kwargs = {'sortby': field_labels[sortby_index]}
- pt = prettytable.PrettyTable(field_labels, caching=False)
+ pt = prettytable.PrettyTable(field_labels)
pt.align = 'l'
for o in objs:
@@ -173,7 +180,10 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
row.append(data)
pt.add_row(row)
- print(strutils.safe_encode(pt.get_string(**kwargs)))
+ if six.PY3:
+ print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
+ else:
+ print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0):
@@ -183,7 +193,7 @@ def print_dict(dct, dict_property="Property", wrap=0):
:param dict_property: name of the first column
:param wrap: wrapping for the second column
"""
- pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False)
+ pt = prettytable.PrettyTable([dict_property, 'Value'])
pt.align = 'l'
for k, v in six.iteritems(dct):
# convert dict to str to check length
@@ -201,7 +211,11 @@ def print_dict(dct, dict_property="Property", wrap=0):
col1 = ''
else:
pt.add_row([k, v])
- print(strutils.safe_encode(pt.get_string()))
+
+ if six.PY3:
+ print(encodeutils.safe_encode(pt.get_string()).decode())
+ else:
+ print(encodeutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3):
@@ -225,74 +239,6 @@ def get_password(max_password_prompts=3):
return pw
-def find_resource(manager, name_or_id, **find_args):
- """Look for resource in a given manager.
-
- Used as a helper for the _find_* methods.
- Example:
-
- .. code-block:: python
-
- def _find_hypervisor(cs, hypervisor):
- #Get a hypervisor by name or ID.
- return cliutils.find_resource(cs.hypervisors, hypervisor)
- """
- # first try to get entity as integer id
- try:
- return manager.get(int(name_or_id))
- except (TypeError, ValueError, exceptions.NotFound):
- pass
-
- # now try to get entity as uuid
- try:
- if six.PY2:
- tmp_id = strutils.safe_encode(name_or_id)
- else:
- tmp_id = strutils.safe_decode(name_or_id)
-
- if uuidutils.is_uuid_like(tmp_id):
- return manager.get(tmp_id)
- except (TypeError, ValueError, exceptions.NotFound):
- pass
-
- # for str id which is not uuid
- if getattr(manager, 'is_alphanum_id_allowed', False):
- try:
- return manager.get(name_or_id)
- except exceptions.NotFound:
- pass
-
- try:
- try:
- return manager.find(human_id=name_or_id, **find_args)
- except exceptions.NotFound:
- pass
-
- # finally try to find entity by name
- try:
- resource = getattr(manager, 'resource_class', None)
- name_attr = resource.NAME_ATTR if resource else 'name'
- kwargs = {name_attr: name_or_id}
- kwargs.update(find_args)
- return manager.find(**kwargs)
- except exceptions.NotFound:
- msg = _("No %(name)s with a name or "
- "ID of '%(name_or_id)s' exists.") % \
- {
- "name": manager.resource_class.__name__.lower(),
- "name_or_id": name_or_id
- }
- raise exceptions.CommandError(msg)
- except exceptions.NoUniqueMatch:
- msg = _("Multiple %(name)s matches found for "
- "'%(name_or_id)s', use an ID to be more specific.") % \
- {
- "name": manager.resource_class.__name__.lower(),
- "name_or_id": name_or_id
- }
- raise exceptions.CommandError(msg)
-
-
def service_type(stype):
"""Adds 'service_type' attribute to decorated function.
diff --git a/ceilometerclient/openstack/common/gettextutils.py b/ceilometerclient/openstack/common/gettextutils.py
deleted file mode 100644
index 75082d7..0000000
--- a/ceilometerclient/openstack/common/gettextutils.py
+++ /dev/null
@@ -1,479 +0,0 @@
-# Copyright 2012 Red Hat, Inc.
-# Copyright 2013 IBM Corp.
-# 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.
-
-"""
-gettext for openstack-common modules.
-
-Usual usage in an openstack.common module:
-
- from ceilometerclient.openstack.common.gettextutils import _
-"""
-
-import copy
-import gettext
-import locale
-from logging import handlers
-import os
-
-from babel import localedata
-import six
-
-_AVAILABLE_LANGUAGES = {}
-
-# FIXME(dhellmann): Remove this when moving to oslo.i18n.
-USE_LAZY = False
-
-
-class TranslatorFactory(object):
- """Create translator functions
- """
-
- def __init__(self, domain, localedir=None):
- """Establish a set of translation functions for the domain.
-
- :param domain: Name of translation domain,
- specifying a message catalog.
- :type domain: str
- :param lazy: Delays translation until a message is emitted.
- Defaults to False.
- :type lazy: Boolean
- :param localedir: Directory with translation catalogs.
- :type localedir: str
- """
- self.domain = domain
- if localedir is None:
- localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
- self.localedir = localedir
-
- def _make_translation_func(self, domain=None):
- """Return a new translation function ready for use.
-
- Takes into account whether or not lazy translation is being
- done.
-
- The domain can be specified to override the default from the
- factory, but the localedir from the factory is always used
- because we assume the log-level translation catalogs are
- installed in the same directory as the main application
- catalog.
-
- """
- if domain is None:
- domain = self.domain
- t = gettext.translation(domain,
- localedir=self.localedir,
- fallback=True)
- # Use the appropriate method of the translation object based
- # on the python version.
- m = t.gettext if six.PY3 else t.ugettext
-
- def f(msg):
- """oslo.i18n.gettextutils translation function."""
- if USE_LAZY:
- return Message(msg, domain=domain)
- return m(msg)
- return f
-
- @property
- def primary(self):
- "The default translation function."
- return self._make_translation_func()
-
- def _make_log_translation_func(self, level):
- return self._make_translation_func(self.domain + '-log-' + level)
-
- @property
- def log_info(self):
- "Translate info-level log messages."
- return self._make_log_translation_func('info')
-
- @property
- def log_warning(self):
- "Translate warning-level log messages."
- return self._make_log_translation_func('warning')
-
- @property
- def log_error(self):
- "Translate error-level log messages."
- return self._make_log_translation_func('error')
-
- @property
- def log_critical(self):
- "Translate critical-level log messages."
- return self._make_log_translation_func('critical')
-
-
-# NOTE(dhellmann): When this module moves out of the incubator into
-# oslo.i18n, these global variables can be moved to an integration
-# module within each application.
-
-# Create the global translation functions.
-_translators = TranslatorFactory('ceilometerclient')
-
-# The primary translation function using the well-known name "_"
-_ = _translators.primary
-
-# Translators for log levels.
-#
-# The abbreviated names are meant to reflect the usual use of a short
-# name like '_'. The "L" is for "log" and the other letter comes from
-# the level.
-_LI = _translators.log_info
-_LW = _translators.log_warning
-_LE = _translators.log_error
-_LC = _translators.log_critical
-
-# NOTE(dhellmann): End of globals that will move to the application's
-# integration module.
-
-
-def enable_lazy():
- """Convenience function for configuring _() to use lazy gettext
-
- Call this at the start of execution to enable the gettextutils._
- function to use lazy gettext functionality. This is useful if
- your project is importing _ directly instead of using the
- gettextutils.install() way of importing the _ function.
- """
- global USE_LAZY
- USE_LAZY = True
-
-
-def install(domain):
- """Install a _() function using the given translation domain.
-
- Given a translation domain, install a _() function using gettext's
- install() function.
-
- The main difference from gettext.install() is that we allow
- overriding the default localedir (e.g. /usr/share/locale) using
- a translation-domain-specific environment variable (e.g.
- NOVA_LOCALEDIR).
-
- Note that to enable lazy translation, enable_lazy must be
- called.
-
- :param domain: the translation domain
- """
- from six import moves
- tf = TranslatorFactory(domain)
- moves.builtins.__dict__['_'] = tf.primary
-
-
-class Message(six.text_type):
- """A Message object is a unicode object that can be translated.
-
- Translation of Message is done explicitly using the translate() method.
- For all non-translation intents and purposes, a Message is simply unicode,
- and can be treated as such.
- """
-
- def __new__(cls, msgid, msgtext=None, params=None,
- domain='ceilometerclient', *args):
- """Create a new Message object.
-
- In order for translation to work gettext requires a message ID, this
- msgid will be used as the base unicode text. It is also possible
- for the msgid and the base unicode text to be different by passing
- the msgtext parameter.
- """
- # If the base msgtext is not given, we use the default translation
- # of the msgid (which is in English) just in case the system locale is
- # not English, so that the base text will be in that locale by default.
- if not msgtext:
- msgtext = Message._translate_msgid(msgid, domain)
- # We want to initialize the parent unicode with the actual object that
- # would have been plain unicode if 'Message' was not enabled.
- msg = super(Message, cls).__new__(cls, msgtext)
- msg.msgid = msgid
- msg.domain = domain
- msg.params = params
- return msg
-
- def translate(self, desired_locale=None):
- """Translate this message to the desired locale.
-
- :param desired_locale: The desired locale to translate the message to,
- if no locale is provided the message will be
- translated to the system's default locale.
-
- :returns: the translated message in unicode
- """
-
- translated_message = Message._translate_msgid(self.msgid,
- self.domain,
- desired_locale)
- if self.params is None:
- # No need for more translation
- return translated_message
-
- # This Message object may have been formatted with one or more
- # Message objects as substitution arguments, given either as a single
- # argument, part of a tuple, or as one or more values in a dictionary.
- # When translating this Message we need to translate those Messages too
- translated_params = _translate_args(self.params, desired_locale)
-
- translated_message = translated_message % translated_params
-
- return translated_message
-
- @staticmethod
- def _translate_msgid(msgid, domain, desired_locale=None):
- if not desired_locale:
- system_locale = locale.getdefaultlocale()
- # If the system locale is not available to the runtime use English
- if not system_locale[0]:
- desired_locale = 'en_US'
- else:
- desired_locale = system_locale[0]
-
- locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
- lang = gettext.translation(domain,
- localedir=locale_dir,
- languages=[desired_locale],
- fallback=True)
- if six.PY3:
- translator = lang.gettext
- else:
- translator = lang.ugettext
-
- translated_message = translator(msgid)
- return translated_message
-
- def __mod__(self, other):
- # When we mod a Message we want the actual operation to be performed
- # by the parent class (i.e. unicode()), the only thing we do here is
- # save the original msgid and the parameters in case of a translation
- params = self._sanitize_mod_params(other)
- unicode_mod = super(Message, self).__mod__(params)
- modded = Message(self.msgid,
- msgtext=unicode_mod,
- params=params,
- domain=self.domain)
- return modded
-
- def _sanitize_mod_params(self, other):
- """Sanitize the object being modded with this Message.
-
- - Add support for modding 'None' so translation supports it
- - Trim the modded object, which can be a large dictionary, to only
- those keys that would actually be used in a translation
- - Snapshot the object being modded, in case the message is
- translated, it will be used as it was when the Message was created
- """
- if other is None:
- params = (other,)
- elif isinstance(other, dict):
- # Merge the dictionaries
- # Copy each item in case one does not support deep copy.
- params = {}
- if isinstance(self.params, dict):
- for key, val in self.params.items():
- params[key] = self._copy_param(val)
- for key, val in other.items():
- params[key] = self._copy_param(val)
- else:
- params = self._copy_param(other)
- return params
-
- def _copy_param(self, param):
- try:
- return copy.deepcopy(param)
- except Exception:
- # Fallback to casting to unicode this will handle the
- # python code-like objects that can't be deep-copied
- return six.text_type(param)
-
- def __add__(self, other):
- msg = _('Message objects do not support addition.')
- raise TypeError(msg)
-
- def __radd__(self, other):
- return self.__add__(other)
-
- if six.PY2:
- def __str__(self):
- # NOTE(luisg): Logging in python 2.6 tries to str() log records,
- # and it expects specifically a UnicodeError in order to proceed.
- msg = _('Message objects do not support str() because they may '
- 'contain non-ascii characters. '
- 'Please use unicode() or translate() instead.')
- raise UnicodeError(msg)
-
-
-def get_available_languages(domain):
- """Lists the available languages for the given translation domain.
-
- :param domain: the domain to get languages for
- """
- if domain in _AVAILABLE_LANGUAGES:
- return copy.copy(_AVAILABLE_LANGUAGES[domain])
-
- localedir = '%s_LOCALEDIR' % domain.upper()
- find = lambda x: gettext.find(domain,
- localedir=os.environ.get(localedir),
- languages=[x])
-
- # NOTE(mrodden): en_US should always be available (and first in case
- # order matters) since our in-line message strings are en_US
- language_list = ['en_US']
- # NOTE(luisg): Babel <1.0 used a function called list(), which was
- # renamed to locale_identifiers() in >=1.0, the requirements master list
- # requires >=0.9.6, uncapped, so defensively work with both. We can remove
- # this check when the master list updates to >=1.0, and update all projects
- list_identifiers = (getattr(localedata, 'list', None) or
- getattr(localedata, 'locale_identifiers'))
- locale_identifiers = list_identifiers()
-
- for i in locale_identifiers:
- if find(i) is not None:
- language_list.append(i)
-
- # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
- # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
- # are perfectly legitimate locales:
- # https://github.com/mitsuhiko/babel/issues/37
- # In Babel 1.3 they fixed the bug and they support these locales, but
- # they are still not explicitly "listed" by locale_identifiers().
- # That is why we add the locales here explicitly if necessary so that
- # they are listed as supported.
- aliases = {'zh': 'zh_CN',
- 'zh_Hant_HK': 'zh_HK',
- 'zh_Hant': 'zh_TW',
- 'fil': 'tl_PH'}
- for (locale_, alias) in six.iteritems(aliases):
- if locale_ in language_list and alias not in language_list:
- language_list.append(alias)
-
- _AVAILABLE_LANGUAGES[domain] = language_list
- return copy.copy(language_list)
-
-
-def translate(obj, desired_locale=None):
- """Gets the translated unicode representation of the given object.
-
- If the object is not translatable it is returned as-is.
- If the locale is None the object is translated to the system locale.
-
- :param obj: the object to translate
- :param desired_locale: the locale to translate the message to, if None the
- default system locale will be used
- :returns: the translated object in unicode, or the original object if
- it could not be translated
- """
- message = obj
- if not isinstance(message, Message):
- # If the object to translate is not already translatable,
- # let's first get its unicode representation
- message = six.text_type(obj)
- if isinstance(message, Message):
- # Even after unicoding() we still need to check if we are
- # running with translatable unicode before translating
- return message.translate(desired_locale)
- return obj
-
-
-def _translate_args(args, desired_locale=None):
- """Translates all the translatable elements of the given arguments object.
-
- This method is used for translating the translatable values in method
- arguments which include values of tuples or dictionaries.
- If the object is not a tuple or a dictionary the object itself is
- translated if it is translatable.
-
- If the locale is None the object is translated to the system locale.
-
- :param args: the args to translate
- :param desired_locale: the locale to translate the args to, if None the
- default system locale will be used
- :returns: a new args object with the translated contents of the original
- """
- if isinstance(args, tuple):
- return tuple(translate(v, desired_locale) for v in args)
- if isinstance(args, dict):
- translated_dict = {}
- for (k, v) in six.iteritems(args):
- translated_v = translate(v, desired_locale)
- translated_dict[k] = translated_v
- return translated_dict
- return translate(args, desired_locale)
-
-
-class TranslationHandler(handlers.MemoryHandler):
- """Handler that translates records before logging them.
-
- The TranslationHandler takes a locale and a target logging.Handler object
- to forward LogRecord objects to after translating them. This handler
- depends on Message objects being logged, instead of regular strings.
-
- The handler can be configured declaratively in the logging.conf as follows:
-
- [handlers]
- keys = translatedlog, translator
-
- [handler_translatedlog]
- class = handlers.WatchedFileHandler
- args = ('/var/log/api-localized.log',)
- formatter = context
-
- [handler_translator]
- class = openstack.common.log.TranslationHandler
- target = translatedlog
- args = ('zh_CN',)
-
- If the specified locale is not available in the system, the handler will
- log in the default locale.
- """
-
- def __init__(self, locale=None, target=None):
- """Initialize a TranslationHandler
-
- :param locale: locale to use for translating messages
- :param target: logging.Handler object to forward
- LogRecord objects to after translation
- """
- # NOTE(luisg): In order to allow this handler to be a wrapper for
- # other handlers, such as a FileHandler, and still be able to
- # configure it using logging.conf, this handler has to extend
- # MemoryHandler because only the MemoryHandlers' logging.conf
- # parsing is implemented such that it accepts a target handler.
- handlers.MemoryHandler.__init__(self, capacity=0, target=target)
- self.locale = locale
-
- def setFormatter(self, fmt):
- self.target.setFormatter(fmt)
-
- def emit(self, record):
- # We save the message from the original record to restore it
- # after translation, so other handlers are not affected by this
- original_msg = record.msg
- original_args = record.args
-
- try:
- self._translate_and_log_record(record)
- finally:
- record.msg = original_msg
- record.args = original_args
-
- def _translate_and_log_record(self, record):
- record.msg = translate(record.msg, self.locale)
-
- # In addition to translating the message, we also need to translate
- # arguments that were passed to the log method that were not part
- # of the main message e.g., log.info(_('Some message %s'), this_one))
- record.args = _translate_args(record.args, self.locale)
-
- self.target.emit(record)
diff --git a/ceilometerclient/openstack/common/strutils.py b/ceilometerclient/openstack/common/strutils.py
deleted file mode 100644
index 23b117e..0000000
--- a/ceilometerclient/openstack/common/strutils.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# Copyright 2011 OpenStack Foundation.
-# 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.
-
-"""
-System-level utilities and helper functions.
-"""
-
-import re
-import sys
-import unicodedata
-
-import six
-
-from ceilometerclient.openstack.common.gettextutils import _
-
-
-# Used for looking up extensions of text
-# to their 'multiplied' byte amount
-BYTE_MULTIPLIERS = {
- '': 1,
- 't': 1024 ** 4,
- 'g': 1024 ** 3,
- 'm': 1024 ** 2,
- 'k': 1024,
-}
-BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)')
-
-TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
-FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
-
-SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
-SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
-
-
-def int_from_bool_as_string(subject):
- """Interpret a string as a boolean and return either 1 or 0.
-
- Any string value in:
-
- ('True', 'true', 'On', 'on', '1')
-
- is interpreted as a boolean True.
-
- Useful for JSON-decoded stuff and config file parsing
- """
- return bool_from_string(subject) and 1 or 0
-
-
-def bool_from_string(subject, strict=False):
- """Interpret a string as a boolean.
-
- A case-insensitive match is performed such that strings matching 't',
- 'true', 'on', 'y', 'yes', or '1' are considered True and, when
- `strict=False`, anything else is considered False.
-
- Useful for JSON-decoded stuff and config file parsing.
-
- If `strict=True`, unrecognized values, including None, will raise a
- ValueError which is useful when parsing values passed in from an API call.
- Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
- """
- if not isinstance(subject, six.string_types):
- subject = str(subject)
-
- lowered = subject.strip().lower()
-
- if lowered in TRUE_STRINGS:
- return True
- elif lowered in FALSE_STRINGS:
- return False
- elif strict:
- acceptable = ', '.join(
- "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
- msg = _("Unrecognized value '%(val)s', acceptable values are:"
- " %(acceptable)s") % {'val': subject,
- 'acceptable': acceptable}
- raise ValueError(msg)
- else:
- return False
-
-
-def safe_decode(text, incoming=None, errors='strict'):
- """Decodes incoming str using `incoming` if they're not already unicode.
-
- :param incoming: Text's current encoding
- :param errors: Errors handling policy. See here for valid
- values http://docs.python.org/2/library/codecs.html
- :returns: text or a unicode `incoming` encoded
- representation of it.
- :raises TypeError: If text is not an instance of str
- """
- if not isinstance(text, six.string_types):
- raise TypeError("%s can't be decoded" % type(text))
-
- if isinstance(text, six.text_type):
- return text
-
- if not incoming:
- incoming = (sys.stdin.encoding or
- sys.getdefaultencoding())
-
- try:
- return text.decode(incoming, errors)
- except UnicodeDecodeError:
- # Note(flaper87) If we get here, it means that
- # sys.stdin.encoding / sys.getdefaultencoding
- # didn't return a suitable encoding to decode
- # text. This happens mostly when global LANG
- # var is not set correctly and there's no
- # default encoding. In this case, most likely
- # python will use ASCII or ANSI encoders as
- # default encodings but they won't be capable
- # of decoding non-ASCII characters.
- #
- # Also, UTF-8 is being used since it's an ASCII
- # extension.
- return text.decode('utf-8', errors)
-
-
-def safe_encode(text, incoming=None,
- encoding='utf-8', errors='strict'):
- """Encodes incoming str/unicode using `encoding`.
-
- If incoming is not specified, text is expected to be encoded with
- current python's default encoding. (`sys.getdefaultencoding`)
-
- :param incoming: Text's current encoding
- :param encoding: Expected encoding for text (Default UTF-8)
- :param errors: Errors handling policy. See here for valid
- values http://docs.python.org/2/library/codecs.html
- :returns: text or a bytestring `encoding` encoded
- representation of it.
- :raises TypeError: If text is not an instance of str
- """
- if not isinstance(text, six.string_types):
- raise TypeError("%s can't be encoded" % type(text))
-
- if not incoming:
- incoming = (sys.stdin.encoding or
- sys.getdefaultencoding())
-
- if isinstance(text, six.text_type):
- if six.PY3:
- return text.encode(encoding, errors).decode(incoming)
- else:
- return text.encode(encoding, errors)
- elif text and encoding != incoming:
- # Decode text before encoding it with `encoding`
- text = safe_decode(text, incoming, errors)
- if six.PY3:
- return text.encode(encoding, errors).decode(incoming)
- else:
- return text.encode(encoding, errors)
-
- return text
-
-
-def to_bytes(text, default=0):
- """Converts a string into an integer of bytes.
-
- Looks at the last characters of the text to determine
- what conversion is needed to turn the input text into a byte number.
- Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive)
-
- :param text: String input for bytes size conversion.
- :param default: Default return value when text is blank.
-
- """
- match = BYTE_REGEX.search(text)
- if match:
- magnitude = int(match.group(1))
- mult_key_org = match.group(2)
- if not mult_key_org:
- return magnitude
- elif text:
- msg = _('Invalid string format: %s') % text
- raise TypeError(msg)
- else:
- return default
- mult_key = mult_key_org.lower().replace('b', '', 1)
- multiplier = BYTE_MULTIPLIERS.get(mult_key)
- if multiplier is None:
- msg = _('Unknown byte multiplier: %s') % mult_key_org
- raise TypeError(msg)
- return magnitude * multiplier
-
-
-def to_slug(value, incoming=None, errors="strict"):
- """Normalize string.
-
- Convert to lowercase, remove non-word characters, and convert spaces
- to hyphens.
-
- Inspired by Django's `slugify` filter.
-
- :param value: Text to slugify
- :param incoming: Text's current encoding
- :param errors: Errors handling policy. See here for valid
- values http://docs.python.org/2/library/codecs.html
- :returns: slugified unicode representation of `value`
- :raises TypeError: If text is not an instance of str
- """
- value = safe_decode(value, incoming, errors)
- # NOTE(aababilov): no need to use safe_(encode|decode) here:
- # encodings are always "ascii", error handling is always "ignore"
- # and types are always known (first: unicode; second: str)
- value = unicodedata.normalize("NFKD", value).encode(
- "ascii", "ignore").decode("ascii")
- value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
- return SLUGIFY_HYPHENATE_RE.sub("-", value)