diff options
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.py | 609 |
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) + ) |