2 # Cython -- Things that don't belong
3 # anywhere else in particular
6 import os
, sys
, re
, codecs
8 modification_time
= os
.path
.getmtime
10 def cached_function(f
):
14 res
= cache
.get(args
, uncomputed
)
16 res
= cache
[args
] = f(*args
)
21 cache_name
= '__%s_cache' % f
.__name
__
22 def wrapper(self
, *args
):
23 cache
= getattr(self
, cache_name
, None)
26 setattr(self
, cache_name
, cache
)
29 res
= cache
[args
] = f(self
, *args
)
33 def replace_suffix(path
, newsuf
):
34 base
, _
= os
.path
.splitext(path
)
37 def open_new_file(path
):
38 if os
.path
.exists(path
):
39 # Make sure to create a new file here so we can
40 # safely hard link the output files.
43 # we use the ISO-8859-1 encoding here because we only write pure
44 # ASCII strings or (e.g. for file names) byte encoded strings as
45 # Unicode, so we need a direct mapping from the first 256 Unicode
46 # characters to a byte sequence, which ISO-8859-1 provides
47 return codecs
.open(path
, "w", encoding
="ISO-8859-1")
49 def castrate_file(path
, st
):
50 # Remove junk contents from an output file after a
52 # Also sets access and modification times back to
53 # those specified by st (a stat struct).
55 f
= open_new_file(path
)
56 except EnvironmentError:
60 "#error Do not use this file, it is the result of a failed Cython compilation.\n")
63 os
.utime(path
, (st
.st_atime
, st
.st_mtime
-1))
65 def file_newer_than(path
, time
):
66 ftime
= modification_time(path
)
70 def search_include_directories(dirs
, qualified_name
, suffix
, pos
,
71 include
=False, sys_path
=False):
72 # Search the list of include directories for the given
73 # file name. If a source file position is given, first
74 # searches the directory containing that file. Returns
75 # None if not found, but does not report an error.
76 # The 'include' option will disable package dereferencing.
77 # If 'sys_path' is True, also search sys.path.
79 dirs
= dirs
+ tuple(sys
.path
)
82 from Cython
.Compiler
.Scanning
import FileSourceDescriptor
83 if not isinstance(file_desc
, FileSourceDescriptor
):
84 raise RuntimeError("Only file sources for code supported")
86 dirs
= (os
.path
.dirname(file_desc
.filename
),) + dirs
88 dirs
= (find_root_package_dir(file_desc
.filename
),) + dirs
90 dotted_filename
= qualified_name
92 dotted_filename
+= suffix
94 names
= qualified_name
.split('.')
95 package_names
= tuple(names
[:-1])
96 module_name
= names
[-1]
97 module_filename
= module_name
+ suffix
98 package_filename
= "__init__" + suffix
101 path
= os
.path
.join(dir, dotted_filename
)
102 if path_exists(path
):
105 package_dir
= check_package_dir(dir, package_names
)
106 if package_dir
is not None:
107 path
= os
.path
.join(package_dir
, module_filename
)
108 if path_exists(path
):
110 path
= os
.path
.join(dir, package_dir
, module_name
,
112 if path_exists(path
):
118 def find_root_package_dir(file_path
):
119 dir = os
.path
.dirname(file_path
)
122 elif is_package_dir(dir):
123 return find_root_package_dir(dir)
128 def check_package_dir(dir, package_names
):
129 for dirname
in package_names
:
130 dir = os
.path
.join(dir, dirname
)
131 if not is_package_dir(dir):
136 def is_package_dir(dir_path
):
137 for filename
in ("__init__.py",
140 path
= os
.path
.join(dir_path
, filename
)
141 if path_exists(path
):
145 def path_exists(path
):
146 # try on the filesystem first
147 if os
.path
.exists(path
):
149 # figure out if a PEP 302 loader is around
152 # XXX the code below assumes a 'zipimport.zipimporter' instance
153 # XXX should be easy to generalize, but too lazy right now to write it
154 archive_path
= getattr(loader
, 'archive', None)
156 normpath
= os
.path
.normpath(path
)
157 if normpath
.startswith(archive_path
):
158 arcname
= normpath
[len(archive_path
)+1:]
160 loader
.get_data(arcname
)
168 # file name encodings
170 def decode_filename(filename
):
171 if isinstance(filename
, unicode):
174 filename_encoding
= sys
.getfilesystemencoding()
175 if filename_encoding
is None:
176 filename_encoding
= sys
.getdefaultencoding()
177 filename
= filename
.decode(filename_encoding
)
178 except UnicodeDecodeError:
182 # support for source file encoding detection
184 _match_file_encoding
= re
.compile(u
"coding[:=]\s*([-\w.]+)").search
186 def detect_file_encoding(source_filename
):
187 f
= open_source_file(source_filename
, encoding
="UTF-8", error_handling
='ignore')
189 return detect_opened_file_encoding(f
)
193 def detect_opened_file_encoding(f
):
195 # Most of the time the first two lines fall in the first 250 chars,
196 # and this bulk read/split is much faster.
197 lines
= f
.read(250).split("\n")
199 m
= _match_file_encoding(lines
[0]) or _match_file_encoding(lines
[1])
205 # Fallback to one-char-at-a-time detection.
210 while c
and c
!= u
'\n':
213 encoding
= _match_file_encoding(u
''.join(chars
))
215 return encoding
.group(1)
221 Read past a BOM at the beginning of a source file.
222 This could be added to the scanner, but it's *substantially* easier
223 to keep it at this level.
225 if f
.read(1) != u
'\uFEFF':
229 normalise_newlines
= re
.compile(u
'\r\n?|\n').sub
232 class NormalisedNewlineStream(object):
233 """The codecs module doesn't provide universal newline support.
234 This class is used as a stream wrapper that provides this
235 functionality. The new 'io' in Py2.6+/3.x supports this out of the
239 def __init__(self
, stream
):
240 # let's assume .read() doesn't change
242 self
._read
= stream
.read
243 self
.close
= stream
.close
244 self
.encoding
= getattr(stream
, 'encoding', 'UTF-8')
246 def read(self
, count
=-1):
247 data
= self
._read
(count
)
248 if u
'\r' not in data
:
250 if data
.endswith(u
'\r'):
251 # may be missing a '\n'
252 data
+= self
._read
(1)
253 return normalise_newlines(u
'\n', data
)
257 data
= self
.read(0x1000)
260 data
= self
.read(0x1000)
262 return u
''.join(content
).splitlines(True)
268 raise NotImplementedError
272 if sys
.version_info
>= (2,6):
279 def open_source_file(source_filename
, mode
="r",
280 encoding
=None, error_handling
=None,
281 require_normalised_newlines
=True):
283 # Most of the time the coding is unspecified, so be optimistic that
285 f
= open_source_file(source_filename
, encoding
="UTF-8", mode
=mode
, error_handling
='ignore')
286 encoding
= detect_opened_file_encoding(f
)
287 if (encoding
== "UTF-8"
288 and error_handling
== 'ignore'
289 and require_normalised_newlines
):
296 if not os
.path
.exists(source_filename
):
299 if source_filename
.startswith(loader
.archive
):
300 return open_source_from_loader(
301 loader
, source_filename
,
302 encoding
, error_handling
,
303 require_normalised_newlines
)
304 except (NameError, AttributeError):
308 stream
= io
.open(source_filename
, mode
=mode
,
309 encoding
=encoding
, errors
=error_handling
)
311 # codecs module doesn't have universal newline support
312 stream
= codecs
.open(source_filename
, mode
=mode
,
313 encoding
=encoding
, errors
=error_handling
)
314 if require_normalised_newlines
:
315 stream
= NormalisedNewlineStream(stream
)
320 def open_source_from_loader(loader
,
322 encoding
=None, error_handling
=None,
323 require_normalised_newlines
=True):
324 nrmpath
= os
.path
.normpath(source_filename
)
325 arcname
= nrmpath
[len(loader
.archive
)+1:]
326 data
= loader
.get_data(arcname
)
328 return io
.TextIOWrapper(io
.BytesIO(data
),
330 errors
=error_handling
)
333 import cStringIO
as StringIO
336 reader
= codecs
.getreader(encoding
)
337 stream
= reader(StringIO
.StringIO(data
))
338 if require_normalised_newlines
:
339 stream
= NormalisedNewlineStream(stream
)
342 def str_to_number(value
):
343 # note: this expects a string as input that was accepted by the
346 value
= int(value
, 0)
347 elif value
[0] == '0':
349 # hex notation ('0x1AF')
350 value
= int(value
[2:], 16)
351 elif value
[1] in 'oO':
352 # Py3 octal notation ('0o136')
353 value
= int(value
[2:], 8)
354 elif value
[1] in 'bB':
355 # Py3 binary notation ('0b101')
356 value
= int(value
[2:], 2)
358 # Py2 octal notation ('0136')
359 value
= int(value
, 8)
361 value
= int(value
, 0)
364 def long_literal(value
):
365 if isinstance(value
, basestring
):
366 value
= str_to_number(value
)
367 return not -2**31 <= value
< 2**31
369 # all() and any() are new in 2.5
371 # Make sure to bind them on the module, as they will be accessed as
389 def get_cython_cache_dir():
390 """get the cython cache dir
395 2. (OS X): ~/Library/Caches/Cython
396 (posix not OS X): XDG_CACHE_HOME/cython if XDG_CACHE_HOME defined
400 if 'CYTHON_CACHE_DIR' in os
.environ
:
401 return os
.environ
['CYTHON_CACHE_DIR']
404 if os
.name
== 'posix':
405 if sys
.platform
== 'darwin':
406 parent
= os
.path
.expanduser('~/Library/Caches')
408 # this could fallback on ~/.cache
409 parent
= os
.environ
.get('XDG_CACHE_HOME')
411 if parent
and os
.path
.isdir(parent
):
412 return os
.path
.join(parent
, 'cython')
414 # last fallback: ~/.cython
415 return os
.path
.expanduser(os
.path
.join('~', '.cython'))