1# Copyright 2015 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Utilities for Python2 / Python3 compatibility."""
15
16import codecs
17import io
18import os
19import sys
20
21PY3 = sys.version_info[0] >= 3
22PY36 = sys.version_info[0] >= 3 and sys.version_info[1] >= 6
23PY37 = sys.version_info[0] >= 3 and sys.version_info[1] >= 7
24PY38 = sys.version_info[0] >= 3 and sys.version_info[1] >= 8
25
26if PY3:
27  StringIO = io.StringIO
28  BytesIO = io.BytesIO
29
30  import codecs
31
32  def open_with_encoding(filename, mode, encoding, newline=''):  # pylint: disable=unused-argument
33    return codecs.open(filename, mode=mode, encoding=encoding)
34
35  import functools
36  lru_cache = functools.lru_cache
37
38  range = range
39  ifilter = filter
40
41  def raw_input():
42    wrapper = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
43    return wrapper.buffer.raw.readall().decode('utf-8')
44
45  import configparser
46
47  # Mappings from strings to booleans (such as '1' to True, 'false' to False,
48  # etc.)
49  CONFIGPARSER_BOOLEAN_STATES = configparser.ConfigParser.BOOLEAN_STATES
50else:
51  import __builtin__
52  import cStringIO
53  StringIO = BytesIO = cStringIO.StringIO
54
55  open_with_encoding = io.open
56
57  # Python 2.7 doesn't have a native LRU cache, so do nothing.
58  def lru_cache(maxsize=128, typed=False):
59
60    def fake_wrapper(user_function):
61      return user_function
62
63    return fake_wrapper
64
65  range = xrange
66
67  from itertools import ifilter
68  raw_input = raw_input
69
70  import ConfigParser as configparser
71  CONFIGPARSER_BOOLEAN_STATES = configparser.ConfigParser._boolean_states  # pylint: disable=protected-access
72
73
74def EncodeAndWriteToStdout(s, encoding='utf-8'):
75  """Encode the given string and emit to stdout.
76
77  The string may contain non-ascii characters. This is a problem when stdout is
78  redirected, because then Python doesn't know the encoding and we may get a
79  UnicodeEncodeError.
80
81  Arguments:
82    s: (string) The string to encode.
83    encoding: (string) The encoding of the string.
84  """
85  if PY3:
86    sys.stdout.buffer.write(s.encode(encoding))
87  elif sys.platform == 'win32':
88    # On python 2 and Windows universal newline transformation will be in
89    # effect on stdout. Python 2 will not let us avoid the easily because
90    # it happens based on whether the file handle is opened in O_BINARY or
91    # O_TEXT state. However we can tell Windows itself to change the current
92    # mode, and python 2 will follow suit. However we must take care to change
93    # the mode on the actual external stdout not just the current sys.stdout
94    # which may have been monkey-patched inside the python environment.
95    import msvcrt  # pylint: disable=g-import-not-at-top
96    if sys.__stdout__ is sys.stdout:
97      msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
98    sys.stdout.write(s.encode(encoding))
99  else:
100    sys.stdout.write(s.encode(encoding))
101
102
103if PY3:
104  basestring = str
105  unicode = str  # pylint: disable=redefined-builtin,invalid-name
106else:
107  basestring = basestring
108
109  def unicode(s):  # pylint: disable=invalid-name
110    """Force conversion of s to unicode."""
111    return __builtin__.unicode(s, 'utf-8')
112
113
114# In Python 3.2+, readfp is deprecated in favor of read_file, which doesn't
115# exist in Python 2 yet. To avoid deprecation warnings, subclass ConfigParser to
116# fix this - now read_file works across all Python versions we care about.
117class ConfigParser(configparser.ConfigParser):
118  if not PY3:
119
120    def read_file(self, fp, source=None):
121      self.readfp(fp, filename=source)
122
123
124def removeBOM(source):
125  """Remove any Byte-order-Mark bytes from the beginning of a file."""
126  bom = codecs.BOM_UTF8
127  if PY3:
128    bom = bom.decode('utf-8')
129  if source.startswith(bom):
130    return source[len(bom):]
131  return source
132