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__':{}}, {}))
Copyright (C) 2012-2017 Edgewall Software