Mercurial > babel > old > babel-test
comparison babel/messages/extract.py @ 80:9c84b9fa5d30
Added support for translator comments at the API and frontends levels.(See #12, item 1). Updated docs and tests accordingly.
author | palgarvio |
---|---|
date | Sun, 10 Jun 2007 14:21:01 +0000 |
parents | 05502363c925 |
children | 1e89661e6b26 |
comparison
equal
deleted
inserted
replaced
79:9a05230571f8 | 80:9c84b9fa5d30 |
---|---|
25 try: | 25 try: |
26 set | 26 set |
27 except NameError: | 27 except NameError: |
28 from sets import Set as set | 28 from sets import Set as set |
29 import sys | 29 import sys |
30 from tokenize import generate_tokens, NAME, OP, STRING | 30 from tokenize import generate_tokens, NAME, OP, STRING, COMMENT |
31 | 31 |
32 from babel.util import pathmatch, relpath | 32 from babel.util import pathmatch, relpath |
33 | 33 |
34 __all__ = ['extract', 'extract_from_dir', 'extract_from_file'] | 34 __all__ = ['extract', 'extract_from_dir', 'extract_from_file'] |
35 __docformat__ = 'restructuredtext en' | 35 __docformat__ = 'restructuredtext en' |
48 | 48 |
49 DEFAULT_MAPPING = [('**.py', 'python')] | 49 DEFAULT_MAPPING = [('**.py', 'python')] |
50 | 50 |
51 def extract_from_dir(dirname=os.getcwd(), method_map=DEFAULT_MAPPING, | 51 def extract_from_dir(dirname=os.getcwd(), method_map=DEFAULT_MAPPING, |
52 options_map=None, keywords=DEFAULT_KEYWORDS, | 52 options_map=None, keywords=DEFAULT_KEYWORDS, |
53 callback=None): | 53 comments_tags=[], callback=None): |
54 """Extract messages from any source files found in the given directory. | 54 """Extract messages from any source files found in the given directory. |
55 | 55 |
56 This function generates tuples of the form: | 56 This function generates tuples of the form: |
57 | 57 |
58 ``(filename, lineno, message)`` | 58 ``(filename, lineno, message)`` |
135 for opattern, odict in options_map.items(): | 135 for opattern, odict in options_map.items(): |
136 if pathmatch(opattern, filename): | 136 if pathmatch(opattern, filename): |
137 options = odict | 137 options = odict |
138 if callback: | 138 if callback: |
139 callback(filename, method, options) | 139 callback(filename, method, options) |
140 for lineno, message in extract_from_file(method, filepath, | 140 for lineno, message, comments in \ |
141 keywords=keywords, | 141 extract_from_file(method, filepath, |
142 options=options): | 142 keywords=keywords, |
143 yield filename, lineno, message | 143 comments_tags=comments_tags, |
144 options=options): | |
145 yield filename, lineno, message, comments | |
144 break | 146 break |
145 | 147 |
146 def extract_from_file(method, filename, keywords=DEFAULT_KEYWORDS, | 148 def extract_from_file(method, filename, keywords=DEFAULT_KEYWORDS, |
147 options=None): | 149 comments_tags=[], options=None): |
148 """Extract messages from a specific file. | 150 """Extract messages from a specific file. |
149 | 151 |
150 This function returns a list of tuples of the form: | 152 This function returns a list of tuples of the form: |
151 | 153 |
152 ``(lineno, funcname, message)`` | 154 ``(lineno, funcname, message)`` |
161 :return: the list of extracted messages | 163 :return: the list of extracted messages |
162 :rtype: `list` | 164 :rtype: `list` |
163 """ | 165 """ |
164 fileobj = open(filename, 'U') | 166 fileobj = open(filename, 'U') |
165 try: | 167 try: |
166 return list(extract(method, fileobj, keywords, options=options)) | 168 return list(extract(method, fileobj, keywords, |
169 comments_tags=comments_tags, options=options)) | |
167 finally: | 170 finally: |
168 fileobj.close() | 171 fileobj.close() |
169 | 172 |
170 def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, options=None): | 173 def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comments_tags=[], |
174 options=None): | |
171 """Extract messages from the given file-like object using the specified | 175 """Extract messages from the given file-like object using the specified |
172 extraction method. | 176 extraction method. |
173 | 177 |
174 This function returns a list of tuples of the form: | 178 This function returns a list of tuples of the form: |
175 | 179 |
176 ``(lineno, message)`` | 180 ``(lineno, message, comments)`` |
177 | 181 |
178 The implementation dispatches the actual extraction to plugins, based on the | 182 The implementation dispatches the actual extraction to plugins, based on the |
179 value of the ``method`` parameter. | 183 value of the ``method`` parameter. |
180 | 184 |
181 >>> source = '''# foo module | 185 >>> source = '''# foo module |
184 ... ''' | 188 ... ''' |
185 | 189 |
186 >>> from StringIO import StringIO | 190 >>> from StringIO import StringIO |
187 >>> for message in extract('python', StringIO(source)): | 191 >>> for message in extract('python', StringIO(source)): |
188 ... print message | 192 ... print message |
189 (3, 'Hello, world!') | 193 (3, 'Hello, world!', []) |
190 | 194 |
191 :param method: a string specifying the extraction method (.e.g. "python") | 195 :param method: a string specifying the extraction method (.e.g. "python") |
192 :param fileobj: the file-like object the messages should be extracted from | 196 :param fileobj: the file-like object the messages should be extracted from |
193 :param keywords: a dictionary mapping keywords (i.e. names of functions | 197 :param keywords: a dictionary mapping keywords (i.e. names of functions |
194 that should be recognized as translation functions) to | 198 that should be recognized as translation functions) to |
195 tuples that specify which of their arguments contain | 199 tuples that specify which of their arguments contain |
196 localizable strings | 200 localizable strings |
201 :param comments_tags: a list of translator tags to search for and include in | |
202 output | |
197 :param options: a dictionary of additional options (optional) | 203 :param options: a dictionary of additional options (optional) |
198 :return: the list of extracted messages | 204 :return: the list of extracted messages |
199 :rtype: `list` | 205 :rtype: `list` |
200 :raise ValueError: if the extraction method is not registered | 206 :raise ValueError: if the extraction method is not registered |
201 """ | 207 """ |
202 from pkg_resources import working_set | 208 from pkg_resources import working_set |
203 | 209 |
204 for entry_point in working_set.iter_entry_points(GROUP_NAME, method): | 210 for entry_point in working_set.iter_entry_points(GROUP_NAME, method): |
205 func = entry_point.load(require=True) | 211 func = entry_point.load(require=True) |
206 m = [] | 212 m = [] |
207 for lineno, funcname, messages in func(fileobj, keywords.keys(), | 213 for lineno, funcname, messages, comments in \ |
208 options=options or {}): | 214 func(fileobj, |
215 keywords.keys(), | |
216 comments_tags=comments_tags, | |
217 options=options or {}): | |
209 if isinstance(messages, (list, tuple)): | 218 if isinstance(messages, (list, tuple)): |
210 msgs = [] | 219 msgs = [] |
211 for index in keywords[funcname]: | 220 for index in keywords[funcname]: |
212 msgs.append(messages[index - 1]) | 221 msgs.append(messages[index - 1]) |
213 messages = tuple(msgs) | 222 messages = tuple(msgs) |
214 if len(messages) == 1: | 223 if len(messages) == 1: |
215 messages = messages[0] | 224 messages = messages[0] |
216 yield lineno, messages | 225 yield lineno, messages, comments |
217 return | 226 return |
218 | 227 |
219 raise ValueError('Unknown extraction method %r' % method) | 228 raise ValueError('Unknown extraction method %r' % method) |
220 | 229 |
221 def extract_nothing(fileobj, keywords, options): | 230 def extract_nothing(fileobj, keywords, comments_tags, options): |
222 """Pseudo extractor that does not actually extract anything, but simply | 231 """Pseudo extractor that does not actually extract anything, but simply |
223 returns an empty list. | 232 returns an empty list. |
224 """ | 233 """ |
225 return [] | 234 return [] |
226 | 235 |
227 def extract_genshi(fileobj, keywords, options): | 236 def extract_genshi(fileobj, keywords, comments_tags, options): |
228 """Extract messages from Genshi templates. | 237 """Extract messages from Genshi templates. |
229 | 238 |
230 :param fileobj: the file-like object the messages should be extracted from | 239 :param fileobj: the file-like object the messages should be extracted from |
231 :param keywords: a list of keywords (i.e. function names) that should be | 240 :param keywords: a list of keywords (i.e. function names) that should be |
232 recognized as translation functions | 241 recognized as translation functions |
251 include_attrs = include_attrs.split() | 260 include_attrs = include_attrs.split() |
252 | 261 |
253 tmpl = template_class(fileobj, filename=getattr(fileobj, 'name'), | 262 tmpl = template_class(fileobj, filename=getattr(fileobj, 'name'), |
254 encoding=encoding) | 263 encoding=encoding) |
255 translator = Translator(None, ignore_tags, include_attrs) | 264 translator = Translator(None, ignore_tags, include_attrs) |
256 for message in translator.extract(tmpl.stream, gettext_functions=keywords): | 265 for lineno, func, message in translator.extract(tmpl.stream, |
257 yield message | 266 gettext_functions=keywords): |
258 | 267 yield lineno, func, message, [] |
259 def extract_python(fileobj, keywords, options): | 268 |
269 def extract_python(fileobj, keywords, comments_tags, options): | |
260 """Extract messages from Python source code. | 270 """Extract messages from Python source code. |
261 | 271 |
262 :param fileobj: the file-like object the messages should be extracted from | 272 :param fileobj: the file-like object the messages should be extracted from |
263 :param keywords: a list of keywords (i.e. function names) that should be | 273 :param keywords: a list of keywords (i.e. function names) that should be |
264 recognized as translation functions | 274 recognized as translation functions |
268 """ | 278 """ |
269 funcname = None | 279 funcname = None |
270 lineno = None | 280 lineno = None |
271 buf = [] | 281 buf = [] |
272 messages = [] | 282 messages = [] |
283 translator_comments = [] | |
273 in_args = False | 284 in_args = False |
285 in_translator_comments = False | |
274 | 286 |
275 tokens = generate_tokens(fileobj.readline) | 287 tokens = generate_tokens(fileobj.readline) |
276 for tok, value, (lineno, _), _, _ in tokens: | 288 for tok, value, (lineno, _), _, _ in tokens: |
277 if funcname and tok == OP and value == '(': | 289 if funcname and tok == OP and value == '(': |
278 in_args = True | 290 in_args = True |
291 elif tok == COMMENT: | |
292 if in_translator_comments is True: | |
293 translator_comments.append(value[1:].strip()) | |
294 continue | |
295 for comments_tag in comments_tags: | |
296 if comments_tag in value: | |
297 if in_translator_comments is not True: | |
298 in_translator_comments = True | |
299 translator_comments.append(value[1:].strip()) | |
279 elif funcname and in_args: | 300 elif funcname and in_args: |
280 if tok == OP and value == ')': | 301 if tok == OP and value == ')': |
281 in_args = False | 302 in_args = in_translator_comments = False |
282 if buf: | 303 if buf: |
283 messages.append(''.join(buf)) | 304 messages.append(''.join(buf)) |
284 del buf[:] | 305 del buf[:] |
285 if filter(None, messages): | 306 if filter(None, messages): |
286 if len(messages) > 1: | 307 if len(messages) > 1: |
287 messages = tuple(messages) | 308 messages = tuple(messages) |
288 else: | 309 else: |
289 messages = messages[0] | 310 messages = messages[0] |
290 yield lineno, funcname, messages | 311 yield lineno, funcname, messages, translator_comments |
291 funcname = lineno = None | 312 funcname = lineno = None |
292 messages = [] | 313 messages = [] |
314 translator_comments = [] | |
293 elif tok == STRING: | 315 elif tok == STRING: |
294 if lineno is None: | 316 if lineno is None: |
295 lineno = stup[0] | 317 lineno = stup[0] |
296 # Unwrap quotes in a safe manner | 318 # Unwrap quotes in a safe manner |
297 buf.append(eval(value, {'__builtins__':{}}, {})) | 319 buf.append(eval(value, {'__builtins__':{}}, {})) |