]> git.ipfire.org Git - thirdparty/babel.git/commitdiff
Fix #426 427/head
authorHeungsub Lee <sub@subl.ee>
Fri, 1 Jul 2016 10:50:34 +0000 (19:50 +0900)
committerHeungsub Lee <sub@subl.ee>
Sat, 9 Jul 2016 09:36:30 +0000 (18:36 +0900)
Parse compiler flags based on __future__ imports in Python codes.
Evaluate a string literal with the parsed compiler flags.

babel/messages/extract.py
babel/util.py
tests/messages/test_extract.py

index 4dc56a4544e0ea8a44bb0dc757136001b826b516..db1784896e9a3258696b69f10f49324b0f98ebb5 100644 (file)
@@ -22,7 +22,7 @@ from os.path import relpath
 import sys
 from tokenize import generate_tokens, COMMENT, NAME, OP, STRING
 
-from babel.util import parse_encoding, pathmatch
+from babel.util import parse_encoding, parse_future_flags, pathmatch
 from babel._compat import PY2, text_type
 from textwrap import dedent
 
@@ -399,6 +399,7 @@ def extract_python(fileobj, keywords, comment_tags, options):
     comment_tag = None
 
     encoding = parse_encoding(fileobj) or options.get('encoding', 'UTF-8')
+    future_flags = parse_future_flags(fileobj, encoding)
 
     if PY2:
         next_line = fileobj.readline
@@ -470,8 +471,9 @@ def extract_python(fileobj, keywords, comment_tags, options):
                 # encoding
                 # https://sourceforge.net/tracker/?func=detail&atid=355470&
                 # aid=617979&group_id=5470
-                value = eval('# coding=%s\n%s' % (str(encoding), value),
-                             {'__builtins__': {}}, {})
+                code = compile('# coding=%s\n%s' % (str(encoding), value),
+                               '<string>', 'eval', future_flags)
+                value = eval(code, {'__builtins__': {}}, {})
                 if PY2 and not isinstance(value, text_type):
                     value = value.decode(encoding)
                 buf.append(value)
index aeb9a5fab0293a17fe3ffcc8b33da83a152c12be..996f902b2e3d5f3141d9cf75469109a501411d55 100644 (file)
@@ -95,6 +95,29 @@ def parse_encoding(fp):
         fp.seek(pos)
 
 
+PYTHON_FUTURE_IMPORT_re = re.compile(
+    r'from\s+__future__\s+import\s+\(*(.+)\)*')
+
+
+def parse_future_flags(fp, encoding='latin-1'):
+    """Parse the compiler flags by :mod:`__future__` from the given Python
+    code.
+    """
+    import __future__
+    pos = fp.tell()
+    fp.seek(0)
+    flags = 0
+    try:
+        body = fp.read().decode(encoding)
+        for m in PYTHON_FUTURE_IMPORT_re.finditer(body):
+            names = [x.strip() for x in m.group(1).split(',')]
+            for name in names:
+                flags |= getattr(__future__, name).compiler_flag
+    finally:
+        fp.seek(pos)
+    return flags
+
+
 def pathmatch(pattern, filename):
     """Extended pathname pattern matching.
 
index 9d78d923b10cb749c1e1ab5da8c3c750420d185a..22ea1cd6c64ef37fea76597e6ca1c7813c7b758d 100644 (file)
@@ -498,3 +498,13 @@ msg = _('')
             return [(1, None, (), ())]
         for x in extract.extract(arbitrary_extractor, BytesIO(b"")):
             assert x[0] == 1
+
+    def test_future(self):
+        buf = BytesIO(br"""
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+nbsp = _('\xa0')
+""")
+        messages = list(extract.extract('python', buf,
+                                        extract.DEFAULT_KEYWORDS, [], {}))
+        assert messages[0][1] == u'\xa0'