From: benselme Date: Fri, 9 Jan 2015 15:33:24 +0000 (-0500) Subject: plural.extract_operands function and tests X-Git-Tag: dev-2a51c9b95d06~51^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1beec5d6d5c4c6a14976033661e35c337df22cee;p=thirdparty%2Fbabel.git plural.extract_operands function and tests --- diff --git a/babel/plural.py b/babel/plural.py index ce6659b4..b11b17e5 100644 --- a/babel/plural.py +++ b/babel/plural.py @@ -8,7 +8,7 @@ :copyright: (c) 2013 by the Babel Team. :license: BSD, see LICENSE for more details. """ - +import decimal import re @@ -16,6 +16,32 @@ _plural_tags = ('zero', 'one', 'two', 'few', 'many', 'other') _fallback_tag = 'other' +def extract_operands(source): + """Extract operands from a decimal, a float or an int, according to + `CLDR rules`_. + + .. _`CLDR rules`: http://www.unicode.org/reports/tr35/tr35-33/tr35-numbers.html#Operands + """ + n = abs(source) + i = int(n) + if isinstance(n, float): + n = i if i == n else decimal.Decimal(n) + + if isinstance(n, decimal.Decimal): + dec_tuple = n.as_tuple() + exp = dec_tuple.exponent + fraction_digits = dec_tuple.digits[exp:] if exp < 0 else () + trailing = ''.join(str(d) for d in fraction_digits) + no_trailing = trailing.rstrip('0') + v = len(trailing) + w = len(no_trailing) + f = int(trailing or 0) + t = int(no_trailing or 0) + else: + v = w = f = t = 0 + return n, i, v, w, f, t + + class PluralRule(object): """Represents a set of language pluralization rules. The constructor accepts a list of (tag, expr) tuples or a dict of `CLDR rules`_. The @@ -106,7 +132,7 @@ class PluralRule(object): def __call__(self, n): if not hasattr(self, '_func'): self._func = to_python(self) - return self._func(n) + return self._func(*extract_operands(n)) def to_javascript(rule): @@ -156,12 +182,15 @@ def to_python(rule): 'WITHIN': within_range_list, 'MOD': cldr_modulo } - to_python = _PythonCompiler().compile - result = ['def evaluate(n):'] + to_python_func = _PythonCompiler().compile + result = [ + 'def evaluate(n, v=0, w=0, f=0, t=0):', + ' i = int(n)', + ] for tag, ast in PluralRule.parse(rule).abstract: # the str() call is to coerce the tag to the native string. It's # a limited ascii restricted set of tags anyways so that is fine. - result.append(' if (%s): return %r' % (to_python(ast), str(tag))) + result.append(' if (%s): return %r' % (to_python_func(ast), str(tag))) result.append(' return %r' % _fallback_tag) code = compile('\n'.join(result), '', 'exec') eval(code, namespace) diff --git a/tests/test_plural.py b/tests/test_plural.py index 122d64d7..ece1358c 100644 --- a/tests/test_plural.py +++ b/tests/test_plural.py @@ -10,11 +10,12 @@ # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://babel.edgewall.org/log/. +import decimal import doctest import unittest import pytest - +from decimal import Decimal as Dec from babel import plural @@ -123,7 +124,7 @@ def test_tokenize_well_formed(rule_text, tokens): MALFORMED_TOKEN_TESTS = ( - ('a = 1'), ('n ! 2'), + 'a = 1', 'n ! 2', ) @@ -201,3 +202,21 @@ class PluralRuleParserTestCase(unittest.TestCase): plural.value_node(100))), (make_range_list((1, 19))))))) )) + + +EXTRACT_OPERANDS_TESTS = ( + (1, 1, 1, 0, 0, 0, 0), + ('1.0', '1.0', 1, 1, 0, 0, 0), + ('1.00', '1.00', 1, 2, 0, 0, 0), + ('1.3', '1.3', 1, 1, 1, 3, 3), + ('1.30', '1.30', 1, 2, 1, 30, 3), + ('1.03', '1.03', 1, 2, 2, 3, 3), + ('1.230', '1.230', 1, 3, 2, 230, 23), + (-1, 1, 1, 0, 0, 0, 0), +) + + +@pytest.mark.parametrize('source,n,i,v,w,f,t', EXTRACT_OPERANDS_TESTS) +def test_extract_operands(source, n, i, v, w, f, t): + assert (plural.extract_operands(decimal.Decimal(source)) == + decimal.Decimal(n), i, v, w, f, t)