diff options
Diffstat (limited to 'sphinx/util/pycompat.py')
-rw-r--r-- | sphinx/util/pycompat.py | 231 |
1 files changed, 27 insertions, 204 deletions
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) |