1"""Add things to old Pythons so I can pretend they are newer."""
2
3# This file does lots of tricky stuff, so disable a bunch of lintisms.
4# pylint: disable=F0401,W0611,W0622
5# F0401: Unable to import blah
6# W0611: Unused import blah
7# W0622: Redefining built-in blah
8
9import os, sys
10
11# Python 2.3 doesn't have `set`
12try:
13    set = set       # new in 2.4
14except NameError:
15    from sets import Set as set
16
17# Python 2.3 doesn't have `sorted`.
18try:
19    sorted = sorted
20except NameError:
21    def sorted(iterable):
22        """A 2.3-compatible implementation of `sorted`."""
23        lst = list(iterable)
24        lst.sort()
25        return lst
26
27# Pythons 2 and 3 differ on where to get StringIO
28try:
29    from cStringIO import StringIO
30    BytesIO = StringIO
31except ImportError:
32    from io import StringIO, BytesIO
33
34# What's a string called?
35try:
36    string_class = basestring
37except NameError:
38    string_class = str
39
40# Where do pickles come from?
41try:
42    import cPickle as pickle
43except ImportError:
44    import pickle
45
46# range or xrange?
47try:
48    range = xrange
49except NameError:
50    range = range
51
52# Exec is a statement in Py2, a function in Py3
53if sys.version_info >= (3, 0):
54    def exec_code_object(code, global_map):
55        """A wrapper around exec()."""
56        exec(code, global_map)
57else:
58    # OK, this is pretty gross.  In Py2, exec was a statement, but that will
59    # be a syntax error if we try to put it in a Py3 file, even if it is never
60    # executed.  So hide it inside an evaluated string literal instead.
61    eval(
62        compile(
63            "def exec_code_object(code, global_map):\n"
64            "    exec code in global_map\n",
65            "<exec_function>", "exec"
66            )
67        )
68
69# ConfigParser was renamed to the more-standard configparser
70try:
71    import configparser
72except ImportError:
73    import ConfigParser as configparser
74
75# Python 3.2 provides `tokenize.open`, the best way to open source files.
76import tokenize
77try:
78    open_source = tokenize.open     # pylint: disable=E1101
79except AttributeError:
80    try:
81        detect_encoding = tokenize.detect_encoding  # pylint: disable=E1101
82    except AttributeError:
83        def open_source(fname):
84            """Open a source file the best way."""
85            return open(fname, "rU")
86    else:
87        from io import TextIOWrapper
88        # Copied from the 3.2 stdlib:
89        def open_source(fname):
90            """Open a file in read only mode using the encoding detected by
91            detect_encoding().
92            """
93            buffer = open(fname, 'rb')
94            encoding, _ = detect_encoding(buffer.readline)
95            buffer.seek(0)
96            text = TextIOWrapper(buffer, encoding, line_buffering=True)
97            text.mode = 'r'
98            return text
99
100# Python 3.x is picky about bytes and strings, so provide methods to
101# get them right, and make them no-ops in 2.x
102if sys.version_info >= (3, 0):
103    def to_bytes(s):
104        """Convert string `s` to bytes."""
105        return s.encode('utf8')
106
107    def to_string(b):
108        """Convert bytes `b` to a string."""
109        return b.decode('utf8')
110
111else:
112    def to_bytes(s):
113        """Convert string `s` to bytes (no-op in 2.x)."""
114        return s
115
116    def to_string(b):
117        """Convert bytes `b` to a string (no-op in 2.x)."""
118        return b
119
120# A few details about writing encoded text are different in 2.x and 3.x.
121if sys.version_info >= (3, 0):
122    def write_encoded(fname, text, encoding='utf8', errors='strict'):
123        '''Write string `text` to file names `fname`, with encoding.'''
124        # Don't use "with", so that this file is still good for old 2.x.
125        f = open(fname, 'w', encoding=encoding, errors=errors)
126        try:
127            f.write(text)
128        finally:
129            f.close()
130else:
131    # It's not clear that using utf8 strings in 2.x is the right thing to do.
132    def write_encoded(fname, text, encoding='utf8', errors='strict'):
133        '''Write utf8 string `text` to file names `fname`, with encoding.'''
134        import codecs
135        f = codecs.open(fname, 'w', encoding=encoding, errors=errors)
136        try:
137            f.write(text.decode('utf8'))
138        finally:
139            f.close()
140
141# Md5 is available in different places.
142try:
143    import hashlib
144    md5 = hashlib.md5
145except ImportError:
146    import md5
147    md5 = md5.new
148