Mercurial > babel > old > babel-test
annotate babel/messages/checkers.py @ 470:5c4daf6cbe53 stable-0.9.x
Merged revisions 459 via svnmerge from
http://svn.edgewall.org/repos/babel/trunk
........
r459 | palgarvio | 2008-12-16 00:14:50 +0100 (di, 16 dec 2008) | 2 lines
Typo's and sorted output of `list-locales`.
........
author | jruigrok |
---|---|
date | Sun, 11 Apr 2010 08:15:12 +0000 |
parents | c2ae38340540 |
children | 07a27065102a |
rev | line source |
---|---|
220
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
1 # -*- coding: utf-8 -*- |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
2 # |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
3 # Copyright (C) 2007 Edgewall Software |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
4 # All rights reserved. |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
5 # |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
6 # This software is licensed as described in the file COPYING, which |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
7 # you should have received as part of this distribution. The terms |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
8 # are also available at http://babel.edgewall.org/wiki/License. |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
9 # |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
10 # This software consists of voluntary contributions made by many |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
11 # individuals. For the exact contribution history, see the revision |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
12 # history and logs, available at http://babel.edgewall.org/log/. |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
13 |
234 | 14 """Various routines that help with validation of translations. |
15 | |
16 :since: version 0.9 | |
17 """ | |
220
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
18 |
369 | 19 from itertools import izip |
220
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
20 from babel.messages.catalog import TranslationError, PYTHON_FORMAT |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
21 |
369 | 22 #: list of format chars that are compatible to each other |
23 _string_format_compatibilities = [ | |
24 set(['i', 'd', 'u']), | |
25 set(['x', 'X']), | |
26 set(['f', 'F', 'g', 'G']) | |
27 ] | |
28 | |
29 | |
220
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
30 def num_plurals(catalog, message): |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
31 """Verify the number of plurals in the translation.""" |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
32 if not message.pluralizable: |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
33 if not isinstance(message.string, basestring): |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
34 raise TranslationError("Found plural forms for non-pluralizable " |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
35 "message") |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
36 return |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
37 |
369 | 38 # skip further tests if no catalog is provided. |
39 elif catalog is None: | |
40 return | |
41 | |
220
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
42 msgstrs = message.string |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
43 if not isinstance(msgstrs, (list, tuple)): |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
44 msgstrs = (msgstrs,) |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
45 if len(msgstrs) != catalog.num_plurals: |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
46 raise TranslationError("Wrong number of plural forms (expected %d)" % |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
47 catalog.num_plurals) |
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
48 |
369 | 49 |
220
677147547e2d
Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
cmlenz
parents:
diff
changeset
|
50 def python_format(catalog, message): |
369 | 51 """Verify the format string placeholders in the translation.""" |
52 if 'python-format' not in message.flags: | |
53 return | |
54 msgids = message.id | |
55 if not isinstance(msgids, (list, tuple)): | |
56 msgids = (msgids,) | |
57 msgstrs = message.string | |
58 if not isinstance(msgstrs, (list, tuple)): | |
59 msgstrs = (msgstrs,) | |
60 | |
61 for msgid, msgstr in izip(msgids, msgstrs): | |
62 if msgstr: | |
63 _validate_format(msgid, msgstr) | |
64 | |
65 | |
66 def _validate_format(format, alternative): | |
67 """Test format string `alternative` against `format`. `format` can be the | |
68 msgid of a message and `alternative` one of the `msgstr`\s. The two | |
69 arguments are not interchangeable as `alternative` may contain less | |
70 placeholders if `format` uses named placeholders. | |
71 | |
72 If `format` does not use string formatting a `ValueError` is raised. | |
73 | |
74 If the string formatting of `alternative` is compatible to `format` the | |
75 function returns `None`, otherwise a `TranslationError` is raised. | |
76 | |
77 Examples for compatible format strings: | |
78 | |
79 >>> _validate_format('Hello %s!', 'Hallo %s!') | |
80 >>> _validate_format('Hello %i!', 'Hallo %d!') | |
81 | |
82 Example for an incompatible format strings: | |
83 | |
84 >>> _validate_format('Hello %(name)s!', 'Hallo %s!') | |
85 Traceback (most recent call last): | |
86 ... | |
87 TranslationError: the format strings are of different kinds | |
88 | |
89 This function is used by the `python_format` checker. | |
90 | |
91 :param format: The original format string | |
92 :param alternative: The alternative format string that should be checked | |
93 against format | |
94 :return: None on success | |
95 :raises TranslationError: on formatting errors | |
96 """ | |
97 | |
98 def _parse(string): | |
99 result = [] | |
100 for match in PYTHON_FORMAT.finditer(string): | |
101 name, format, typechar = match.groups() | |
102 if typechar == '%' and name is None: | |
103 continue | |
104 result.append((name, str(typechar))) | |
105 return result | |
106 | |
107 def _compatible(a, b): | |
108 if a == b: | |
109 return True | |
110 for set in _string_format_compatibilities: | |
111 if a in set and b in set: | |
112 return True | |
113 return False | |
114 | |
115 def _check_positional(results): | |
116 positional = None | |
117 for name, char in results: | |
118 if positional is None: | |
119 positional = name is None | |
120 else: | |
121 if (name is None) != positional: | |
122 raise TranslationError('format string mixes positional ' | |
123 'and named placeholders') | |
124 return bool(positional) | |
125 | |
126 a, b = map(_parse, (format, alternative)) | |
127 | |
128 # if a does not use string formattings, we are dealing with invalid | |
129 # input data. This function only works if the first string provided | |
130 # does contain string format chars | |
131 if not a: | |
132 raise ValueError('original string provided does not use string ' | |
133 'formatting.') | |
134 | |
135 # now check if both strings are positional or named | |
136 a_positional, b_positional = map(_check_positional, (a, b)) | |
137 if a_positional and not b_positional and not b: | |
138 raise TranslationError('placeholders are incompatible') | |
139 elif a_positional != b_positional: | |
140 raise TranslationError('the format strings are of different kinds') | |
141 | |
142 # if we are operating on positional strings both must have the | |
143 # same number of format chars and those must be compatible | |
144 if a_positional: | |
145 if len(a) != len(b): | |
146 raise TranslationError('positional format placeholders are ' | |
147 'unbalanced') | |
148 for idx, ((_, first), (_, second)) in enumerate(izip(a, b)): | |
149 if not _compatible(first, second): | |
150 raise TranslationError('incompatible format for placeholder ' | |
151 '%d: %r and %r are not compatible' % | |
152 (idx + 1, first, second)) | |
153 | |
154 # otherwise the second string must not have names the first one | |
155 # doesn't have and the types of those included must be compatible | |
156 else: | |
157 type_map = dict(a) | |
158 for name, typechar in b: | |
159 if name not in type_map: | |
160 raise TranslationError('unknown named placeholder %r' % name) | |
161 elif not _compatible(typechar, type_map[name]): | |
162 raise TranslationError('incompatible format for ' | |
163 'placeholder %r: ' | |
164 '%r and %r are not compatible' % | |
165 (name, typechar, type_map[name])) | |
166 | |
167 | |
168 def _find_checkers(): | |
169 try: | |
170 from pkg_resources import working_set | |
171 except ImportError: | |
172 return [num_plurals, python_format] | |
173 checkers = [] | |
174 for entry_point in working_set.iter_entry_points('babel.checkers'): | |
175 checkers.append(entry_point.load()) | |
176 return checkers | |
177 | |
178 | |
179 checkers = _find_checkers() |