Skip to content

Commit f63ca45

Browse files
author
Jonathan Ross Rogers
committed
extract: Determine python-format flag explicitly
During extraction, Message instances can be created with the "python-format" flag, indicating that the message string contains Python percent-formatting placeholders. To avoid setting the flag erroneously because the string source is not Python code or otherwise is not expected to contain such placeholders, the extractor interface must be extended to allow extractor functions to indicate which flags are valid. Fixes python-babel#35
1 parent 5116c16 commit f63ca45

File tree

4 files changed

+35
-22
lines changed

4 files changed

+35
-22
lines changed

babel/messages/catalog.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,7 @@ def __init__(self, id, string=u'', locations=(), flags=(), auto_comments=(),
9999
self.string = string #: The message translation
100100
self.locations = list(distinct(locations))
101101
self.flags = set(flags)
102-
if id and self.python_format:
103-
self.flags.add('python-format')
104-
else:
105-
self.flags.discard('python-format')
102+
106103
self.auto_comments = list(distinct(auto_comments))
107104
self.user_comments = list(distinct(user_comments))
108105
if isinstance(previous_id, string_types):
@@ -112,6 +109,13 @@ def __init__(self, id, string=u'', locations=(), flags=(), auto_comments=(),
112109
self.lineno = lineno
113110
self.context = context
114111

112+
def determine_python_format(self):
113+
"""Sets python-format flag if message contains a format string"""
114+
if self.id and self.python_format:
115+
self.flags.add('python-format')
116+
else:
117+
self.flags.discard('python-format')
118+
115119
def __repr__(self):
116120
return '<%s %r (flags: %r)>' % (type(self).__name__, self.id,
117121
list(self.flags))

babel/messages/extract.py

+20-12
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def extract_from_dir(dirname=None, method_map=DEFAULT_MAPPING,
6565
"""Extract messages from any source files found in the given directory.
6666
6767
This function generates tuples of the form ``(filename, lineno, message,
68-
comments, context)``.
68+
comments, context, flags)``.
6969
7070
Which extraction method is used per file is determined by the `method_map`
7171
parameter, which maps extended glob patterns to extraction method names.
@@ -154,23 +154,23 @@ def extract_from_dir(dirname=None, method_map=DEFAULT_MAPPING,
154154
options = odict
155155
if callback:
156156
callback(filename, method, options)
157-
for lineno, message, comments, context in \
157+
for lineno, message, comments, context, flags in \
158158
extract_from_file(method, filepath,
159159
keywords=keywords,
160160
comment_tags=comment_tags,
161161
options=options,
162162
strip_comment_tags=
163163
strip_comment_tags):
164-
yield filename, lineno, message, comments, context
164+
yield filename, lineno, message, comments, context, flags
165165
break
166166

167167

168168
def extract_from_file(method, filename, keywords=DEFAULT_KEYWORDS,
169169
comment_tags=(), options=None, strip_comment_tags=False):
170170
"""Extract messages from a specific file.
171171
172-
This function returns a list of tuples of the form ``(lineno, funcname,
173-
message)``.
172+
This function returns a list of tuples of the form
173+
``(lineno, messages, comments, context, flags)``.
174174
175175
:param filename: the path to the file to extract messages from
176176
:param method: a string specifying the extraction method (.e.g. "python")
@@ -197,7 +197,8 @@ def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(),
197197
"""Extract messages from the given file-like object using the specified
198198
extraction method.
199199
200-
This function returns tuples of the form ``(lineno, message, comments)``.
200+
This generator function yields tuples of the form
201+
``(lineno, messages, comments, context, flags)``.
201202
202203
The implementation dispatches the actual extraction to plugins, based on the
203204
value of the ``method`` parameter.
@@ -210,7 +211,7 @@ def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(),
210211
>>> from StringIO import StringIO
211212
>>> for message in extract('python', StringIO(source)):
212213
... print message
213-
(3, u'Hello, world!', [], None)
214+
(3, u'Hello, world!', [], None, ())
214215
215216
:param method: a string specifying the extraction method (.e.g. "python");
216217
if this is a simple name, the extraction function will be
@@ -261,10 +262,17 @@ def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(),
261262
if func is None:
262263
raise ValueError('Unknown extraction method %r' % method)
263264

264-
results = func(fileobj, keywords.keys(), comment_tags,
265-
options=options or {})
265+
for result in func(fileobj, keywords.keys(), comment_tags,
266+
options=options or {}):
267+
flags = ()
268+
if len(result) == 4:
269+
lineno, funcname, messages, comments = result
270+
elif len(result) == 5:
271+
lineno, funcname, messages, comments, flags = result
272+
else:
273+
raise ValueError(
274+
'Extraction function must yield tuples with 4 or 5 values')
266275

267-
for lineno, funcname, messages, comments in results:
268276
if funcname:
269277
spec = keywords[funcname] or (1,)
270278
else:
@@ -315,7 +323,7 @@ def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(),
315323

316324
if strip_comment_tags:
317325
_strip_comment_tags(comments, comment_tags)
318-
yield lineno, messages, comments, context
326+
yield lineno, messages, comments, context, flags
319327

320328

321329
def extract_nothing(fileobj, keywords, comment_tags, options):
@@ -408,7 +416,7 @@ def extract_python(fileobj, keywords, comment_tags, options):
408416
translator_comments = []
409417

410418
yield (message_lineno, funcname, messages,
411-
[comment[1] for comment in translator_comments])
419+
[comment[1] for comment in translator_comments], ())
412420

413421
funcname = lineno = message_lineno = None
414422
call_stack = -1

babel/messages/frontend.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,10 @@ def callback(filename, method, options):
302302
callback=callback,
303303
strip_comment_tags=
304304
self.strip_comments)
305-
for filename, lineno, message, comments, context in extracted:
305+
for filename, lineno, message, comments, context, flags \
306+
in extracted:
306307
filepath = os.path.normpath(os.path.join(dirname, filename))
307-
catalog.add(message, None, [(filepath, lineno)],
308+
catalog.add(message, None, [(filepath, lineno)], flags=flags,
308309
auto_comments=comments, context=context)
309310

310311
log.info('writing PO template file to %s' % self.output_file)
@@ -916,9 +917,9 @@ def callback(filename, method, options):
916917
callback=callback,
917918
strip_comment_tags=
918919
options.strip_comment_tags)
919-
for filename, lineno, message, comments, context in extracted:
920+
for filename, lineno, message, comments, context, flags in extracted:
920921
filepath = os.path.normpath(os.path.join(dirname, filename))
921-
catalog.add(message, None, [(filepath, lineno)],
922+
catalog.add(message, None, [(filepath, lineno)], flags=flags,
922923
auto_comments=comments, context=context)
923924

924925
catalog_charset = catalog.charset

babel/messages/pofile.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,9 @@ def write_po(fileobj, catalog, width=76, no_location=False, omit_header=False,
341341
message catalog to the provided file-like object.
342342
343343
>>> catalog = Catalog()
344-
>>> catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
344+
>>> message = catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
345345
... flags=('fuzzy',))
346-
<Message...>
346+
>>> message.determine_python_format()
347347
>>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)])
348348
<Message...>
349349
>>> from io import BytesIO

0 commit comments

Comments
 (0)