merge
[cython.git] / Cython / Utils.py
1 #
2 #   Cython -- Things that don't belong
3 #            anywhere else in particular
4 #
5
6 import os, sys, re, codecs
7
8 def replace_suffix(path, newsuf):
9     base, _ = os.path.splitext(path)
10     return base + newsuf
11
12 def open_new_file(path):
13     if os.path.exists(path):
14         # Make sure to create a new file here so we can 
15         # safely hard link the output files. 
16         os.unlink(path)
17
18     # we use the ISO-8859-1 encoding here because we only write pure
19     # ASCII strings or (e.g. for file names) byte encoded strings as
20     # Unicode, so we need a direct mapping from the first 256 Unicode
21     # characters to a byte sequence, which ISO-8859-1 provides
22     return codecs.open(path, "w", encoding="ISO-8859-1")
23
24 def castrate_file(path, st):
25     #  Remove junk contents from an output file after a
26     #  failed compilation.
27     #  Also sets access and modification times back to
28     #  those specified by st (a stat struct).
29     try:
30         f = open_new_file(path)
31     except EnvironmentError:
32         pass
33     else:
34         f.write(
35             "#error Do not use this file, it is the result of a failed Cython compilation.\n")
36         f.close()
37         if st:
38             os.utime(path, (st.st_atime, st.st_mtime-1))
39
40 def modification_time(path):
41     st = os.stat(path)
42     return st.st_mtime
43
44 def file_newer_than(path, time):
45     ftime = modification_time(path)
46     return ftime > time
47
48 def path_exists(path):
49     # try on the filesystem first
50     if os.path.exists(path):
51         return True
52     # figure out if a PEP 302 loader is around
53     try:
54         loader = __loader__
55         # XXX the code below assumes as 'zipimport.zipimporter' instance
56         # XXX should be easy to generalize, but too lazy right now to write it
57         if path.startswith(loader.archive):
58             nrmpath = os.path.normpath(path)
59             arcname = nrmpath[len(loader.archive)+1:]
60             try:
61                 loader.get_data(arcname)
62                 return True
63             except IOError:
64                 return False
65     except NameError:
66         pass
67     return False
68
69 # support for source file encoding detection
70
71 def encode_filename(filename):
72     if isinstance(filename, unicode):
73         return filename
74     try:
75         filename_encoding = sys.getfilesystemencoding()
76         if filename_encoding is None:
77             filename_encoding = sys.getdefaultencoding()
78         filename = filename.decode(filename_encoding)
79     except UnicodeDecodeError:
80         pass
81     return filename
82
83 _match_file_encoding = re.compile(u"coding[:=]\s*([-\w.]+)").search
84
85 def detect_file_encoding(source_filename):
86     # PEPs 263 and 3120
87     f = open_source_file(source_filename, encoding="UTF-8", error_handling='ignore')
88     try:
89         chars = []
90         for i in range(2):
91             c = f.read(1)
92             while c and c != u'\n':
93                 chars.append(c)
94                 c = f.read(1)
95             encoding = _match_file_encoding(u''.join(chars))
96             if encoding:
97                 return encoding.group(1)
98     finally:
99         f.close()
100     return "UTF-8"
101
102 normalise_newlines = re.compile(u'\r\n?|\n').sub
103
104 class NormalisedNewlineStream(object):
105   """The codecs module doesn't provide universal newline support.
106   This class is used as a stream wrapper that provides this
107   functionality.  The new 'io' in Py2.6+/3.1+ supports this out of the
108   box.
109   """
110   def __init__(self, stream):
111     # let's assume .read() doesn't change
112     self._read = stream.read
113     self.close = stream.close
114     self.encoding = getattr(stream, 'encoding', 'UTF-8')
115
116   def read(self, count):
117     data = self._read(count)
118     if u'\r' not in data:
119       return data
120     if data.endswith(u'\r'):
121       # may be missing a '\n'
122       data += self._read(1)
123     return normalise_newlines(u'\n', data)
124
125   def readlines(self):
126     content = []
127     data = self._read(0x1000)
128     while data:
129         content.append(data)
130         data = self._read(0x1000)
131     return u''.join(content).split(u'\n')
132
133 io = None
134 if sys.version_info >= (2,6):
135     try:
136         import io
137     except ImportError:
138         pass
139
140 def open_source_file(source_filename, mode="r",
141                      encoding=None, error_handling=None,
142                      require_normalised_newlines=True):
143     if encoding is None:
144         encoding = detect_file_encoding(source_filename)
145     #
146     try:
147         loader = __loader__
148         if source_filename.startswith(loader.archive):
149             return open_source_from_loader(
150                 loader, source_filename,
151                 encoding, error_handling,
152                 require_normalised_newlines)
153     except (NameError, AttributeError):
154         pass
155     #
156     if io is not None:
157         return io.open(source_filename, mode=mode,
158                        encoding=encoding, errors=error_handling)
159     else:
160         # codecs module doesn't have universal newline support
161         stream = codecs.open(source_filename, mode=mode,
162                              encoding=encoding, errors=error_handling)
163         if require_normalised_newlines:
164             stream = NormalisedNewlineStream(stream)
165         return stream
166
167 def open_source_from_loader(loader,
168                             source_filename,
169                             encoding=None, error_handling=None,
170                             require_normalised_newlines=True):
171     nrmpath = os.path.normpath(source_filename)
172     arcname = nrmpath[len(loader.archive)+1:]
173     data = loader.get_data(arcname)
174     if io is not None:
175         return io.TextIOWrapper(io.BytesIO(data),
176                                 encoding=encoding,
177                                 errors=error_handling)
178     else:
179         try:
180             import cStringIO as StringIO
181         except ImportError:
182             import StringIO
183         reader = codecs.getreader(encoding)
184         stream = reader(StringIO.StringIO(data))
185         if require_normalised_newlines:
186             stream = NormalisedNewlineStream(stream)
187         return stream
188
189 def long_literal(value):
190     if isinstance(value, basestring):
191         if len(value) < 2:
192             value = int(value)
193         elif value[0] == 0:
194             value = int(value, 8)
195         elif value[1] in 'xX':
196             value = int(value[2:], 16)
197         else:
198             value = int(value)
199     return not -2**31 <= value < 2**31
200
201 def none_or_sub(s, data):
202     if s is None:
203         return s
204     else:
205         return s % data
206