##// END OF EJS Templates
Backport PR #5917: use shutil.move instead of os.rename...
MinRK -
Show More
@@ -1,577 +1,577 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18 import sys
18 import sys
19 import errno
19 import errno
20 import shutil
20 import shutil
21 import random
21 import random
22 import tempfile
22 import tempfile
23 import warnings
23 import warnings
24 from hashlib import md5
24 from hashlib import md5
25 import glob
25 import glob
26
26
27 import IPython
27 import IPython
28 from IPython.testing.skipdoctest import skip_doctest
28 from IPython.testing.skipdoctest import skip_doctest
29 from IPython.utils.process import system
29 from IPython.utils.process import system
30 from IPython.utils.importstring import import_item
30 from IPython.utils.importstring import import_item
31 from IPython.utils import py3compat
31 from IPython.utils import py3compat
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Code
33 # Code
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 fs_encoding = sys.getfilesystemencoding()
36 fs_encoding = sys.getfilesystemencoding()
37
37
38 def _get_long_path_name(path):
38 def _get_long_path_name(path):
39 """Dummy no-op."""
39 """Dummy no-op."""
40 return path
40 return path
41
41
42 def _writable_dir(path):
42 def _writable_dir(path):
43 """Whether `path` is a directory, to which the user has write access."""
43 """Whether `path` is a directory, to which the user has write access."""
44 return os.path.isdir(path) and os.access(path, os.W_OK)
44 return os.path.isdir(path) and os.access(path, os.W_OK)
45
45
46 if sys.platform == 'win32':
46 if sys.platform == 'win32':
47 @skip_doctest
47 @skip_doctest
48 def _get_long_path_name(path):
48 def _get_long_path_name(path):
49 """Get a long path name (expand ~) on Windows using ctypes.
49 """Get a long path name (expand ~) on Windows using ctypes.
50
50
51 Examples
51 Examples
52 --------
52 --------
53
53
54 >>> get_long_path_name('c:\\docume~1')
54 >>> get_long_path_name('c:\\docume~1')
55 u'c:\\\\Documents and Settings'
55 u'c:\\\\Documents and Settings'
56
56
57 """
57 """
58 try:
58 try:
59 import ctypes
59 import ctypes
60 except ImportError:
60 except ImportError:
61 raise ImportError('you need to have ctypes installed for this to work')
61 raise ImportError('you need to have ctypes installed for this to work')
62 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
62 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
63 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
63 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
64 ctypes.c_uint ]
64 ctypes.c_uint ]
65
65
66 buf = ctypes.create_unicode_buffer(260)
66 buf = ctypes.create_unicode_buffer(260)
67 rv = _GetLongPathName(path, buf, 260)
67 rv = _GetLongPathName(path, buf, 260)
68 if rv == 0 or rv > 260:
68 if rv == 0 or rv > 260:
69 return path
69 return path
70 else:
70 else:
71 return buf.value
71 return buf.value
72
72
73
73
74 def get_long_path_name(path):
74 def get_long_path_name(path):
75 """Expand a path into its long form.
75 """Expand a path into its long form.
76
76
77 On Windows this expands any ~ in the paths. On other platforms, it is
77 On Windows this expands any ~ in the paths. On other platforms, it is
78 a null operation.
78 a null operation.
79 """
79 """
80 return _get_long_path_name(path)
80 return _get_long_path_name(path)
81
81
82
82
83 def unquote_filename(name, win32=(sys.platform=='win32')):
83 def unquote_filename(name, win32=(sys.platform=='win32')):
84 """ On Windows, remove leading and trailing quotes from filenames.
84 """ On Windows, remove leading and trailing quotes from filenames.
85 """
85 """
86 if win32:
86 if win32:
87 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
87 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
88 name = name[1:-1]
88 name = name[1:-1]
89 return name
89 return name
90
90
91 def compress_user(path):
91 def compress_user(path):
92 """Reverse of :func:`os.path.expanduser`
92 """Reverse of :func:`os.path.expanduser`
93 """
93 """
94 home = os.path.expanduser('~')
94 home = os.path.expanduser('~')
95 if path.startswith(home):
95 if path.startswith(home):
96 path = "~" + path[len(home):]
96 path = "~" + path[len(home):]
97 return path
97 return path
98
98
99 def get_py_filename(name, force_win32=None):
99 def get_py_filename(name, force_win32=None):
100 """Return a valid python filename in the current directory.
100 """Return a valid python filename in the current directory.
101
101
102 If the given name is not a file, it adds '.py' and searches again.
102 If the given name is not a file, it adds '.py' and searches again.
103 Raises IOError with an informative message if the file isn't found.
103 Raises IOError with an informative message if the file isn't found.
104
104
105 On Windows, apply Windows semantics to the filename. In particular, remove
105 On Windows, apply Windows semantics to the filename. In particular, remove
106 any quoting that has been applied to it. This option can be forced for
106 any quoting that has been applied to it. This option can be forced for
107 testing purposes.
107 testing purposes.
108 """
108 """
109
109
110 name = os.path.expanduser(name)
110 name = os.path.expanduser(name)
111 if force_win32 is None:
111 if force_win32 is None:
112 win32 = (sys.platform == 'win32')
112 win32 = (sys.platform == 'win32')
113 else:
113 else:
114 win32 = force_win32
114 win32 = force_win32
115 name = unquote_filename(name, win32=win32)
115 name = unquote_filename(name, win32=win32)
116 if not os.path.isfile(name) and not name.endswith('.py'):
116 if not os.path.isfile(name) and not name.endswith('.py'):
117 name += '.py'
117 name += '.py'
118 if os.path.isfile(name):
118 if os.path.isfile(name):
119 return name
119 return name
120 else:
120 else:
121 raise IOError('File `%r` not found.' % name)
121 raise IOError('File `%r` not found.' % name)
122
122
123
123
124 def filefind(filename, path_dirs=None):
124 def filefind(filename, path_dirs=None):
125 """Find a file by looking through a sequence of paths.
125 """Find a file by looking through a sequence of paths.
126
126
127 This iterates through a sequence of paths looking for a file and returns
127 This iterates through a sequence of paths looking for a file and returns
128 the full, absolute path of the first occurence of the file. If no set of
128 the full, absolute path of the first occurence of the file. If no set of
129 path dirs is given, the filename is tested as is, after running through
129 path dirs is given, the filename is tested as is, after running through
130 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
130 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
131
131
132 filefind('myfile.txt')
132 filefind('myfile.txt')
133
133
134 will find the file in the current working dir, but::
134 will find the file in the current working dir, but::
135
135
136 filefind('~/myfile.txt')
136 filefind('~/myfile.txt')
137
137
138 Will find the file in the users home directory. This function does not
138 Will find the file in the users home directory. This function does not
139 automatically try any paths, such as the cwd or the user's home directory.
139 automatically try any paths, such as the cwd or the user's home directory.
140
140
141 Parameters
141 Parameters
142 ----------
142 ----------
143 filename : str
143 filename : str
144 The filename to look for.
144 The filename to look for.
145 path_dirs : str, None or sequence of str
145 path_dirs : str, None or sequence of str
146 The sequence of paths to look for the file in. If None, the filename
146 The sequence of paths to look for the file in. If None, the filename
147 need to be absolute or be in the cwd. If a string, the string is
147 need to be absolute or be in the cwd. If a string, the string is
148 put into a sequence and the searched. If a sequence, walk through
148 put into a sequence and the searched. If a sequence, walk through
149 each element and join with ``filename``, calling :func:`expandvars`
149 each element and join with ``filename``, calling :func:`expandvars`
150 and :func:`expanduser` before testing for existence.
150 and :func:`expanduser` before testing for existence.
151
151
152 Returns
152 Returns
153 -------
153 -------
154 Raises :exc:`IOError` or returns absolute path to file.
154 Raises :exc:`IOError` or returns absolute path to file.
155 """
155 """
156
156
157 # If paths are quoted, abspath gets confused, strip them...
157 # If paths are quoted, abspath gets confused, strip them...
158 filename = filename.strip('"').strip("'")
158 filename = filename.strip('"').strip("'")
159 # If the input is an absolute path, just check it exists
159 # If the input is an absolute path, just check it exists
160 if os.path.isabs(filename) and os.path.isfile(filename):
160 if os.path.isabs(filename) and os.path.isfile(filename):
161 return filename
161 return filename
162
162
163 if path_dirs is None:
163 if path_dirs is None:
164 path_dirs = ("",)
164 path_dirs = ("",)
165 elif isinstance(path_dirs, py3compat.string_types):
165 elif isinstance(path_dirs, py3compat.string_types):
166 path_dirs = (path_dirs,)
166 path_dirs = (path_dirs,)
167
167
168 for path in path_dirs:
168 for path in path_dirs:
169 if path == '.': path = py3compat.getcwd()
169 if path == '.': path = py3compat.getcwd()
170 testname = expand_path(os.path.join(path, filename))
170 testname = expand_path(os.path.join(path, filename))
171 if os.path.isfile(testname):
171 if os.path.isfile(testname):
172 return os.path.abspath(testname)
172 return os.path.abspath(testname)
173
173
174 raise IOError("File %r does not exist in any of the search paths: %r" %
174 raise IOError("File %r does not exist in any of the search paths: %r" %
175 (filename, path_dirs) )
175 (filename, path_dirs) )
176
176
177
177
178 class HomeDirError(Exception):
178 class HomeDirError(Exception):
179 pass
179 pass
180
180
181
181
182 def get_home_dir(require_writable=False):
182 def get_home_dir(require_writable=False):
183 """Return the 'home' directory, as a unicode string.
183 """Return the 'home' directory, as a unicode string.
184
184
185 Uses os.path.expanduser('~'), and checks for writability.
185 Uses os.path.expanduser('~'), and checks for writability.
186
186
187 See stdlib docs for how this is determined.
187 See stdlib docs for how this is determined.
188 $HOME is first priority on *ALL* platforms.
188 $HOME is first priority on *ALL* platforms.
189
189
190 Parameters
190 Parameters
191 ----------
191 ----------
192
192
193 require_writable : bool [default: False]
193 require_writable : bool [default: False]
194 if True:
194 if True:
195 guarantees the return value is a writable directory, otherwise
195 guarantees the return value is a writable directory, otherwise
196 raises HomeDirError
196 raises HomeDirError
197 if False:
197 if False:
198 The path is resolved, but it is not guaranteed to exist or be writable.
198 The path is resolved, but it is not guaranteed to exist or be writable.
199 """
199 """
200
200
201 homedir = os.path.expanduser('~')
201 homedir = os.path.expanduser('~')
202 # Next line will make things work even when /home/ is a symlink to
202 # Next line will make things work even when /home/ is a symlink to
203 # /usr/home as it is on FreeBSD, for example
203 # /usr/home as it is on FreeBSD, for example
204 homedir = os.path.realpath(homedir)
204 homedir = os.path.realpath(homedir)
205
205
206 if not _writable_dir(homedir) and os.name == 'nt':
206 if not _writable_dir(homedir) and os.name == 'nt':
207 # expanduser failed, use the registry to get the 'My Documents' folder.
207 # expanduser failed, use the registry to get the 'My Documents' folder.
208 try:
208 try:
209 try:
209 try:
210 import winreg as wreg # Py 3
210 import winreg as wreg # Py 3
211 except ImportError:
211 except ImportError:
212 import _winreg as wreg # Py 2
212 import _winreg as wreg # Py 2
213 key = wreg.OpenKey(
213 key = wreg.OpenKey(
214 wreg.HKEY_CURRENT_USER,
214 wreg.HKEY_CURRENT_USER,
215 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
215 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
216 )
216 )
217 homedir = wreg.QueryValueEx(key,'Personal')[0]
217 homedir = wreg.QueryValueEx(key,'Personal')[0]
218 key.Close()
218 key.Close()
219 except:
219 except:
220 pass
220 pass
221
221
222 if (not require_writable) or _writable_dir(homedir):
222 if (not require_writable) or _writable_dir(homedir):
223 return py3compat.cast_unicode(homedir, fs_encoding)
223 return py3compat.cast_unicode(homedir, fs_encoding)
224 else:
224 else:
225 raise HomeDirError('%s is not a writable dir, '
225 raise HomeDirError('%s is not a writable dir, '
226 'set $HOME environment variable to override' % homedir)
226 'set $HOME environment variable to override' % homedir)
227
227
228 def get_xdg_dir():
228 def get_xdg_dir():
229 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
229 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
230
230
231 This is only for non-OS X posix (Linux,Unix,etc.) systems.
231 This is only for non-OS X posix (Linux,Unix,etc.) systems.
232 """
232 """
233
233
234 env = os.environ
234 env = os.environ
235
235
236 if os.name == 'posix' and sys.platform != 'darwin':
236 if os.name == 'posix' and sys.platform != 'darwin':
237 # Linux, Unix, AIX, etc.
237 # Linux, Unix, AIX, etc.
238 # use ~/.config if empty OR not set
238 # use ~/.config if empty OR not set
239 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
239 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
240 if xdg and _writable_dir(xdg):
240 if xdg and _writable_dir(xdg):
241 return py3compat.cast_unicode(xdg, fs_encoding)
241 return py3compat.cast_unicode(xdg, fs_encoding)
242
242
243 return None
243 return None
244
244
245
245
246 def get_xdg_cache_dir():
246 def get_xdg_cache_dir():
247 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
247 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
248
248
249 This is only for non-OS X posix (Linux,Unix,etc.) systems.
249 This is only for non-OS X posix (Linux,Unix,etc.) systems.
250 """
250 """
251
251
252 env = os.environ
252 env = os.environ
253
253
254 if os.name == 'posix' and sys.platform != 'darwin':
254 if os.name == 'posix' and sys.platform != 'darwin':
255 # Linux, Unix, AIX, etc.
255 # Linux, Unix, AIX, etc.
256 # use ~/.cache if empty OR not set
256 # use ~/.cache if empty OR not set
257 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
257 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
258 if xdg and _writable_dir(xdg):
258 if xdg and _writable_dir(xdg):
259 return py3compat.cast_unicode(xdg, fs_encoding)
259 return py3compat.cast_unicode(xdg, fs_encoding)
260
260
261 return None
261 return None
262
262
263
263
264 def get_ipython_dir():
264 def get_ipython_dir():
265 """Get the IPython directory for this platform and user.
265 """Get the IPython directory for this platform and user.
266
266
267 This uses the logic in `get_home_dir` to find the home directory
267 This uses the logic in `get_home_dir` to find the home directory
268 and then adds .ipython to the end of the path.
268 and then adds .ipython to the end of the path.
269 """
269 """
270
270
271 env = os.environ
271 env = os.environ
272 pjoin = os.path.join
272 pjoin = os.path.join
273
273
274
274
275 ipdir_def = '.ipython'
275 ipdir_def = '.ipython'
276
276
277 home_dir = get_home_dir()
277 home_dir = get_home_dir()
278 xdg_dir = get_xdg_dir()
278 xdg_dir = get_xdg_dir()
279
279
280 # import pdb; pdb.set_trace() # dbg
280 # import pdb; pdb.set_trace() # dbg
281 if 'IPYTHON_DIR' in env:
281 if 'IPYTHON_DIR' in env:
282 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
282 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
283 'Please use IPYTHONDIR instead.')
283 'Please use IPYTHONDIR instead.')
284 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
284 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
285 if ipdir is None:
285 if ipdir is None:
286 # not set explicitly, use ~/.ipython
286 # not set explicitly, use ~/.ipython
287 ipdir = pjoin(home_dir, ipdir_def)
287 ipdir = pjoin(home_dir, ipdir_def)
288 if xdg_dir:
288 if xdg_dir:
289 # Several IPython versions (up to 1.x) defaulted to .config/ipython
289 # Several IPython versions (up to 1.x) defaulted to .config/ipython
290 # on Linux. We have decided to go back to using .ipython everywhere
290 # on Linux. We have decided to go back to using .ipython everywhere
291 xdg_ipdir = pjoin(xdg_dir, 'ipython')
291 xdg_ipdir = pjoin(xdg_dir, 'ipython')
292
292
293 if _writable_dir(xdg_ipdir):
293 if _writable_dir(xdg_ipdir):
294 cu = compress_user
294 cu = compress_user
295 if os.path.exists(ipdir):
295 if os.path.exists(ipdir):
296 warnings.warn(('Ignoring {0} in favour of {1}. Remove {0} '
296 warnings.warn(('Ignoring {0} in favour of {1}. Remove {0} '
297 'to get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
297 'to get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
298 elif os.path.islink(xdg_ipdir):
298 elif os.path.islink(xdg_ipdir):
299 warnings.warn(('{0} is deprecated. Move link to {1} '
299 warnings.warn(('{0} is deprecated. Move link to {1} '
300 'to get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
300 'to get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
301 else:
301 else:
302 warnings.warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
302 warnings.warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
303 os.rename(xdg_ipdir, ipdir)
303 shutil.move(xdg_ipdir, ipdir)
304
304
305 ipdir = os.path.normpath(os.path.expanduser(ipdir))
305 ipdir = os.path.normpath(os.path.expanduser(ipdir))
306
306
307 if os.path.exists(ipdir) and not _writable_dir(ipdir):
307 if os.path.exists(ipdir) and not _writable_dir(ipdir):
308 # ipdir exists, but is not writable
308 # ipdir exists, but is not writable
309 warnings.warn("IPython dir '%s' is not a writable location,"
309 warnings.warn("IPython dir '%s' is not a writable location,"
310 " using a temp directory."%ipdir)
310 " using a temp directory."%ipdir)
311 ipdir = tempfile.mkdtemp()
311 ipdir = tempfile.mkdtemp()
312 elif not os.path.exists(ipdir):
312 elif not os.path.exists(ipdir):
313 parent = os.path.dirname(ipdir)
313 parent = os.path.dirname(ipdir)
314 if not _writable_dir(parent):
314 if not _writable_dir(parent):
315 # ipdir does not exist and parent isn't writable
315 # ipdir does not exist and parent isn't writable
316 warnings.warn("IPython parent '%s' is not a writable location,"
316 warnings.warn("IPython parent '%s' is not a writable location,"
317 " using a temp directory."%parent)
317 " using a temp directory."%parent)
318 ipdir = tempfile.mkdtemp()
318 ipdir = tempfile.mkdtemp()
319
319
320 return py3compat.cast_unicode(ipdir, fs_encoding)
320 return py3compat.cast_unicode(ipdir, fs_encoding)
321
321
322
322
323 def get_ipython_cache_dir():
323 def get_ipython_cache_dir():
324 """Get the cache directory it is created if it does not exist."""
324 """Get the cache directory it is created if it does not exist."""
325 xdgdir = get_xdg_cache_dir()
325 xdgdir = get_xdg_cache_dir()
326 if xdgdir is None:
326 if xdgdir is None:
327 return get_ipython_dir()
327 return get_ipython_dir()
328 ipdir = os.path.join(xdgdir, "ipython")
328 ipdir = os.path.join(xdgdir, "ipython")
329 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
329 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
330 os.makedirs(ipdir)
330 os.makedirs(ipdir)
331 elif not _writable_dir(xdgdir):
331 elif not _writable_dir(xdgdir):
332 return get_ipython_dir()
332 return get_ipython_dir()
333
333
334 return py3compat.cast_unicode(ipdir, fs_encoding)
334 return py3compat.cast_unicode(ipdir, fs_encoding)
335
335
336
336
337 def get_ipython_package_dir():
337 def get_ipython_package_dir():
338 """Get the base directory where IPython itself is installed."""
338 """Get the base directory where IPython itself is installed."""
339 ipdir = os.path.dirname(IPython.__file__)
339 ipdir = os.path.dirname(IPython.__file__)
340 return py3compat.cast_unicode(ipdir, fs_encoding)
340 return py3compat.cast_unicode(ipdir, fs_encoding)
341
341
342
342
343 def get_ipython_module_path(module_str):
343 def get_ipython_module_path(module_str):
344 """Find the path to an IPython module in this version of IPython.
344 """Find the path to an IPython module in this version of IPython.
345
345
346 This will always find the version of the module that is in this importable
346 This will always find the version of the module that is in this importable
347 IPython package. This will always return the path to the ``.py``
347 IPython package. This will always return the path to the ``.py``
348 version of the module.
348 version of the module.
349 """
349 """
350 if module_str == 'IPython':
350 if module_str == 'IPython':
351 return os.path.join(get_ipython_package_dir(), '__init__.py')
351 return os.path.join(get_ipython_package_dir(), '__init__.py')
352 mod = import_item(module_str)
352 mod = import_item(module_str)
353 the_path = mod.__file__.replace('.pyc', '.py')
353 the_path = mod.__file__.replace('.pyc', '.py')
354 the_path = the_path.replace('.pyo', '.py')
354 the_path = the_path.replace('.pyo', '.py')
355 return py3compat.cast_unicode(the_path, fs_encoding)
355 return py3compat.cast_unicode(the_path, fs_encoding)
356
356
357 def locate_profile(profile='default'):
357 def locate_profile(profile='default'):
358 """Find the path to the folder associated with a given profile.
358 """Find the path to the folder associated with a given profile.
359
359
360 I.e. find $IPYTHONDIR/profile_whatever.
360 I.e. find $IPYTHONDIR/profile_whatever.
361 """
361 """
362 from IPython.core.profiledir import ProfileDir, ProfileDirError
362 from IPython.core.profiledir import ProfileDir, ProfileDirError
363 try:
363 try:
364 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
364 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
365 except ProfileDirError:
365 except ProfileDirError:
366 # IOError makes more sense when people are expecting a path
366 # IOError makes more sense when people are expecting a path
367 raise IOError("Couldn't find profile %r" % profile)
367 raise IOError("Couldn't find profile %r" % profile)
368 return pd.location
368 return pd.location
369
369
370 def expand_path(s):
370 def expand_path(s):
371 """Expand $VARS and ~names in a string, like a shell
371 """Expand $VARS and ~names in a string, like a shell
372
372
373 :Examples:
373 :Examples:
374
374
375 In [2]: os.environ['FOO']='test'
375 In [2]: os.environ['FOO']='test'
376
376
377 In [3]: expand_path('variable FOO is $FOO')
377 In [3]: expand_path('variable FOO is $FOO')
378 Out[3]: 'variable FOO is test'
378 Out[3]: 'variable FOO is test'
379 """
379 """
380 # This is a pretty subtle hack. When expand user is given a UNC path
380 # This is a pretty subtle hack. When expand user is given a UNC path
381 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
381 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
382 # the $ to get (\\server\share\%username%). I think it considered $
382 # the $ to get (\\server\share\%username%). I think it considered $
383 # alone an empty var. But, we need the $ to remains there (it indicates
383 # alone an empty var. But, we need the $ to remains there (it indicates
384 # a hidden share).
384 # a hidden share).
385 if os.name=='nt':
385 if os.name=='nt':
386 s = s.replace('$\\', 'IPYTHON_TEMP')
386 s = s.replace('$\\', 'IPYTHON_TEMP')
387 s = os.path.expandvars(os.path.expanduser(s))
387 s = os.path.expandvars(os.path.expanduser(s))
388 if os.name=='nt':
388 if os.name=='nt':
389 s = s.replace('IPYTHON_TEMP', '$\\')
389 s = s.replace('IPYTHON_TEMP', '$\\')
390 return s
390 return s
391
391
392
392
393 def unescape_glob(string):
393 def unescape_glob(string):
394 """Unescape glob pattern in `string`."""
394 """Unescape glob pattern in `string`."""
395 def unescape(s):
395 def unescape(s):
396 for pattern in '*[]!?':
396 for pattern in '*[]!?':
397 s = s.replace(r'\{0}'.format(pattern), pattern)
397 s = s.replace(r'\{0}'.format(pattern), pattern)
398 return s
398 return s
399 return '\\'.join(map(unescape, string.split('\\\\')))
399 return '\\'.join(map(unescape, string.split('\\\\')))
400
400
401
401
402 def shellglob(args):
402 def shellglob(args):
403 """
403 """
404 Do glob expansion for each element in `args` and return a flattened list.
404 Do glob expansion for each element in `args` and return a flattened list.
405
405
406 Unmatched glob pattern will remain as-is in the returned list.
406 Unmatched glob pattern will remain as-is in the returned list.
407
407
408 """
408 """
409 expanded = []
409 expanded = []
410 # Do not unescape backslash in Windows as it is interpreted as
410 # Do not unescape backslash in Windows as it is interpreted as
411 # path separator:
411 # path separator:
412 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
412 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
413 for a in args:
413 for a in args:
414 expanded.extend(glob.glob(a) or [unescape(a)])
414 expanded.extend(glob.glob(a) or [unescape(a)])
415 return expanded
415 return expanded
416
416
417
417
418 def target_outdated(target,deps):
418 def target_outdated(target,deps):
419 """Determine whether a target is out of date.
419 """Determine whether a target is out of date.
420
420
421 target_outdated(target,deps) -> 1/0
421 target_outdated(target,deps) -> 1/0
422
422
423 deps: list of filenames which MUST exist.
423 deps: list of filenames which MUST exist.
424 target: single filename which may or may not exist.
424 target: single filename which may or may not exist.
425
425
426 If target doesn't exist or is older than any file listed in deps, return
426 If target doesn't exist or is older than any file listed in deps, return
427 true, otherwise return false.
427 true, otherwise return false.
428 """
428 """
429 try:
429 try:
430 target_time = os.path.getmtime(target)
430 target_time = os.path.getmtime(target)
431 except os.error:
431 except os.error:
432 return 1
432 return 1
433 for dep in deps:
433 for dep in deps:
434 dep_time = os.path.getmtime(dep)
434 dep_time = os.path.getmtime(dep)
435 if dep_time > target_time:
435 if dep_time > target_time:
436 #print "For target",target,"Dep failed:",dep # dbg
436 #print "For target",target,"Dep failed:",dep # dbg
437 #print "times (dep,tar):",dep_time,target_time # dbg
437 #print "times (dep,tar):",dep_time,target_time # dbg
438 return 1
438 return 1
439 return 0
439 return 0
440
440
441
441
442 def target_update(target,deps,cmd):
442 def target_update(target,deps,cmd):
443 """Update a target with a given command given a list of dependencies.
443 """Update a target with a given command given a list of dependencies.
444
444
445 target_update(target,deps,cmd) -> runs cmd if target is outdated.
445 target_update(target,deps,cmd) -> runs cmd if target is outdated.
446
446
447 This is just a wrapper around target_outdated() which calls the given
447 This is just a wrapper around target_outdated() which calls the given
448 command if target is outdated."""
448 command if target is outdated."""
449
449
450 if target_outdated(target,deps):
450 if target_outdated(target,deps):
451 system(cmd)
451 system(cmd)
452
452
453 def filehash(path):
453 def filehash(path):
454 """Make an MD5 hash of a file, ignoring any differences in line
454 """Make an MD5 hash of a file, ignoring any differences in line
455 ending characters."""
455 ending characters."""
456 with open(path, "rU") as f:
456 with open(path, "rU") as f:
457 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
457 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
458
458
459 # If the config is unmodified from the default, we'll just delete it.
459 # If the config is unmodified from the default, we'll just delete it.
460 # These are consistent for 0.10.x, thankfully. We're not going to worry about
460 # These are consistent for 0.10.x, thankfully. We're not going to worry about
461 # older versions.
461 # older versions.
462 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
462 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
463 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
463 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
464
464
465 def check_for_old_config(ipython_dir=None):
465 def check_for_old_config(ipython_dir=None):
466 """Check for old config files, and present a warning if they exist.
466 """Check for old config files, and present a warning if they exist.
467
467
468 A link to the docs of the new config is included in the message.
468 A link to the docs of the new config is included in the message.
469
469
470 This should mitigate confusion with the transition to the new
470 This should mitigate confusion with the transition to the new
471 config system in 0.11.
471 config system in 0.11.
472 """
472 """
473 if ipython_dir is None:
473 if ipython_dir is None:
474 ipython_dir = get_ipython_dir()
474 ipython_dir = get_ipython_dir()
475
475
476 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
476 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
477 warned = False
477 warned = False
478 for cfg in old_configs:
478 for cfg in old_configs:
479 f = os.path.join(ipython_dir, cfg)
479 f = os.path.join(ipython_dir, cfg)
480 if os.path.exists(f):
480 if os.path.exists(f):
481 if filehash(f) == old_config_md5.get(cfg, ''):
481 if filehash(f) == old_config_md5.get(cfg, ''):
482 os.unlink(f)
482 os.unlink(f)
483 else:
483 else:
484 warnings.warn("Found old IPython config file %r (modified by user)"%f)
484 warnings.warn("Found old IPython config file %r (modified by user)"%f)
485 warned = True
485 warned = True
486
486
487 if warned:
487 if warned:
488 warnings.warn("""
488 warnings.warn("""
489 The IPython configuration system has changed as of 0.11, and these files will
489 The IPython configuration system has changed as of 0.11, and these files will
490 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
490 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
491 of the new config system.
491 of the new config system.
492 To start configuring IPython, do `ipython profile create`, and edit
492 To start configuring IPython, do `ipython profile create`, and edit
493 `ipython_config.py` in <ipython_dir>/profile_default.
493 `ipython_config.py` in <ipython_dir>/profile_default.
494 If you need to leave the old config files in place for an older version of
494 If you need to leave the old config files in place for an older version of
495 IPython and want to suppress this warning message, set
495 IPython and want to suppress this warning message, set
496 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
496 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
497
497
498 def get_security_file(filename, profile='default'):
498 def get_security_file(filename, profile='default'):
499 """Return the absolute path of a security file given by filename and profile
499 """Return the absolute path of a security file given by filename and profile
500
500
501 This allows users and developers to find security files without
501 This allows users and developers to find security files without
502 knowledge of the IPython directory structure. The search path
502 knowledge of the IPython directory structure. The search path
503 will be ['.', profile.security_dir]
503 will be ['.', profile.security_dir]
504
504
505 Parameters
505 Parameters
506 ----------
506 ----------
507
507
508 filename : str
508 filename : str
509 The file to be found. If it is passed as an absolute path, it will
509 The file to be found. If it is passed as an absolute path, it will
510 simply be returned.
510 simply be returned.
511 profile : str [default: 'default']
511 profile : str [default: 'default']
512 The name of the profile to search. Leaving this unspecified
512 The name of the profile to search. Leaving this unspecified
513 The file to be found. If it is passed as an absolute path, fname will
513 The file to be found. If it is passed as an absolute path, fname will
514 simply be returned.
514 simply be returned.
515
515
516 Returns
516 Returns
517 -------
517 -------
518 Raises :exc:`IOError` if file not found or returns absolute path to file.
518 Raises :exc:`IOError` if file not found or returns absolute path to file.
519 """
519 """
520 # import here, because profiledir also imports from utils.path
520 # import here, because profiledir also imports from utils.path
521 from IPython.core.profiledir import ProfileDir
521 from IPython.core.profiledir import ProfileDir
522 try:
522 try:
523 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
523 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
524 except Exception:
524 except Exception:
525 # will raise ProfileDirError if no such profile
525 # will raise ProfileDirError if no such profile
526 raise IOError("Profile %r not found")
526 raise IOError("Profile %r not found")
527 return filefind(filename, ['.', pd.security_dir])
527 return filefind(filename, ['.', pd.security_dir])
528
528
529
529
530 ENOLINK = 1998
530 ENOLINK = 1998
531
531
532 def link(src, dst):
532 def link(src, dst):
533 """Hard links ``src`` to ``dst``, returning 0 or errno.
533 """Hard links ``src`` to ``dst``, returning 0 or errno.
534
534
535 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
535 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
536 supported by the operating system.
536 supported by the operating system.
537 """
537 """
538
538
539 if not hasattr(os, "link"):
539 if not hasattr(os, "link"):
540 return ENOLINK
540 return ENOLINK
541 link_errno = 0
541 link_errno = 0
542 try:
542 try:
543 os.link(src, dst)
543 os.link(src, dst)
544 except OSError as e:
544 except OSError as e:
545 link_errno = e.errno
545 link_errno = e.errno
546 return link_errno
546 return link_errno
547
547
548
548
549 def link_or_copy(src, dst):
549 def link_or_copy(src, dst):
550 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
550 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
551
551
552 Attempts to maintain the semantics of ``shutil.copy``.
552 Attempts to maintain the semantics of ``shutil.copy``.
553
553
554 Because ``os.link`` does not overwrite files, a unique temporary file
554 Because ``os.link`` does not overwrite files, a unique temporary file
555 will be used if the target already exists, then that file will be moved
555 will be used if the target already exists, then that file will be moved
556 into place.
556 into place.
557 """
557 """
558
558
559 if os.path.isdir(dst):
559 if os.path.isdir(dst):
560 dst = os.path.join(dst, os.path.basename(src))
560 dst = os.path.join(dst, os.path.basename(src))
561
561
562 link_errno = link(src, dst)
562 link_errno = link(src, dst)
563 if link_errno == errno.EEXIST:
563 if link_errno == errno.EEXIST:
564 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
564 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
565 try:
565 try:
566 link_or_copy(src, new_dst)
566 link_or_copy(src, new_dst)
567 except:
567 except:
568 try:
568 try:
569 os.remove(new_dst)
569 os.remove(new_dst)
570 except OSError:
570 except OSError:
571 pass
571 pass
572 raise
572 raise
573 os.rename(new_dst, dst)
573 os.rename(new_dst, dst)
574 elif link_errno != 0:
574 elif link_errno != 0:
575 # Either link isn't supported, or the filesystem doesn't support
575 # Either link isn't supported, or the filesystem doesn't support
576 # linking, or 'src' and 'dst' are on different filesystems.
576 # linking, or 'src' and 'dst' are on different filesystems.
577 shutil.copy(src, dst)
577 shutil.copy(src, dst)
General Comments 0
You need to be logged in to leave comments. Login now