Mercurial > babel > mirror
diff babel/messages/checkers.py @ 352:8860097a9765 trunk
The builtin checkers don't require setuptools any longer, validate_format and python_format from the checkers module are merged into one now.
author | aronacher |
---|---|
date | Tue, 17 Jun 2008 19:54:34 +0000 |
parents | 38053412171b |
children | 0791b3bf42cc |
line wrap: on
line diff
--- a/babel/messages/checkers.py +++ b/babel/messages/checkers.py @@ -16,8 +16,17 @@ :since: version 0.9 """ +from itertools import izip from babel.messages.catalog import TranslationError, PYTHON_FORMAT +#: list of format chars that are compatible to each other +_string_format_compatibilities = [ + set(['i', 'd', 'u']), + set(['x', 'X']), + set(['f', 'F', 'g', 'G']) +] + + def num_plurals(catalog, message): """Verify the number of plurals in the translation.""" if not message.pluralizable: @@ -33,19 +42,125 @@ raise TranslationError("Wrong number of plural forms (expected %d)" % catalog.num_plurals) + def python_format(catalog, message): - if 'python-format' in message.flags: - msgids = message.id - if not isinstance(msgids, (list, tuple)): - msgids = (msgids,) - msgstrs = message.string - if not isinstance(msgstrs, (list, tuple)): - msgstrs = (msgstrs,) - for idx, msgid in enumerate(msgids): - if not msgstrs[idx]: - continue # no translation - for match in PYTHON_FORMAT.finditer(msgid): - param = match.group(0) - if param not in msgstrs[idx]: - raise TranslationError("Python parameter %s not found in " - "translation" % param) + if 'python-format' not in message.flags: + return + msgids = message.id + if not isinstance(msgids, (list, tuple)): + msgids = (msgids,) + msgstrs = message.string + if not isinstance(msgstrs, (list, tuple)): + msgstrs = (msgstrs,) + + for msgid, msgstr in izip(msgids, msgstrs): + if msgstr: + _validate_format(msgid, msgstr) + + +def _validate_format(format, alternative): + """Test format string `alternative` against `format`. `format` can be the + msgid of a message and `alternative` one of the `msgstr`\s. The two + arguments are not interchangeable as `alternative` may contain less + placeholders if `format` uses named placeholders. + + If `format` does not use string formatting a `ValueError` is raised. + + If the string formatting of `alternative` is compatible to `format` the + function returns `None`, otherwise a `TranslationError` is raised. + + Examples for compatible format strings: + + >>> _validate_format('Hello %s!', 'Hallo %s!') + >>> _validate_format('Hello %i!', 'Hallo %d!') + + Example for an incompatible format strings: + + >>> _validate_format('Hello %(name)s!', 'Hallo %s!') + Traceback (most recent call last): + ... + TranslationError: the format strings are of different kinds + + This function is used by the `python_format` checker. + + :param format: The original format string + :param alternative: The alternative format string that should be checked + against format + :return: None on success + :raises TranslationError: on formatting errors + """ + + def _parse(string): + result = [] + for match in PYTHON_FORMAT.finditer(string): + name, format, typechar = match.groups() + if typechar == '%' and name is not None: + continue + result.append((name, typechar)) + return result + + def _compatible(a, b): + if a == b: + return True + for set in _string_format_compatibilities: + if a in set and b in set: + return True + return False + + def _check_positional(results): + positional = None + for name, char in results: + if positional is None: + positional = name is None + else: + if (name is None) != positional: + raise ValueError('format string mixes positional ' + 'and named placeholders') + return bool(positional) + + a, b = map(_parse, (format, alternative)) + + # if a does not use string formattings, we are dealing with invalid + # input data. This function only works if the first string provided + # does contain string format chars + if not a: + raise ValueError('original string provided does not use string ' + 'formatting.') + + # now check if both strings are positional or named + a_positional, b_positional = map(_check_positional, (a, b)) + if a_positional and not b_positional and not b: + raise TranslationError('placeholders are incompatible') + elif a_positional != b_positional: + raise TranslationError('the format strings are of different kinds') + + # if we are operating on positional strings both must have the + # same number of format chars and those must be compatible + if a_positional: + if len(a) != len(b): + raise TranslationError('positional format placeholders are ' + 'unbalanced') + for idx, ((_, first), (_, second)) in enumerate(izip(a, b)): + if not _compatible(first, second): + raise TranslationError('incompatible format for placeholder ' + '%d: %r and %r are not compatible' % + (idx + 1, first, second)) + + # otherwise the second string must not have names the first one + # doesn't have and the types of those included must be compatible + else: + type_map = dict(a) + for name, typechar in b: + if name not in type_map: + raise TranslationError('unknown named placeholder %r' % name) + elif not _compatible(typechar, type_map[name]): + raise TranslationErrorError('incompatible format for ' + 'placeholder %r: ' + '%r and %r are not compatible' % + (name, typechar, type_map[name])) + + +#: list of builtin checkers for babel installations without setuptools. +#: Keep this in sync with the mapping in the setup.py +#: :see: babel.messages.catalog.Catalog.check +builtin_checkers = [num_plurals, python_format]