aboutsummaryrefslogtreecommitdiffstats
path: root/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/plural.py
diff options
context:
space:
mode:
Diffstat (limited to 'jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/plural.py')
-rw-r--r--jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/plural.py609
1 files changed, 609 insertions, 0 deletions
diff --git a/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/plural.py b/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/plural.py
new file mode 100644
index 0000000..980629d
--- /dev/null
+++ b/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/plural.py
@@ -0,0 +1,609 @@
+# -*- coding: utf-8 -*-
+"""
+ babel.numbers
+ ~~~~~~~~~~~~~
+
+ CLDR Plural support. See UTS #35.
+
+ :copyright: (c) 2013 by the Babel Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+import sys
+
+from babel._compat import Decimal
+
+
+_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):
+ if i == n:
+ n = i
+ else:
+ # 2.6's Decimal cannot convert from float directly
+ if sys.version_info < (2, 7):
+ n = str(n)
+ n = Decimal(n)
+
+ if isinstance(n, 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
+ resulting object is callable and accepts one parameter with a positive or
+ negative number (both integer and float) for the number that indicates the
+ plural form for a string and returns the tag for the format:
+
+ >>> rule = PluralRule({'one': 'n is 1'})
+ >>> rule(1)
+ 'one'
+ >>> rule(2)
+ 'other'
+
+ Currently the CLDR defines these tags: zero, one, two, few, many and
+ other where other is an implicit default. Rules should be mutually
+ exclusive; for a given numeric value, only one rule should apply (i.e.
+ the condition should only be true for one of the plural rule elements.
+
+ .. _`CLDR rules`: http://www.unicode.org/reports/tr35/tr35-33/tr35-numbers.html#Language_Plural_Rules
+ """
+
+ __slots__ = ('abstract', '_func')
+
+ def __init__(self, rules):
+ """Initialize the rule instance.
+
+ :param rules: a list of ``(tag, expr)``) tuples with the rules
+ conforming to UTS #35 or a dict with the tags as keys
+ and expressions as values.
+ :raise RuleError: if the expression is malformed
+ """
+ if isinstance(rules, dict):
+ rules = rules.items()
+ found = set()
+ self.abstract = []
+ for key, expr in sorted(list(rules)):
+ if key not in _plural_tags:
+ raise ValueError('unknown tag %r' % key)
+ elif key in found:
+ raise ValueError('tag %r defined twice' % key)
+ found.add(key)
+ ast = _Parser(expr).ast
+ if ast:
+ self.abstract.append((key, ast))
+
+ def __repr__(self):
+ rules = self.rules
+ return '<%s %r>' % (
+ type(self).__name__,
+ ', '.join(['%s: %s' % (tag, rules[tag]) for tag in _plural_tags
+ if tag in rules])
+ )
+
+ @classmethod
+ def parse(cls, rules):
+ """Create a `PluralRule` instance for the given rules. If the rules
+ are a `PluralRule` object, that object is returned.
+
+ :param rules: the rules as list or dict, or a `PluralRule` object
+ :raise RuleError: if the expression is malformed
+ """
+ if isinstance(rules, cls):
+ return rules
+ return cls(rules)
+
+ @property
+ def rules(self):
+ """The `PluralRule` as a dict of unicode plural rules.
+
+ >>> rule = PluralRule({'one': 'n is 1'})
+ >>> rule.rules
+ {'one': 'n is 1'}
+ """
+ _compile = _UnicodeCompiler().compile
+ return dict([(tag, _compile(ast)) for tag, ast in self.abstract])
+
+ tags = property(lambda x: frozenset([i[0] for i in x.abstract]), doc="""
+ A set of explicitly defined tags in this rule. The implicit default
+ ``'other'`` rules is not part of this set unless there is an explicit
+ rule for it.""")
+
+ def __getstate__(self):
+ return self.abstract
+
+ def __setstate__(self, abstract):
+ self.abstract = abstract
+
+ def __call__(self, n):
+ if not hasattr(self, '_func'):
+ self._func = to_python(self)
+ return self._func(n)
+
+
+def to_javascript(rule):
+ """Convert a list/dict of rules or a `PluralRule` object into a JavaScript
+ function. This function depends on no external library:
+
+ >>> to_javascript({'one': 'n is 1'})
+ "(function(n) { return (n == 1) ? 'one' : 'other'; })"
+
+ Implementation detail: The function generated will probably evaluate
+ expressions involved into range operations multiple times. This has the
+ advantage that external helper functions are not required and is not a
+ big performance hit for these simple calculations.
+
+ :param rule: the rules as list or dict, or a `PluralRule` object
+ :raise RuleError: if the expression is malformed
+ """
+ to_js = _JavaScriptCompiler().compile
+ result = ['(function(n) { return ']
+ for tag, ast in PluralRule.parse(rule).abstract:
+ result.append('%s ? %r : ' % (to_js(ast), tag))
+ result.append('%r; })' % _fallback_tag)
+ return ''.join(result)
+
+
+def to_python(rule):
+ """Convert a list/dict of rules or a `PluralRule` object into a regular
+ Python function. This is useful in situations where you need a real
+ function and don't are about the actual rule object:
+
+ >>> func = to_python({'one': 'n is 1', 'few': 'n in 2..4'})
+ >>> func(1)
+ 'one'
+ >>> func(3)
+ 'few'
+ >>> func = to_python({'one': 'n in 1,11', 'few': 'n in 3..10,13..19'})
+ >>> func(11)
+ 'one'
+ >>> func(15)
+ 'few'
+
+ :param rule: the rules as list or dict, or a `PluralRule` object
+ :raise RuleError: if the expression is malformed
+ """
+ namespace = {
+ 'IN': in_range_list,
+ 'WITHIN': within_range_list,
+ 'MOD': cldr_modulo,
+ 'extract_operands': extract_operands,
+ }
+ to_python_func = _PythonCompiler().compile
+ result = [
+ 'def evaluate(n):',
+ ' n, i, v, w, f, t = extract_operands(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_func(ast), str(tag)))
+ result.append(' return %r' % _fallback_tag)
+ code = compile('\n'.join(result), '<rule>', 'exec')
+ eval(code, namespace)
+ return namespace['evaluate']
+
+
+def to_gettext(rule):
+ """The plural rule as gettext expression. The gettext expression is
+ technically limited to integers and returns indices rather than tags.
+
+ >>> to_gettext({'one': 'n is 1', 'two': 'n is 2'})
+ 'nplurals=3; plural=((n == 1) ? 0 : (n == 2) ? 1 : 2)'
+
+ :param rule: the rules as list or dict, or a `PluralRule` object
+ :raise RuleError: if the expression is malformed
+ """
+ rule = PluralRule.parse(rule)
+
+ used_tags = rule.tags | set([_fallback_tag])
+ _compile = _GettextCompiler().compile
+ _get_index = [tag for tag in _plural_tags if tag in used_tags].index
+
+ result = ['nplurals=%d; plural=(' % len(used_tags)]
+ for tag, ast in rule.abstract:
+ result.append('%s ? %d : ' % (_compile(ast), _get_index(tag)))
+ result.append('%d)' % _get_index(_fallback_tag))
+ return ''.join(result)
+
+
+def in_range_list(num, range_list):
+ """Integer range list test. This is the callback for the "in" operator
+ of the UTS #35 pluralization rule language:
+
+ >>> in_range_list(1, [(1, 3)])
+ True
+ >>> in_range_list(3, [(1, 3)])
+ True
+ >>> in_range_list(3, [(1, 3), (5, 8)])
+ True
+ >>> in_range_list(1.2, [(1, 4)])
+ False
+ >>> in_range_list(10, [(1, 4)])
+ False
+ >>> in_range_list(10, [(1, 4), (6, 8)])
+ False
+ """
+ return num == int(num) and within_range_list(num, range_list)
+
+
+def within_range_list(num, range_list):
+ """Float range test. This is the callback for the "within" operator
+ of the UTS #35 pluralization rule language:
+
+ >>> within_range_list(1, [(1, 3)])
+ True
+ >>> within_range_list(1.0, [(1, 3)])
+ True
+ >>> within_range_list(1.2, [(1, 4)])
+ True
+ >>> within_range_list(8.8, [(1, 4), (7, 15)])
+ True
+ >>> within_range_list(10, [(1, 4)])
+ False
+ >>> within_range_list(10.5, [(1, 4), (20, 30)])
+ False
+ """
+ return any(num >= min_ and num <= max_ for min_, max_ in range_list)
+
+
+def cldr_modulo(a, b):
+ """Javaish modulo. This modulo operator returns the value with the sign
+ of the dividend rather than the divisor like Python does:
+
+ >>> cldr_modulo(-3, 5)
+ -3
+ >>> cldr_modulo(-3, -5)
+ -3
+ >>> cldr_modulo(3, 5)
+ 3
+ """
+ reverse = 0
+ if a < 0:
+ a *= -1
+ reverse = 1
+ if b < 0:
+ b *= -1
+ rv = a % b
+ if reverse:
+ rv *= -1
+ return rv
+
+
+class RuleError(Exception):
+ """Raised if a rule is malformed."""
+
+_VARS = 'nivwft'
+
+_RULES = [
+ (None, re.compile(r'\s+(?u)')),
+ ('word', re.compile(r'\b(and|or|is|(?:with)?in|not|mod|[{0}])\b'
+ .format(_VARS))),
+ ('value', re.compile(r'\d+')),
+ ('symbol', re.compile(r'%|,|!=|=')),
+ ('ellipsis', re.compile(r'\.{2,3}|\u2026', re.UNICODE)) # U+2026: ELLIPSIS
+]
+
+
+def tokenize_rule(s):
+ s = s.split('@')[0]
+ result = []
+ pos = 0
+ end = len(s)
+ while pos < end:
+ for tok, rule in _RULES:
+ match = rule.match(s, pos)
+ if match is not None:
+ pos = match.end()
+ if tok:
+ result.append((tok, match.group()))
+ break
+ else:
+ raise RuleError('malformed CLDR pluralization rule. '
+ 'Got unexpected %r' % s[pos])
+ return result[::-1]
+
+
+def test_next_token(tokens, type_, value=None):
+ return tokens and tokens[-1][0] == type_ and \
+ (value is None or tokens[-1][1] == value)
+
+
+def skip_token(tokens, type_, value=None):
+ if test_next_token(tokens, type_, value):
+ return tokens.pop()
+
+
+def value_node(value):
+ return 'value', (value, )
+
+
+def ident_node(name):
+ return name, ()
+
+
+def range_list_node(range_list):
+ return 'range_list', range_list
+
+
+def negate(rv):
+ return 'not', (rv,)
+
+
+class _Parser(object):
+ """Internal parser. This class can translate a single rule into an abstract
+ tree of tuples. It implements the following grammar::
+
+ condition = and_condition ('or' and_condition)*
+ ('@integer' samples)?
+ ('@decimal' samples)?
+ and_condition = relation ('and' relation)*
+ relation = is_relation | in_relation | within_relation
+ is_relation = expr 'is' ('not')? value
+ in_relation = expr (('not')? 'in' | '=' | '!=') range_list
+ within_relation = expr ('not')? 'within' range_list
+ expr = operand (('mod' | '%') value)?
+ operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
+ range_list = (range | value) (',' range_list)*
+ value = digit+
+ digit = 0|1|2|3|4|5|6|7|8|9
+ range = value'..'value
+ samples = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
+ sampleRange = decimalValue '~' decimalValue
+ decimalValue = value ('.' value)?
+
+ - Whitespace can occur between or around any of the above tokens.
+ - Rules should be mutually exclusive; for a given numeric value, only one
+ rule should apply (i.e. the condition should only be true for one of
+ the plural rule elements).
+ - The in and within relations can take comma-separated lists, such as:
+ 'n in 3,5,7..15'.
+ - Samples are ignored.
+
+ The translator parses the expression on instanciation into an attribute
+ called `ast`.
+ """
+
+ def __init__(self, string):
+ self.tokens = tokenize_rule(string)
+ if not self.tokens:
+ # If the pattern is only samples, it's entirely possible
+ # no stream of tokens whatsoever is generated.
+ self.ast = None
+ return
+ self.ast = self.condition()
+ if self.tokens:
+ raise RuleError('Expected end of rule, got %r' %
+ self.tokens[-1][1])
+
+ def expect(self, type_, value=None, term=None):
+ token = skip_token(self.tokens, type_, value)
+ if token is not None:
+ return token
+ if term is None:
+ term = repr(value is None and type_ or value)
+ if not self.tokens:
+ raise RuleError('expected %s but end of rule reached' % term)
+ raise RuleError('expected %s but got %r' % (term, self.tokens[-1][1]))
+
+ def condition(self):
+ op = self.and_condition()
+ while skip_token(self.tokens, 'word', 'or'):
+ op = 'or', (op, self.and_condition())
+ return op
+
+ def and_condition(self):
+ op = self.relation()
+ while skip_token(self.tokens, 'word', 'and'):
+ op = 'and', (op, self.relation())
+ return op
+
+ def relation(self):
+ left = self.expr()
+ if skip_token(self.tokens, 'word', 'is'):
+ return skip_token(self.tokens, 'word', 'not') and 'isnot' or 'is', \
+ (left, self.value())
+ negated = skip_token(self.tokens, 'word', 'not')
+ method = 'in'
+ if skip_token(self.tokens, 'word', 'within'):
+ method = 'within'
+ else:
+ if not skip_token(self.tokens, 'word', 'in'):
+ if negated:
+ raise RuleError('Cannot negate operator based rules.')
+ return self.newfangled_relation(left)
+ rv = 'relation', (method, left, self.range_list())
+ return negate(rv) if negated else rv
+
+ def newfangled_relation(self, left):
+ if skip_token(self.tokens, 'symbol', '='):
+ negated = False
+ elif skip_token(self.tokens, 'symbol', '!='):
+ negated = True
+ else:
+ raise RuleError('Expected "=" or "!=" or legacy relation')
+ rv = 'relation', ('in', left, self.range_list())
+ return negate(rv) if negated else rv
+
+ def range_or_value(self):
+ left = self.value()
+ if skip_token(self.tokens, 'ellipsis'):
+ return left, self.value()
+ else:
+ return left, left
+
+ def range_list(self):
+ range_list = [self.range_or_value()]
+ while skip_token(self.tokens, 'symbol', ','):
+ range_list.append(self.range_or_value())
+ return range_list_node(range_list)
+
+ def expr(self):
+ word = skip_token(self.tokens, 'word')
+ if word is None or word[1] not in _VARS:
+ raise RuleError('Expected identifier variable')
+ name = word[1]
+ if skip_token(self.tokens, 'word', 'mod'):
+ return 'mod', ((name, ()), self.value())
+ elif skip_token(self.tokens, 'symbol', '%'):
+ return 'mod', ((name, ()), self.value())
+ return ident_node(name)
+
+ def value(self):
+ return value_node(int(self.expect('value')[1]))
+
+
+def _binary_compiler(tmpl):
+ """Compiler factory for the `_Compiler`."""
+ return lambda self, l, r: tmpl % (self.compile(l), self.compile(r))
+
+
+def _unary_compiler(tmpl):
+ """Compiler factory for the `_Compiler`."""
+ return lambda self, x: tmpl % self.compile(x)
+
+
+compile_zero = lambda x: '0'
+
+
+class _Compiler(object):
+ """The compilers are able to transform the expressions into multiple
+ output formats.
+ """
+
+ def compile(self, arg):
+ op, args = arg
+ return getattr(self, 'compile_' + op)(*args)
+
+ compile_n = lambda x: 'n'
+ compile_i = lambda x: 'i'
+ compile_v = lambda x: 'v'
+ compile_w = lambda x: 'w'
+ compile_f = lambda x: 'f'
+ compile_t = lambda x: 't'
+ compile_value = lambda x, v: str(v)
+ compile_and = _binary_compiler('(%s && %s)')
+ compile_or = _binary_compiler('(%s || %s)')
+ compile_not = _unary_compiler('(!%s)')
+ compile_mod = _binary_compiler('(%s %% %s)')
+ compile_is = _binary_compiler('(%s == %s)')
+ compile_isnot = _binary_compiler('(%s != %s)')
+
+ def compile_relation(self, method, expr, range_list):
+ raise NotImplementedError()
+
+
+class _PythonCompiler(_Compiler):
+ """Compiles an expression to Python."""
+
+ compile_and = _binary_compiler('(%s and %s)')
+ compile_or = _binary_compiler('(%s or %s)')
+ compile_not = _unary_compiler('(not %s)')
+ compile_mod = _binary_compiler('MOD(%s, %s)')
+
+ def compile_relation(self, method, expr, range_list):
+ compile_range_list = '[%s]' % ','.join(
+ ['(%s, %s)' % tuple(map(self.compile, range_))
+ for range_ in range_list[1]])
+ return '%s(%s, %s)' % (method.upper(), self.compile(expr),
+ compile_range_list)
+
+
+class _GettextCompiler(_Compiler):
+ """Compile into a gettext plural expression."""
+
+ compile_i = _Compiler.compile_n
+ compile_v = compile_zero
+ compile_w = compile_zero
+ compile_f = compile_zero
+ compile_t = compile_zero
+
+ def compile_relation(self, method, expr, range_list):
+ rv = []
+ expr = self.compile(expr)
+ for item in range_list[1]:
+ if item[0] == item[1]:
+ rv.append('(%s == %s)' % (
+ expr,
+ self.compile(item[0])
+ ))
+ else:
+ min, max = map(self.compile, item)
+ rv.append('(%s >= %s && %s <= %s)' % (
+ expr,
+ min,
+ expr,
+ max
+ ))
+ return '(%s)' % ' || '.join(rv)
+
+
+class _JavaScriptCompiler(_GettextCompiler):
+ """Compiles the expression to plain of JavaScript."""
+
+ # XXX: presently javascript does not support any of the
+ # fraction support and basically only deals with integers.
+ compile_i = lambda x: 'parseInt(n, 10)'
+ compile_v = compile_zero
+ compile_w = compile_zero
+ compile_f = compile_zero
+ compile_t = compile_zero
+
+ def compile_relation(self, method, expr, range_list):
+ code = _GettextCompiler.compile_relation(
+ self, method, expr, range_list)
+ if method == 'in':
+ expr = self.compile(expr)
+ code = '(parseInt(%s, 10) == %s && %s)' % (expr, expr, code)
+ return code
+
+
+class _UnicodeCompiler(_Compiler):
+ """Returns a unicode pluralization rule again."""
+
+ # XXX: this currently spits out the old syntax instead of the new
+ # one. We can change that, but it will break a whole bunch of stuff
+ # for users I suppose.
+
+ compile_is = _binary_compiler('%s is %s')
+ compile_isnot = _binary_compiler('%s is not %s')
+ compile_and = _binary_compiler('%s and %s')
+ compile_or = _binary_compiler('%s or %s')
+ compile_mod = _binary_compiler('%s mod %s')
+
+ def compile_not(self, relation):
+ return self.compile_relation(negated=True, *relation[1])
+
+ def compile_relation(self, method, expr, range_list, negated=False):
+ ranges = []
+ for item in range_list[1]:
+ if item[0] == item[1]:
+ ranges.append(self.compile(item[0]))
+ else:
+ ranges.append('%s..%s' % tuple(map(self.compile, item)))
+ return '%s%s %s %s' % (
+ self.compile(expr), negated and ' not' or '',
+ method, ','.join(ranges)
+ )