summaryrefslogtreecommitdiff
path: root/sphinx/util
diff options
context:
space:
mode:
authorshimizukawa <shimizukawa@gmail.com>2014-08-30 20:20:19 +0900
committershimizukawa <shimizukawa@gmail.com>2014-08-30 20:20:19 +0900
commitada1c91615d995218ec1299fe258bd2d770697ae (patch)
tree01e6f4dcfaf49ea1b5a278dce65f2fb43d880847 /sphinx/util
parent13e3eaa7acb297c114aa8f9cb72e338a818c96ff (diff)
parent451d4aa0a8ae7123e46a899187a3fd8c224d32b7 (diff)
downloadsphinx-ada1c91615d995218ec1299fe258bd2d770697ae.tar.gz
Merge with stable
Diffstat (limited to 'sphinx/util')
-rw-r--r--sphinx/util/__init__.py44
-rw-r--r--sphinx/util/console.py11
-rw-r--r--sphinx/util/docfields.py12
-rw-r--r--sphinx/util/docstrings.py4
-rw-r--r--sphinx/util/i18n.py89
-rw-r--r--sphinx/util/inspect.py25
-rw-r--r--sphinx/util/jsdump.py10
-rw-r--r--sphinx/util/jsonimpl.py30
-rw-r--r--sphinx/util/matching.py2
-rw-r--r--sphinx/util/nodes.py28
-rw-r--r--sphinx/util/osutil.py30
-rw-r--r--sphinx/util/png.py5
-rw-r--r--sphinx/util/pycompat.py231
-rw-r--r--sphinx/util/tags.py4
-rw-r--r--sphinx/util/texescape.py168
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