summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeungsub Lee <sub@subl.ee>2016-07-01 19:50:34 +0900
committerHeungsub Lee <sub@subl.ee>2016-07-09 18:36:30 +0900
commit60d13b090a3a6a6989e520e08bcf86fb70073c72 (patch)
tree080209038b848c90f19460524ece791a3a947193
parent5fe694b57ef730c4154f77a78c5dbe819c6298a5 (diff)
downloadbabel-60d13b090a3a6a6989e520e08bcf86fb70073c72.tar.gz
Fix #426
Parse compiler flags based on __future__ imports in Python codes. Evaluate a string literal with the parsed compiler flags.
-rw-r--r--babel/messages/extract.py8
-rw-r--r--babel/util.py23
-rw-r--r--tests/messages/test_extract.py10
3 files changed, 38 insertions, 3 deletions
diff --git a/babel/messages/extract.py b/babel/messages/extract.py
index 4dc56a4..db17848 100644
--- a/babel/messages/extract.py
+++ b/babel/messages/extract.py
@@ -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)
diff --git a/babel/util.py b/babel/util.py
index aeb9a5f..996f902 100644
--- a/babel/util.py
+++ b/babel/util.py
@@ -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.
diff --git a/tests/messages/test_extract.py b/tests/messages/test_extract.py
index 9d78d92..22ea1cd 100644
--- a/tests/messages/test_extract.py
+++ b/tests/messages/test_extract.py
@@ -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'