diff options
-rw-r--r-- | openstack/common/strutils.py | 30 | ||||
-rw-r--r-- | tests/unit/test_strutils.py | 15 |
2 files changed, 45 insertions, 0 deletions
diff --git a/openstack/common/strutils.py b/openstack/common/strutils.py index bbe2c92..05c178c 100644 --- a/openstack/common/strutils.py +++ b/openstack/common/strutils.py @@ -19,7 +19,9 @@ System-level utilities and helper functions. """ +import re import sys +import unicodedata from openstack.common.gettextutils import _ @@ -38,6 +40,9 @@ BYTE_MULTIPLIERS = { 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. @@ -182,3 +187,28 @@ def to_bytes(text, default=0): raise TypeError(msg) except ValueError: return default + + +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 basestring + """ + 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) diff --git a/tests/unit/test_strutils.py b/tests/unit/test_strutils.py index 42160a6..a8d8462 100644 --- a/tests/unit/test_strutils.py +++ b/tests/unit/test_strutils.py @@ -198,3 +198,18 @@ class StrUtilsTest(utils.BaseTestCase): ] for v in breaking_examples: self.assertRaises(TypeError, strutils.to_bytes, v) + + def test_slugify(self): + to_slug = strutils.to_slug + self.assertRaises(TypeError, to_slug, True) + self.assertEqual(six.u("hello"), to_slug("hello")) + self.assertEqual(six.u("two-words"), to_slug("Two Words")) + self.assertEqual(six.u("ma-any-spa-ce-es"), + to_slug("Ma-any\t spa--ce- es")) + self.assertEqual(six.u("excamation"), to_slug("exc!amation!")) + self.assertEqual(six.u("ampserand"), to_slug("&ser$and")) + self.assertEqual(six.u("ju5tnum8er"), to_slug("ju5tnum8er")) + self.assertEqual(six.u("strip-"), to_slug(" strip - ")) + self.assertEqual(six.u("perche"), to_slug("perch\xc3\xa9")) + self.assertEqual(six.u("strange"), + to_slug("\x80strange", errors="ignore")) |