diff options
author | shimizukawa <shimizukawa@gmail.com> | 2014-08-30 20:20:19 +0900 |
---|---|---|
committer | shimizukawa <shimizukawa@gmail.com> | 2014-08-30 20:20:19 +0900 |
commit | ada1c91615d995218ec1299fe258bd2d770697ae (patch) | |
tree | 01e6f4dcfaf49ea1b5a278dce65f2fb43d880847 /sphinx/util | |
parent | 13e3eaa7acb297c114aa8f9cb72e338a818c96ff (diff) | |
parent | 451d4aa0a8ae7123e46a899187a3fd8c224d32b7 (diff) | |
download | sphinx-ada1c91615d995218ec1299fe258bd2d770697ae.tar.gz |
Merge with stable
Diffstat (limited to 'sphinx/util')
-rw-r--r-- | sphinx/util/__init__.py | 44 | ||||
-rw-r--r-- | sphinx/util/console.py | 11 | ||||
-rw-r--r-- | sphinx/util/docfields.py | 12 | ||||
-rw-r--r-- | sphinx/util/docstrings.py | 4 | ||||
-rw-r--r-- | sphinx/util/i18n.py | 89 | ||||
-rw-r--r-- | sphinx/util/inspect.py | 25 | ||||
-rw-r--r-- | sphinx/util/jsdump.py | 10 | ||||
-rw-r--r-- | sphinx/util/jsonimpl.py | 30 | ||||
-rw-r--r-- | sphinx/util/matching.py | 2 | ||||
-rw-r--r-- | sphinx/util/nodes.py | 28 | ||||
-rw-r--r-- | sphinx/util/osutil.py | 30 | ||||
-rw-r--r-- | sphinx/util/png.py | 5 | ||||
-rw-r--r-- | sphinx/util/pycompat.py | 231 | ||||
-rw-r--r-- | sphinx/util/tags.py | 4 | ||||
-rw-r--r-- | sphinx/util/texescape.py | 168 |
15 files changed, 301 insertions, 392 deletions
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index cf3ae327..0f11a4c5 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -12,7 +12,6 @@ import os import re import sys -import shutil import fnmatch import tempfile import posixpath @@ -22,6 +21,8 @@ from os import path from codecs import open, BOM_UTF8 from collections import deque +from six import iteritems, text_type, binary_type +from six.moves import range import docutils from docutils.utils import relative_path @@ -29,7 +30,6 @@ import jinja2 import sphinx from sphinx.errors import PycodeError -from sphinx.util.pycompat import bytes # import other utilities; partly for backwards compatibility, so don't # prune unused ones indiscriminately @@ -54,7 +54,7 @@ def docname_join(basedocname, docname): def path_stabilize(filepath): "normalize path separater and unicode string" newpath = filepath.replace(os.path.sep, SEP) - if isinstance(newpath, unicode): + if isinstance(newpath, text_type): newpath = unicodedata.normalize('NFC', newpath) return newpath @@ -122,7 +122,7 @@ class FilenameUniqDict(dict): return uniquename def purge_doc(self, docname): - for filename, (docs, unique) in self.items(): + for filename, (docs, unique) in list(self.items()): docs.discard(docname) if not docs: del self[filename] @@ -190,7 +190,7 @@ def save_traceback(app): docutils.__version__, docutils.__version_details__, jinja2.__version__)).encode('utf-8')) if app is not None: - for extname, extmod in app._extensions.iteritems(): + for extname, extmod in iteritems(app._extensions): os.write(fd, ('# %s from %s\n' % ( extname, getattr(extmod, '__file__', 'unknown')) ).encode('utf-8')) @@ -208,7 +208,7 @@ def get_module_source(modname): if modname not in sys.modules: try: __import__(modname) - except Exception, err: + except Exception as err: raise PycodeError('error importing %r' % modname, err) mod = sys.modules[modname] filename = getattr(mod, '__file__', None) @@ -216,12 +216,12 @@ def get_module_source(modname): if loader and getattr(loader, 'get_filename', None): try: filename = loader.get_filename(modname) - except Exception, err: + except Exception as err: raise PycodeError('error getting filename for %r' % filename, err) if filename is None and loader: try: return 'string', loader.get_source(modname) - except Exception, err: + except Exception as err: raise PycodeError('error getting source for %r' % modname, err) if filename is None: raise PycodeError('no source found for module %r' % modname) @@ -238,6 +238,20 @@ def get_module_source(modname): return 'file', filename +def get_full_modname(modname, attribute): + __import__(modname) + module = sys.modules[modname] + + # Allow an attribute to have multiple parts and incidentially allow + # repeated .s in the attribute. + value = module + for attr in attribute.split('.'): + if attr: + value = getattr(value, attr) + + return getattr(value, '__module__', None) + + # a regex to recognize coding cookies _coding_re = re.compile(r'coding[:=]\s*([-\w.]+)') @@ -328,7 +342,7 @@ def parselinenos(spec, total): else: start = (begend[0] == '') and 0 or int(begend[0])-1 end = (begend[1] == '') and total or int(begend[1]) - items.extend(xrange(start, end)) + items.extend(range(start, end)) except Exception: raise ValueError('invalid line number spec: %r' % spec) return items @@ -336,7 +350,7 @@ def parselinenos(spec, total): def force_decode(string, encoding): """Forcibly get a unicode string out of a bytestring.""" - if isinstance(string, bytes): + if isinstance(string, binary_type): try: if encoding: string = string.decode(encoding) @@ -368,7 +382,7 @@ def rpartition(s, t): def split_into(n, type, value): """Split an index entry into a given number of parts at semicolons.""" - parts = map(lambda x: x.strip(), value.split(';', n-1)) + parts = [x.strip() for x in value.split(';', n-1)] if sum(1 for part in parts if part) < n: raise ValueError('invalid %s index entry %r' % (type, value)) return parts @@ -420,11 +434,13 @@ class PeekableIterator(object): def __iter__(self): return self - def next(self): + def __next__(self): """Return the next item from the iterator.""" if self.remaining: return self.remaining.popleft() - return self._iterator.next() + return next(self._iterator) + + next = __next__ # Python 2 compatibility def push(self, item): """Push the `item` on the internal stack, it will be returned on the @@ -434,6 +450,6 @@ class PeekableIterator(object): def peek(self): """Return the next item without changing the state of the iterator.""" - item = self.next() + item = next(self) self.push(item) return item diff --git a/sphinx/util/console.py b/sphinx/util/console.py index c2330102..2acc8ead 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -13,6 +13,12 @@ import os import sys import re +try: + # check if colorama is installed to support color on Windows + import colorama +except ImportError: + colorama = None + _ansi_re = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm') codes = {} @@ -42,6 +48,9 @@ def term_width_line(text): return text.ljust(_tw + len(text) - len(_ansi_re.sub('', text))) + '\r' def color_terminal(): + if sys.platform == 'win32' and colorama is not None: + colorama.init() + return True if not hasattr(sys.stdout, 'isatty'): return False if not sys.stdout.isatty(): @@ -55,6 +64,8 @@ def color_terminal(): def nocolor(): + if sys.platform == 'win32' and colorama is not None: + colorama.deinit() codes.clear() def coloron(): diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index d6f46ab9..abb73288 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -99,7 +99,8 @@ class GroupedField(Field): return Field.make_field(self, types, domain, items[0]) for fieldarg, content in items: par = nodes.paragraph() - par += self.make_xref(self.rolename, domain, fieldarg, nodes.strong) + par += self.make_xref(self.rolename, domain, fieldarg, + addnodes.literal_strong) par += nodes.Text(' -- ') par += content listnode += nodes.list_item('', par) @@ -137,7 +138,8 @@ class TypedField(GroupedField): def make_field(self, types, domain, items): def handle_item(fieldarg, content): par = nodes.paragraph() - par += self.make_xref(self.rolename, domain, fieldarg, nodes.strong) + par += self.make_xref(self.rolename, domain, fieldarg, + addnodes.literal_strong) if fieldarg in types: par += nodes.Text(' (') # NOTE: using .pop() here to prevent a single type node to be @@ -238,10 +240,8 @@ class DocFieldTransformer(object): if is_typefield: # filter out only inline nodes; others will result in invalid # markup being written out - content = filter( - lambda n: isinstance(n, nodes.Inline) or - isinstance(n, nodes.Text), - content) + content = [n for n in content if isinstance(n, nodes.Inline) or + isinstance(n, nodes.Text)] if content: types.setdefault(typename, {})[fieldarg] = content continue diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index 71381305..6b66eee9 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -23,7 +23,7 @@ def prepare_docstring(s, ignore=1): """ lines = s.expandtabs().splitlines() # Find minimum indentation of any non-blank lines after ignored lines. - margin = sys.maxint + margin = sys.maxsize for line in lines[ignore:]: content = len(line.lstrip()) if content: @@ -33,7 +33,7 @@ def prepare_docstring(s, ignore=1): for i in range(ignore): if i < len(lines): lines[i] = lines[i].lstrip() - if margin < sys.maxint: + if margin < sys.maxsize: for i in range(ignore, len(lines)): lines[i] = lines[i][margin:] # Remove any leading blank lines. while lines and not lines[0]: diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py new file mode 100644 index 00000000..8e61c12b --- /dev/null +++ b/sphinx/util/i18n.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*-
+"""
+ sphinx.util.i18n
+ ~~~~~~~~~~~~~~~~
+
+ Builder superclass for all builders.
+
+ :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from os import path
+from collections import namedtuple
+
+from babel.messages.pofile import read_po
+from babel.messages.mofile import write_mo
+
+from sphinx.util.osutil import walk
+
+
+LocaleFileInfoBase = namedtuple('CatalogInfo', 'base_dir,domain')
+
+
+class CatalogInfo(LocaleFileInfoBase):
+
+ @property
+ def po_file(self):
+ return self.domain + '.po'
+
+ @property
+ def mo_file(self):
+ return self.domain + '.mo'
+
+ @property
+ def po_path(self):
+ return path.join(self.base_dir, self.po_file)
+
+ @property
+ def mo_path(self):
+ return path.join(self.base_dir, self.mo_file)
+
+ def is_outdated(self):
+ return (
+ not path.exists(self.mo_path) or
+ path.getmtime(self.mo_path) < path.getmtime(self.po_path))
+
+ def write_mo(self, locale):
+ with open(self.po_path, 'rt') as po:
+ with open(self.mo_path, 'wb') as mo:
+ write_mo(mo, read_po(po, locale))
+
+
+def get_catalogs(locale_dirs, locale, gettext_compact=False, force_all=False):
+ """
+ :param list locale_dirs:
+ list of path as `['locale_dir1', 'locale_dir2', ...]` to find
+ translation catalogs. Each path contains a structure such as
+ `<locale>/LC_MESSAGES/domain.po`.
+ :param str locale: a language as `'en'`
+ :param boolean gettext_compact:
+ * False: keep domains directory structure (default).
+ * True: domains in the sub directory will be merged into 1 file.
+ :param boolean force_all:
+ Set True if you want to get all catalogs rather than updated catalogs.
+ default is False.
+ :return: [CatalogInfo(), ...]
+ """
+ if not locale:
+ return [] # locale is not specified
+
+ catalogs = set()
+ for locale_dir in locale_dirs:
+ base_dir = path.join(locale_dir, locale, 'LC_MESSAGES')
+
+ if not path.exists(base_dir):
+ continue # locale path is not found
+
+ for dirpath, dirnames, filenames in walk(base_dir, followlinks=True):
+ filenames = [f for f in filenames if f.endswith('.po')]
+ for filename in filenames:
+ base = path.splitext(filename)[0]
+ domain = path.relpath(path.join(dirpath, base), base_dir)
+ if gettext_compact and path.sep in domain:
+ domain = path.split(domain)[0]
+ cat = CatalogInfo(base_dir, domain)
+ if force_all or cat.is_outdated():
+ catalogs.add(cat)
+
+ return catalogs
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index cdbfea76..f82f1f45 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -9,17 +9,17 @@ :license: BSD, see LICENSE for details. """ -import sys - # this imports the standard library inspect module without resorting to # relatively import this module inspect = __import__('inspect') +from six import PY3, binary_type +from six.moves import builtins + from sphinx.util import force_decode -from sphinx.util.pycompat import bytes, builtins -if sys.version_info >= (3, 0): +if PY3: from functools import partial def getargspec(func): """Like inspect.getargspec but supports functools.partial as well.""" @@ -55,12 +55,12 @@ if sys.version_info >= (3, 0): raise TypeError('%r is not a Python function' % func) return inspect.getfullargspec(func) -elif sys.version_info >= (2, 5): +else: # 2.6, 2.7 from functools import partial def getargspec(func): """Like inspect.getargspec but supports functools.partial as well.""" if inspect.ismethod(func): - func = func.im_func + func = func.__func__ parts = 0, () if type(func) is partial: keywords = func.keywords @@ -70,8 +70,8 @@ elif sys.version_info >= (2, 5): func = func.func if not inspect.isfunction(func): raise TypeError('%r is not a Python function' % func) - args, varargs, varkw = inspect.getargs(func.func_code) - func_defaults = func.func_defaults + args, varargs, varkw = inspect.getargs(func.__code__) + func_defaults = func.__defaults__ if func_defaults is None: func_defaults = [] else: @@ -86,12 +86,7 @@ elif sys.version_info >= (2, 5): del func_defaults[i] except IndexError: pass - if sys.version_info >= (2, 6): - return inspect.ArgSpec(args, varargs, varkw, func_defaults) - else: - return (args, varargs, varkw, func_defaults) -else: - getargspec = inspect.getargspec + return inspect.ArgSpec(args, varargs, varkw, func_defaults) def isdescriptor(x): @@ -134,7 +129,7 @@ def safe_repr(object): s = repr(object) except Exception: raise ValueError - if isinstance(s, bytes): + if isinstance(s, binary_type): return force_decode(s, None).replace('\n', ' ') return s.replace('\n', ' ') diff --git a/sphinx/util/jsdump.py b/sphinx/util/jsdump.py index 85845a72..ede4ae7d 100644 --- a/sphinx/util/jsdump.py +++ b/sphinx/util/jsdump.py @@ -12,6 +12,8 @@ import re +from six import iteritems, integer_types, string_types + from sphinx.util.pycompat import u _str_re = re.compile(r'"(\\\\|\\"|[^"])*"') @@ -74,7 +76,7 @@ double in super""".split()) def dumps(obj, key=False): if key: - if not isinstance(obj, basestring): + if not isinstance(obj, string_types): obj = str(obj) if _nameonly_re.match(obj) and obj not in reswords: return obj # return it as a bare word @@ -84,16 +86,16 @@ def dumps(obj, key=False): return 'null' elif obj is True or obj is False: return obj and 'true' or 'false' - elif isinstance(obj, (int, long, float)): + elif isinstance(obj, integer_types + (float,)): return str(obj) elif isinstance(obj, dict): return '{%s}' % ','.join('%s:%s' % ( dumps(key, True), dumps(value) - ) for key, value in obj.iteritems()) + ) for key, value in iteritems(obj)) elif isinstance(obj, (tuple, list, set)): return '[%s]' % ','.join(dumps(x) for x in obj) - elif isinstance(obj, basestring): + elif isinstance(obj, string_types): return encode_string(obj) raise TypeError(type(obj)) diff --git a/sphinx/util/jsonimpl.py b/sphinx/util/jsonimpl.py index 8ccbf0cc..ac5c54ae 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -9,28 +9,18 @@ :license: BSD, see LICENSE for details. """ -import UserString - -try: - import json - # json-py's json module has no JSONEncoder; this will raise AttributeError - # if json-py is imported instead of the built-in json module - JSONEncoder = json.JSONEncoder -except (ImportError, AttributeError): - try: - import simplejson as json - JSONEncoder = json.JSONEncoder - except ImportError: - json = None - JSONEncoder = object - - -class SphinxJSONEncoder(JSONEncoder): +import json + +from six import text_type +from six.moves import UserString + + +class SphinxJSONEncoder(json.JSONEncoder): """JSONEncoder subclass that forces translation proxies.""" def default(self, obj): - if isinstance(obj, UserString.UserString): - return unicode(obj) - return JSONEncoder.default(self, obj) + if isinstance(obj, UserString): + return text_type(obj) + return json.JSONEncoder.default(self, obj) def dump(obj, fp, *args, **kwds): diff --git a/sphinx/util/matching.py b/sphinx/util/matching.py index 51b2056d..da8cfa32 100644 --- a/sphinx/util/matching.py +++ b/sphinx/util/matching.py @@ -77,4 +77,4 @@ def patfilter(names, pat): if pat not in _pat_cache: _pat_cache[pat] = re.compile(_translate_pattern(pat)) match = _pat_cache[pat].match - return filter(match, names) + return list(filter(match, names)) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index eb3b86b5..681046f2 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -10,8 +10,8 @@ """ import re -import sys +from six import text_type from docutils import nodes from sphinx import addnodes @@ -201,7 +201,7 @@ def inline_all_toctrees(builder, docnameset, docname, tree, colorfunc): tree = tree.deepcopy() for toctreenode in tree.traverse(addnodes.toctree): newnodes = [] - includefiles = map(unicode, toctreenode['includefiles']) + includefiles = map(text_type, toctreenode['includefiles']) for includefile in includefiles: try: builder.info(colorfunc(includefile) + " ", nonl=1) @@ -215,6 +215,9 @@ def inline_all_toctrees(builder, docnameset, docname, tree, colorfunc): else: sof = addnodes.start_of_file(docname=includefile) sof.children = subtree.children + for sectionnode in sof.traverse(nodes.section): + if 'docname' not in sectionnode: + sectionnode['docname'] = includefile newnodes.append(sof) toctreenode.parent.replace(toctreenode, newnodes) return tree @@ -239,12 +242,7 @@ def set_source_info(directive, node): directive.state_machine.get_source_and_line(directive.lineno) def set_role_source_info(inliner, lineno, node): - try: - node.source, node.line = \ - inliner.reporter.locator(lineno) - except AttributeError: - # docutils 0.9+ - node.source, node.line = inliner.reporter.get_source_and_line(lineno) + node.source, node.line = inliner.reporter.get_source_and_line(lineno) # monkey-patch Element.copy to copy the rawsource @@ -252,17 +250,3 @@ def _new_copy(self): return self.__class__(self.rawsource, **self.attributes) nodes.Element.copy = _new_copy - -# monkey-patch Element.__repr__ to return str if it returns unicode. -# Was fixed in docutils since 0.10. See sf.net/p/docutils/bugs/218/. - -if sys.version_info < (3,): - _element_repr_orig = nodes.Element.__repr__ - - def _new_repr(self): - s = _element_repr_orig(self) - if isinstance(s, unicode): - return s.encode('utf-8') - return s - - nodes.Element.__repr__ = _new_repr diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index d7b292b3..9b5f58b7 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -8,6 +8,7 @@ :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from __future__ import print_function import os import re @@ -19,6 +20,8 @@ import shutil import gettext from os import path +from six import PY2, text_type + # Errnos that we need. EEXIST = getattr(errno, 'EEXIST', 0) ENOENT = getattr(errno, 'ENOENT', 0) @@ -63,12 +66,15 @@ def ensuredir(path): """Ensure that a path exists.""" try: os.makedirs(path) - except OSError, err: + except OSError as err: # 0 for Jython/Win32 if err.errno not in [0, EEXIST]: raise +# This function is same as os.walk of Python2.6, 2.7, 3.2, 3.3 except a +# customization that check UnicodeError. +# The customization obstacle to replace the function with the os.walk. def walk(top, topdown=True, followlinks=False): """Backport of os.walk from 2.6, where the *followlinks* argument was added. @@ -80,9 +86,9 @@ def walk(top, topdown=True, followlinks=False): try: fullpath = path.join(top, name) except UnicodeError: - print >>sys.stderr, ( - '%s:: ERROR: non-ASCII filename not supported on this ' - 'filesystem encoding %r, skipped.' % (name, fs_encoding)) + print('%s:: ERROR: non-ASCII filename not supported on this ' + 'filesystem encoding %r, skipped.' % (name, fs_encoding), + file=sys.stderr) continue if path.isdir(fullpath): dirs.append(name) @@ -143,21 +149,20 @@ no_fn_re = re.compile(r'[^a-zA-Z0-9_-]') def make_filename(string): return no_fn_re.sub('', string) or 'sphinx' -if sys.version_info < (3, 0): +if PY2: # strftime for unicode strings def ustrftime(format, *args): # if a locale is set, the time strings are encoded in the encoding # given by LC_TIME; if that is available, use it enc = locale.getlocale(locale.LC_TIME)[1] or 'utf-8' - return time.strftime(unicode(format).encode(enc), *args).decode(enc) + return time.strftime(text_type(format).encode(enc), *args).decode(enc) else: ustrftime = time.strftime def safe_relpath(path, start=None): - from sphinx.util.pycompat import relpath try: - return relpath(path, start) + return os.path.relpath(path, start) except ValueError: return path @@ -171,26 +176,19 @@ def find_catalog(docname, compaction): def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction): - from sphinx.util.pycompat import relpath if not(lang and locale_dirs): return [] domain = find_catalog(docname, compaction) files = [gettext.find(domain, path.join(srcdir, dir_), [lang]) for dir_ in locale_dirs] - files = [relpath(f, srcdir) for f in files if f] + files = [path.relpath(f, srcdir) for f in files if f] return files fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() -if sys.version_info < (3, 0): - bytes = str -else: - bytes = bytes - - def abspath(pathdir): pathdir = path.abspath(pathdir) if isinstance(pathdir, bytes): diff --git a/sphinx/util/png.py b/sphinx/util/png.py index 65fc4d8d..397adb24 100644 --- a/sphinx/util/png.py +++ b/sphinx/util/png.py @@ -12,14 +12,13 @@ import struct import binascii -from sphinx.util.pycompat import b LEN_IEND = 12 LEN_DEPTH = 22 DEPTH_CHUNK_LEN = struct.pack('!i', 10) -DEPTH_CHUNK_START = b('tEXtDepth\x00') -IEND_CHUNK = b('\x00\x00\x00\x00IEND\xAE\x42\x60\x82') +DEPTH_CHUNK_START = b'tEXtDepth\x00' +IEND_CHUNK = b'\x00\x00\x00\x00IEND\xAE\x42\x60\x82' def read_png_depth(filename): diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 17f8871e..5031dd9b 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -11,22 +11,17 @@ import sys import codecs -import encodings + +from six import PY3, text_type, exec_ # ------------------------------------------------------------------------------ # Python 2/3 compatibility -if sys.version_info >= (3, 0): +if PY3: # Python 3 - class_types = (type,) - # the ubiquitous "bytes" helper functions - def b(s): - return s.encode('utf-8') - bytes = bytes # prefix for Unicode strings u = '' - # StringIO/BytesIO classes - from io import StringIO, BytesIO, TextIOWrapper + from io import TextIOWrapper # safely encode a string for printing to the terminal def terminal_safe(s): return s.encode('ascii', 'backslashreplace').decode('ascii') @@ -42,24 +37,24 @@ if sys.version_info >= (3, 0): source = refactoring_tool._read_python_source(filepath)[0] try: tree = refactoring_tool.refactor_string(source, 'conf.py') - except ParseError, err: + except ParseError as err: # do not propagate lib2to3 exceptions lineno, offset = err.context[1] # try to match ParseError details with SyntaxError details raise SyntaxError(err.msg, (filepath, lineno, offset, err.value)) - return unicode(tree) - from itertools import zip_longest # Python 3 name - import builtins + return text_type(tree) + from html import escape as htmlescape # >= Python 3.2 + + class UnicodeMixin: + """Mixin class to handle defining the proper __str__/__unicode__ + methods in Python 2 or 3.""" + + def __str__(self): + return self.__unicode__() else: # Python 2 - from types import ClassType - class_types = (type, ClassType) - b = str - bytes = str u = 'u' - from StringIO import StringIO - BytesIO = StringIO # no need to refactor on 2.x versions convert_with_2to3 = None def TextIOWrapper(stream, encoding): @@ -69,11 +64,16 @@ else: return s.encode('ascii', 'backslashreplace') # some kind of default system encoding; should be used with a lenient # error handler - import locale - sys_encoding = locale.getpreferredencoding() + sys_encoding = __import__('locale').getpreferredencoding() # use Python 3 name - from itertools import izip_longest as zip_longest - import __builtin__ as builtins + from cgi import escape as htmlescape # 2.6, 2.7 + + class UnicodeMixin(object): + """Mixin class to handle defining the proper __str__/__unicode__ + methods in Python 2 or 3.""" + + def __str__(self): + return self.__unicode__().encode('utf8') def execfile_(filepath, _globals): @@ -86,9 +86,9 @@ def execfile_(filepath, _globals): finally: f.close() - # py25,py26,py31 accept only LF eol instead of CRLF - if sys.version_info[:2] in ((2, 5), (2, 6), (3, 1)): - source = source.replace(b('\r\n'), b('\n')) + # py26 accept only LF eol instead of CRLF + if sys.version_info[:2] == (2, 6): + source = source.replace(b'\r\n', b'\n') # compile to a code object, handle syntax errors filepath_enc = filepath.encode(fs_encoding) @@ -102,181 +102,4 @@ def execfile_(filepath, _globals): code = compile(source, filepath_enc, 'exec') else: raise - exec code in _globals - - -try: - from html import escape as htmlescape -except ImportError: - from cgi import escape as htmlescape - -# ------------------------------------------------------------------------------ -# Missing builtins and itertools in Python < 2.6 - -if sys.version_info >= (2, 6): - # Python >= 2.6 - next = next - - from itertools import product - try: - from itertools import zip_longest # Python 3 name - except ImportError: - from itertools import izip_longest as zip_longest - - import os - relpath = os.path.relpath - del os - - import io - open = io.open - -else: - # Python < 2.6 - from itertools import izip, repeat, chain - - # this is on Python 2, where the method is called "next" (it is refactored - # to __next__ by 2to3, but in that case never executed) - def next(iterator): - return iterator.next() - - # These replacement functions have been taken from the Python 2.6 - # itertools documentation. - def product(*args, **kwargs): - pools = map(tuple, args) * kwargs.get('repeat', 1) - result = [[]] - for pool in pools: - result = [x + [y] for x in result for y in pool] - for prod in result: - yield tuple(prod) - - def zip_longest(*args, **kwds): - # zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- - fillvalue = kwds.get('fillvalue') - def sentinel(counter = ([fillvalue]*(len(args)-1)).pop): - yield counter() # yields the fillvalue, or raises IndexError - fillers = repeat(fillvalue) - iters = [chain(it, sentinel(), fillers) for it in args] - try: - for tup in izip(*iters): - yield tup - except IndexError: - pass - - from os.path import curdir - def relpath(path, start=curdir): - """Return a relative version of a path""" - from os.path import sep, abspath, commonprefix, join, pardir - - if not path: - raise ValueError("no path specified") - - start_list = abspath(start).split(sep) - path_list = abspath(path).split(sep) - - # Work out how much of the filepath is shared by start and path. - i = len(commonprefix([start_list, path_list])) - - rel_list = [pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return start - return join(*rel_list) - del curdir - - from types import MethodType - def open(filename, mode='r', *args, **kw): - newline = kw.pop('newline', None) - mode = mode.replace('t', '') - f = codecs.open(filename, mode, *args, **kw) - if newline is not None: - f._write = f.write - def write(self, text): - text = text.replace(u'\r\n', u'\n').replace(u'\n', newline) - self._write(text) - f.write = MethodType(write, f) - return f - - -# ------------------------------------------------------------------------------ -# Missing builtins and codecs in Python < 2.5 - -if sys.version_info >= (2, 5): - # Python >= 2.5 - base_exception = BaseException - any = any - all = all - -else: - # Python 2.4 - base_exception = Exception - - def all(gen): - for i in gen: - if not i: - return False - return True - - def any(gen): - for i in gen: - if i: - return True - return False - - # Python 2.4 doesn't know the utf-8-sig encoding, so deliver it here - - def my_search_function(encoding): - norm_encoding = encodings.normalize_encoding(encoding) - if norm_encoding != 'utf_8_sig': - return None - return (encode, decode, StreamReader, StreamWriter) - - codecs.register(my_search_function) - - # begin code copied from utf_8_sig.py in Python 2.6 - - def encode(input, errors='strict'): - return (codecs.BOM_UTF8 + - codecs.utf_8_encode(input, errors)[0], len(input)) - - def decode(input, errors='strict'): - prefix = 0 - if input[:3] == codecs.BOM_UTF8: - input = input[3:] - prefix = 3 - (output, consumed) = codecs.utf_8_decode(input, errors, True) - return (output, consumed+prefix) - - class StreamWriter(codecs.StreamWriter): - def reset(self): - codecs.StreamWriter.reset(self) - try: - del self.encode - except AttributeError: - pass - - def encode(self, input, errors='strict'): - self.encode = codecs.utf_8_encode - return encode(input, errors) - - class StreamReader(codecs.StreamReader): - def reset(self): - codecs.StreamReader.reset(self) - try: - del self.decode - except AttributeError: - pass - - def decode(self, input, errors='strict'): - if len(input) < 3: - if codecs.BOM_UTF8.startswith(input): - # not enough data to decide if this is a BOM - # => try again on the next call - return (u"", 0) - elif input[:3] == codecs.BOM_UTF8: - self.decode = codecs.utf_8_decode - (output, consumed) = codecs.utf_8_decode(input[3:],errors) - return (output, consumed+3) - # (else) no BOM present - self.decode = codecs.utf_8_decode - return codecs.utf_8_decode(input, errors) - - # end code copied from utf_8_sig.py + exec_(code, _globals) diff --git a/sphinx/util/tags.py b/sphinx/util/tags.py index 2a9b2a02..d5141587 100644 --- a/sphinx/util/tags.py +++ b/sphinx/util/tags.py @@ -35,9 +35,9 @@ class BooleanParser(Parser): node = nodes.Const(None, lineno=token.lineno) else: node = nodes.Name(token.value, 'load', lineno=token.lineno) - self.stream.next() + next(self.stream) elif token.type == 'lparen': - self.stream.next() + next(self.stream) node = self.parse_expression() self.stream.expect('rparen') else: diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py index c0619f46..6fcf9ff0 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -9,93 +9,95 @@ :license: BSD, see LICENSE for details. """ +from __future__ import unicode_literals + tex_replacements = [ # map TeX special chars - (u'$', ur'\$'), - (u'%', ur'\%'), - (u'&', ur'\&'), - (u'#', ur'\#'), - (u'_', ur'\_'), - (u'{', ur'\{'), - (u'}', ur'\}'), - (u'[', ur'{[}'), - (u']', ur'{]}'), - (u'`', ur'{}`'), - (u'\\',ur'\textbackslash{}'), - (u'~', ur'\textasciitilde{}'), - (u'<', ur'\textless{}'), - (u'>', ur'\textgreater{}'), - (u'^', ur'\textasciicircum{}'), + ('$', r'\$'), + ('%', r'\%'), + ('&', r'\&'), + ('#', r'\#'), + ('_', r'\_'), + ('{', r'\{'), + ('}', r'\}'), + ('[', r'{[}'), + (']', r'{]}'), + ('`', r'{}`'), + ('\\',r'\textbackslash{}'), + ('~', r'\textasciitilde{}'), + ('<', r'\textless{}'), + ('>', r'\textgreater{}'), + ('^', r'\textasciicircum{}'), # map special Unicode characters to TeX commands - (u'¶', ur'\P{}'), - (u'§', ur'\S{}'), - (u'€', ur'\texteuro{}'), - (u'∞', ur'\(\infty\)'), - (u'±', ur'\(\pm\)'), - (u'→', ur'\(\rightarrow\)'), - (u'‣', ur'\(\rightarrow\)'), + ('¶', r'\P{}'), + ('§', r'\S{}'), + ('€', r'\texteuro{}'), + ('∞', r'\(\infty\)'), + ('±', r'\(\pm\)'), + ('→', r'\(\rightarrow\)'), + ('‣', r'\(\rightarrow\)'), # used to separate -- in options - (u'', ur'{}'), + ('', r'{}'), # map some special Unicode characters to similar ASCII ones - (u'─', ur'-'), - (u'⎽', ur'\_'), - (u'╲', ur'\textbackslash{}'), - (u'|', ur'\textbar{}'), - (u'│', ur'\textbar{}'), - (u'ℯ', ur'e'), - (u'ⅈ', ur'i'), - (u'₁', ur'1'), - (u'₂', ur'2'), + ('─', r'-'), + ('⎽', r'\_'), + ('╲', r'\textbackslash{}'), + ('|', r'\textbar{}'), + ('│', r'\textbar{}'), + ('ℯ', r'e'), + ('ⅈ', r'i'), + ('₁', r'1'), + ('₂', r'2'), # map Greek alphabet - (u'α', ur'\(\alpha\)'), - (u'β', ur'\(\beta\)'), - (u'γ', ur'\(\gamma\)'), - (u'δ', ur'\(\delta\)'), - (u'ε', ur'\(\epsilon\)'), - (u'ζ', ur'\(\zeta\)'), - (u'η', ur'\(\eta\)'), - (u'θ', ur'\(\theta\)'), - (u'ι', ur'\(\iota\)'), - (u'κ', ur'\(\kappa\)'), - (u'λ', ur'\(\lambda\)'), - (u'μ', ur'\(\mu\)'), - (u'ν', ur'\(\nu\)'), - (u'ξ', ur'\(\xi\)'), - (u'ο', ur'o'), - (u'π', ur'\(\pi\)'), - (u'ρ', ur'\(\rho\)'), - (u'σ', ur'\(\sigma\)'), - (u'τ', ur'\(\tau\)'), - (u'υ', u'\\(\\upsilon\\)'), - (u'φ', ur'\(\phi\)'), - (u'χ', ur'\(\chi\)'), - (u'ψ', ur'\(\psi\)'), - (u'ω', ur'\(\omega\)'), - (u'Α', ur'A'), - (u'Β', ur'B'), - (u'Γ', ur'\(\Gamma\)'), - (u'Δ', ur'\(\Delta\)'), - (u'Ε', ur'E'), - (u'Ζ', ur'Z'), - (u'Η', ur'H'), - (u'Θ', ur'\(\Theta\)'), - (u'Ι', ur'I'), - (u'Κ', ur'K'), - (u'Λ', ur'\(\Lambda\)'), - (u'Μ', ur'M'), - (u'Ν', ur'N'), - (u'Ξ', ur'\(\Xi\)'), - (u'Ο', ur'O'), - (u'Π', ur'\(\Pi\)'), - (u'Ρ', ur'P'), - (u'Σ', ur'\(\Sigma\)'), - (u'Τ', ur'T'), - (u'Υ', u'\\(\\Upsilon\\)'), - (u'Φ', ur'\(\Phi\)'), - (u'Χ', ur'X'), - (u'Ψ', ur'\(\Psi\)'), - (u'Ω', ur'\(\Omega\)'), - (u'Ω', ur'\(\Omega\)'), + ('α', r'\(\alpha\)'), + ('β', r'\(\beta\)'), + ('γ', r'\(\gamma\)'), + ('δ', r'\(\delta\)'), + ('ε', r'\(\epsilon\)'), + ('ζ', r'\(\zeta\)'), + ('η', r'\(\eta\)'), + ('θ', r'\(\theta\)'), + ('ι', r'\(\iota\)'), + ('κ', r'\(\kappa\)'), + ('λ', r'\(\lambda\)'), + ('μ', r'\(\mu\)'), + ('ν', r'\(\nu\)'), + ('ξ', r'\(\xi\)'), + ('ο', r'o'), + ('π', r'\(\pi\)'), + ('ρ', r'\(\rho\)'), + ('σ', r'\(\sigma\)'), + ('τ', r'\(\tau\)'), + ('υ', '\\(\\upsilon\\)'), + ('φ', r'\(\phi\)'), + ('χ', r'\(\chi\)'), + ('ψ', r'\(\psi\)'), + ('ω', r'\(\omega\)'), + ('Α', r'A'), + ('Β', r'B'), + ('Γ', r'\(\Gamma\)'), + ('Δ', r'\(\Delta\)'), + ('Ε', r'E'), + ('Ζ', r'Z'), + ('Η', r'H'), + ('Θ', r'\(\Theta\)'), + ('Ι', r'I'), + ('Κ', r'K'), + ('Λ', r'\(\Lambda\)'), + ('Μ', r'M'), + ('Ν', r'N'), + ('Ξ', r'\(\Xi\)'), + ('Ο', r'O'), + ('Π', r'\(\Pi\)'), + ('Ρ', r'P'), + ('Σ', r'\(\Sigma\)'), + ('Τ', r'T'), + ('Υ', '\\(\\Upsilon\\)'), + ('Φ', r'\(\Phi\)'), + ('Χ', r'X'), + ('Ψ', r'\(\Psi\)'), + ('Ω', r'\(\Omega\)'), + ('Ω', r'\(\Omega\)'), ] tex_escape_map = {} @@ -105,8 +107,8 @@ tex_hl_escape_map_new = {} def init(): for a, b in tex_replacements: tex_escape_map[ord(a)] = b - tex_replace_map[ord(a)] = u'_' + tex_replace_map[ord(a)] = '_' for a, b in tex_replacements: - if a in u'[]{}\\': continue + if a in '[]{}\\': continue tex_hl_escape_map_new[ord(a)] = b |