plural_to_python
10 June 2008
22:51
as Python
| 1 | def plural_to_python(expr): |
|---|---|
| 2 | """Gets a C expression as used in PO files for plural forms and returns a |
| 3 | Python function that implements an equivalent expression. |
| 4 | |
| 5 | >>> f = plural_to_python('(n != 1)') |
| 6 | >>> f(0), f(1), f(2), f(3) |
| 7 | (1, 0, 1, 1) |
| 8 | |
| 9 | >>> f = plural_to_python('n==1 ? 0 : n==2 ? 1 : 2') |
| 10 | >>> f(0), f(1), f(2), f(3) |
| 11 | (2, 0, 1, 2) |
| 12 | |
| 13 | >>> f = plural_to_python('(n==1') |
| 14 | Traceback (most recent call last): |
| 15 | ... |
| 16 | ValueError: Syntax error in plural form expression |
| 17 | |
| 18 | >>> f = plural_to_python('(foo==1)') |
| 19 | Traceback (most recent call last): |
| 20 | ... |
| 21 | ValueError: Unsafe plural form expression |
| 22 | |
| 23 | :param expr: a string containing a C expression as used for plural forms |
| 24 | in PO files |
| 25 | :return: the Python equivalent of the expression as a callable that expects |
| 26 | the number as single positional argument |
| 27 | :rtype: ``callable`` |
| 28 | |
| 29 | :note: The implementation of this function is based on the ``c2py`` |
| 30 | function in the standard ``gettext`` module. |
| 31 | """ |
| 32 | # Security check, allow only the "n" identifier |
| 33 | tokens = tokenize.generate_tokens(StringIO(expr).readline) |
| 34 | try: |
| 35 | if [x for x in tokens if x[0] == token.NAME and x[1] != 'n']: |
| 36 | raise ValueError('Unsafe plural form expression') |
| 37 | except tokenize.TokenError: |
| 38 | raise ValueError('Syntax error in plural form expression') |
| 39 | |
| 40 | # Replace some C operators by their Python equivalents |
| 41 | expr = expr.replace('&&', ' and ').replace('||', ' or ') |
| 42 | expr = re.sub(r'\!([^=])', ' not \\1', expr) |
| 43 | |
| 44 | # Define C-like "cond ? true : false" |
| 45 | def _test(condition, true, false): |
| 46 | if condition: |
| 47 | return true |
| 48 | else: |
| 49 | return false |
| 50 | |
| 51 | # Regular expression and replacement function used to transform |
| 52 | # "a?b:c" to "test(a,b,c)". |
| 53 | regex = re.compile(r'(.*?)\?(.*?):(.*)') |
| 54 | def _repl(m): |
| 55 | return "test(%s, %s, %s)" % (m.group(1), m.group(2), |
| 56 | regex.sub(_repl, m.group(3))) |
| 57 | |
| 58 | # Code to transform the plural expression, taking care of parentheses |
| 59 | stack = [''] |
| 60 | for char in expr: |
| 61 | if char == '(': |
| 62 | stack.append('') |
| 63 | elif char == ')': |
| 64 | subexpr = regex.sub(_repl, stack.pop()) |
| 65 | stack[-1] += '(%s)' % subexpr |
| 66 | else: |
| 67 | stack[-1] += char |
| 68 | expr = regex.sub(_repl, stack.pop()) |
| 69 | |
| 70 | try: |
| 71 | return eval('lambda n: int(%s)' % expr, {'test': _test}, {}) |
| 72 | except SyntaxError: |
| 73 | raise ValueError('Syntax error in plural form expression') |
| 74 |
Comments
No comments so far.
