[blivet/rhel7-branch 2/4] Fix the handling of size specs in non-English locales.

David Shea dshea at redhat.com
Wed Jan 15 22:34:51 UTC 2014


Change _parseSpec to accept both English and localized size specs.
Perform translation of specs where they are used instead of where they
are defined so that localized Size display works correctly when the
locale is changed after the module is loaded.

Related: rhbz#1039485
---
 blivet/size.py | 97 +++++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 69 insertions(+), 28 deletions(-)

diff --git a/blivet/size.py b/blivet/size.py
index 5f83d5f..aa3da90 100644
--- a/blivet/size.py
+++ b/blivet/size.py
@@ -20,6 +20,7 @@
 # Red Hat Author(s): David Cantrell <dcantrell at redhat.com>
 
 import re
+import string
 
 from decimal import Decimal
 from decimal import InvalidOperation
@@ -29,44 +30,58 @@ from errors import *
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
 P_ = lambda x, y, z: gettext.ldngettext("blivet", x, y, z)
+N_ = lambda x: x
 
 # Decimal prefixes for different size increments, along with the name
 # and accepted abbreviation for the prefix.  These prefixes are all
 # for 'bytes'.
-_decimalPrefix = [(1000, _("kilo"), _("k")),
-                  (1000**2, _("mega"), _("M")),
-                  (1000**3, _("giga"), _("G")),
-                  (1000**4, _("tera"), _("T")),
-                  (1000**5, _("peta"), _("P")),
-                  (1000**6, _("exa"), _("E")),
-                  (1000**7, _("zetta"), _("Z")),
-                  (1000**8, _("yotta"), _("Y"))]
+_decimalPrefix = [(1000, N_("kilo"), N_("k")),
+                  (1000**2, N_("mega"), N_("M")),
+                  (1000**3, N_("giga"), N_("G")),
+                  (1000**4, N_("tera"), N_("T")),
+                  (1000**5, N_("peta"), N_("P")),
+                  (1000**6, N_("exa"), N_("E")),
+                  (1000**7, N_("zetta"), N_("Z")),
+                  (1000**8, N_("yotta"), N_("Y"))]
 
 # Binary prefixes for the different size increments.  Same structure
 # as the above list.
-_binaryPrefix = [(1024, _("kibi"), _("Ki")),
-                 (1024**2, _("mebi"), _("Mi")),
-                 (1024**3, _("gibi"), _("Gi")),
-                 (1024**4, _("tebi"), None),
-                 (1024**5, _("pebi"), None),
-                 (1024**6, _("ebi"), None),
-                 (1024**7, _("zebi"), None),
-                 (1024**8, _("yobi"), None)]
+_binaryPrefix = [(1024, N_("kibi"), N_("Ki")),
+                 (1024**2, N_("mebi"), N_("Mi")),
+                 (1024**3, N_("gibi"), N_("Gi")),
+                 (1024**4, N_("tebi"), None),
+                 (1024**5, N_("pebi"), None),
+                 (1024**6, N_("ebi"), None),
+                 (1024**7, N_("zebi"), None),
+                 (1024**8, N_("yobi"), None)]
 
-_bytes = [_('b'), _('byte'), _('bytes')]
+_bytes = [N_('b'), N_('byte'), N_('bytes')]
 _prefixes = _decimalPrefix + _binaryPrefix
 
-def _makeSpecs(prefix, abbr):
+_ASCIIlower_table = string.maketrans(string.ascii_uppercase, string.ascii_lowercase)
+def _lowerASCII(s):
+    """Convert a string to lowercase using only ASCII character definitions."""
+    return string.translate(s, _ASCIIlower_table)
+
+def _makeSpecs(prefix, abbr, xlate):
     """ Internal method used to generate a list of specifiers. """
     specs = []
 
     if prefix:
-        specs.append(prefix.lower() + _("byte"))
-        specs.append(prefix.lower() + _("bytes"))
+        if xlate:
+            specs.append(prefix.lower() + _("byte"))
+            specs.append(prefix.lower() + _("bytes"))
+        else:
+            specs.append(_lowerASCII(prefix) + "byte")
+            specs.append(_lowerASCII(prefix) + "bytes")
 
     if abbr:
-        specs.append(abbr.lower() + _("b"))
-        specs.append(abbr.lower())
+        if xlate:
+            specs.append(abbr.lower() + _("b"))
+            specs.append(abbr.lower())
+        else:
+            specs.append(_lowerASCII(abbr) + "b")
+            specs.append(_lowerASCII(abbr))
 
     return specs
 
@@ -75,7 +90,10 @@ def _parseSpec(spec):
     if not spec:
         raise ValueError("invalid size specification", spec)
 
-    m = re.match(r'([0-9.]+)\s*([A-Za-z]*)$', spec.strip())
+    # Match the size specification using only digit/space/not-space, since the
+    # string might be non-English and python is profoundly awful at dealing
+    # with unicode data via regular expressions
+    m = re.match(r'(-?\s*[0-9.]+)\s*([^\s]*)$', spec.strip())
     if not m:
         raise ValueError("invalid size specification", spec)
 
@@ -87,12 +105,34 @@ def _parseSpec(spec):
     if size < 0:
         raise SizeNotPositiveError("spec= param must be >=0")
 
+    # Only attempt to parse as English if all characters are ASCII
+    try:
+        specifier = _lowerASCII(str(m.groups()[1].decode('ascii')))
+    except UnicodeDecodeError:
+        pass
+    else:
+        if specifier in _bytes or not specifier:
+            return size
+
+        for factor, prefix, abbr in _prefixes:
+            check = _makeSpecs(prefix, abbr, False)
+
+            if specifier in check:
+                return size * factor
+
+    # No English match found, try localized size specs
+    xlated_bytes = [_(b) for b in _bytes]
     specifier = m.groups()[1].lower()
-    if specifier in _bytes or not specifier:
+    if specifier in xlated_bytes:
         return size
 
-    for factor, prefix, abbr in _prefixes:
-        check = _makeSpecs(prefix, abbr)
+    xlated_prefixes = [(p[0], _(p[1]), _(p[2])) for p in _prefixes]
+    for factor, prefix, abbr in xlated_prefixes:
+        if prefix:
+            prefix = prefix.decode("utf-8")
+        if abbr:
+            abbr = abbr.decode("utf-8")
+        check = _makeSpecs(prefix, abbr, True)
 
         if specifier in check:
             return size * factor
@@ -186,7 +226,7 @@ class Size(Decimal):
             return self
 
         for factor, prefix, abbr in _prefixes:
-            check = _makeSpecs(prefix, abbr)
+            check = _makeSpecs(prefix, abbr, False)
 
             if spec in check:
                 return Decimal(self / Decimal(factor))
@@ -209,7 +249,8 @@ class Size(Decimal):
         if Decimal(check) < 1000:
             return "%s B" % check
 
-        for factor, prefix, abbr in _prefixes:
+        prefixes_xlated = [(p[0], _(p[1]), _(p[2])) for p in _prefixes]
+        for factor, prefix, abbr in prefixes_xlated:
             newcheck = super(Size, self).__div__(Decimal(factor))
 
             if newcheck < 1000:
-- 
1.8.4.2



More information about the anaconda-patches mailing list