Georg Brandl avatar Georg Brandl committed 46be1ca

Run 2to3 on config files which contain Python 2.x unicode literals.

Comments (0)

Files changed (3)

 import re
 import sys
 from os import path
+try:
+    from distutils.util import run_2to3
+except ImportError:
+    run_2to3 = None
 
 from sphinx.errors import ConfigError
 from sphinx.util.osutil import make_filename
-from sphinx.util.pycompat import bytes, b
+from sphinx.util.pycompat import bytes, b, should_run_2to3, run_2to3
 
 nonascii_re = re.compile(b(r'[\x80-\xff]'))
 
+CONFIG_SYNTAX_ERROR = "There is a syntax error in your configuration file: %s"
+if sys.version_info >= (3, 0):
+    CONFIG_SYNTAX_ERROR += "\nDid you change the syntax from 2.x to 3.x?"
 
 class Config(object):
     """Configuration file abstraction."""
             try:
                 try:
                     os.chdir(dirname)
-                    f = open(config_file, 'rb')
-                    try:
-                        code = compile(f.read(), config_file, 'exec')
-                    finally:
-                        f.close()
-                    exec code in config
+                    if should_run_2to3(config_file):
+                        code = run_2to3(config_file)
+                    else:
+                        f = open(config_file, 'rb')
+                        try:
+                            code = f.read()
+                        finally:
+                            f.close()
+                    exec compile(code, config_file, 'exec') in config
                 except SyntaxError, err:
-                    raise ConfigError('There is a syntax error in your '
-                                      'configuration file: ' + str(err))
+                    raise ConfigError(CONFIG_SYNTAX_ERROR % err)
             finally:
                 os.chdir(olddir)
 

sphinx/quickstart.py

 
 PROMPT_PREFIX = '> '
 
-QUICKSTART_CONF = '''\
+if sys.version_info >= (3, 0):
+    # prevents that the file is checked for being written in Python 2.x syntax
+    QUICKSTART_CONF = '#!/usr/bin/env python3\n'
+else:
+    QUICKSTART_CONF = ''
+
+QUICKSTART_CONF += '''\
 # -*- coding: utf-8 -*-
 #
 # %(project)s documentation build configuration file, created by

sphinx/util/pycompat.py

 import sys
 import codecs
 import encodings
+import re
 
 try:
     from types import ClassType
     # Python 3
     class_types = (type,)
 
-try:
-    base_exception = BaseException
-except NameError:
-    base_exception = Exception
-
 
 # the ubiquitous "bytes" helper function
 if sys.version_info >= (3, 0):
     b = str
 
 
+encoding_re = re.compile(b(r'coding[=:]\s*([-\w.]+)'))
+unicode_literal_re = re.compile(ur"""
+(?:
+    "(?:[^"\]]*(?:\\.[^"\\]*)*)"|
+    '(?:[^'\]]*(?:\\.[^'\\]*)*)'
+)
+""", re.VERBOSE)
+
+
+try:
+    from lib2to3.refactor import RefactoringTool, get_fixers_from_package
+except ImportError:
+    _run_2to3 = None
+    def should_run_2to3(filepath):
+        return False
+else:
+    def should_run_2to3(filepath):
+        # th default source code encoding for python 2.x
+        encoding = 'ascii'
+        # only the first match of the encoding cookie counts
+        encoding_set = False
+        f = open(filepath, 'rb')
+        try:
+            for i, line in enumerate(f):
+                if line.startswith(b('#')):
+                    if i == 0 and b('python3') in line:
+                        return False
+                    if not encoding_set:
+                        encoding_match = encoding_re.match(line)
+                        if encoding_match:
+                            encoding = encoding_match.group(1)
+                            encodin_set = True
+                elif line.strip():
+                    try:
+                        line = line.decode(encoding)
+                    except UnicodeDecodeError:
+                        # I'm not sure this will work but let's try it anyway
+                        return True
+                    if unicode_literal_re.search(line) is not None:
+                        return True
+        finally:
+            f.close()
+        return False
+
+    def run_2to3(filepath):
+        sys.path.append('..')
+        fixers = get_fixers_from_package('lib2to3.fixes')
+        fixers.extend(get_fixers_from_package('custom_fixers'))
+        refactoring_tool = RefactoringTool(fixers)
+        source = refactoring_tool._read_python_source(filepath)[0]
+        ast = refactoring_tool.refactor_string(source, 'conf.py')
+        return unicode(ast)
+
+
+try:
+    base_exception = BaseException
+except NameError:
+    base_exception = Exception
+
+
 try:
     next = next
 except NameError:
     def next(iterator):
         return iterator.next()
 
+
 try:
     bytes = bytes
 except NameError:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.