##// END OF EJS Templates
Switch default IPython directory back to ~/.ipython...
Thomas Kluyver -
Show More
@@ -1,574 +1,574 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 = os.getcwdu()
169 if path == '.': path = os.getcwdu()
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 xdg_def = 'ipython'
277
276
278 home_dir = get_home_dir()
277 home_dir = get_home_dir()
279 xdg_dir = get_xdg_dir()
278 xdg_dir = get_xdg_dir()
280
279
281 # import pdb; pdb.set_trace() # dbg
280 # import pdb; pdb.set_trace() # dbg
282 if 'IPYTHON_DIR' in env:
281 if 'IPYTHON_DIR' in env:
283 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
282 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
284 'Please use IPYTHONDIR instead.')
283 'Please use IPYTHONDIR instead.')
285 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
284 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
286 if ipdir is None:
285 if ipdir is None:
287 # not set explicitly, use XDG_CONFIG_HOME or HOME
286 # not set explicitly, use ~/.ipython
288 home_ipdir = pjoin(home_dir, ipdir_def)
287 ipdir = pjoin(home_dir, ipdir_def)
289 if xdg_dir:
288 if xdg_dir:
290 # use XDG, as long as the user isn't already
289 # Several IPython versions (up to 1.x) defaulted to .config/ipython
291 # using $HOME/.ipython and *not* XDG/ipython
290 # on Linux. We have decided to go back to using .ipython everywhere
292
291 xdg_ipdir = pjoin(xdg_dir, 'ipython')
293 xdg_ipdir = pjoin(xdg_dir, xdg_def)
292
294
293 if _writable_dir(xdg_ipdir):
295 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
294 cu = compress_user
296 ipdir = xdg_ipdir
295 if os.path.exists(ipdir):
297
296 warnings.warn(('Ignoring {0} in favour of {1}. Remove {0} '
298 if ipdir is None:
297 'to get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
299 # not using XDG
298 else:
300 ipdir = home_ipdir
299 warnings.warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
300 os.rename(xdg_ipdir, ipdir)
301
301
302 ipdir = os.path.normpath(os.path.expanduser(ipdir))
302 ipdir = os.path.normpath(os.path.expanduser(ipdir))
303
303
304 if os.path.exists(ipdir) and not _writable_dir(ipdir):
304 if os.path.exists(ipdir) and not _writable_dir(ipdir):
305 # ipdir exists, but is not writable
305 # ipdir exists, but is not writable
306 warnings.warn("IPython dir '%s' is not a writable location,"
306 warnings.warn("IPython dir '%s' is not a writable location,"
307 " using a temp directory."%ipdir)
307 " using a temp directory."%ipdir)
308 ipdir = tempfile.mkdtemp()
308 ipdir = tempfile.mkdtemp()
309 elif not os.path.exists(ipdir):
309 elif not os.path.exists(ipdir):
310 parent = os.path.dirname(ipdir)
310 parent = os.path.dirname(ipdir)
311 if not _writable_dir(parent):
311 if not _writable_dir(parent):
312 # ipdir does not exist and parent isn't writable
312 # ipdir does not exist and parent isn't writable
313 warnings.warn("IPython parent '%s' is not a writable location,"
313 warnings.warn("IPython parent '%s' is not a writable location,"
314 " using a temp directory."%parent)
314 " using a temp directory."%parent)
315 ipdir = tempfile.mkdtemp()
315 ipdir = tempfile.mkdtemp()
316
316
317 return py3compat.cast_unicode(ipdir, fs_encoding)
317 return py3compat.cast_unicode(ipdir, fs_encoding)
318
318
319
319
320 def get_ipython_cache_dir():
320 def get_ipython_cache_dir():
321 """Get the cache directory it is created if it does not exist."""
321 """Get the cache directory it is created if it does not exist."""
322 xdgdir = get_xdg_cache_dir()
322 xdgdir = get_xdg_cache_dir()
323 if xdgdir is None:
323 if xdgdir is None:
324 return get_ipython_dir()
324 return get_ipython_dir()
325 ipdir = os.path.join(xdgdir, "ipython")
325 ipdir = os.path.join(xdgdir, "ipython")
326 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
326 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
327 os.makedirs(ipdir)
327 os.makedirs(ipdir)
328 elif not _writable_dir(xdgdir):
328 elif not _writable_dir(xdgdir):
329 return get_ipython_dir()
329 return get_ipython_dir()
330
330
331 return py3compat.cast_unicode(ipdir, fs_encoding)
331 return py3compat.cast_unicode(ipdir, fs_encoding)
332
332
333
333
334 def get_ipython_package_dir():
334 def get_ipython_package_dir():
335 """Get the base directory where IPython itself is installed."""
335 """Get the base directory where IPython itself is installed."""
336 ipdir = os.path.dirname(IPython.__file__)
336 ipdir = os.path.dirname(IPython.__file__)
337 return py3compat.cast_unicode(ipdir, fs_encoding)
337 return py3compat.cast_unicode(ipdir, fs_encoding)
338
338
339
339
340 def get_ipython_module_path(module_str):
340 def get_ipython_module_path(module_str):
341 """Find the path to an IPython module in this version of IPython.
341 """Find the path to an IPython module in this version of IPython.
342
342
343 This will always find the version of the module that is in this importable
343 This will always find the version of the module that is in this importable
344 IPython package. This will always return the path to the ``.py``
344 IPython package. This will always return the path to the ``.py``
345 version of the module.
345 version of the module.
346 """
346 """
347 if module_str == 'IPython':
347 if module_str == 'IPython':
348 return os.path.join(get_ipython_package_dir(), '__init__.py')
348 return os.path.join(get_ipython_package_dir(), '__init__.py')
349 mod = import_item(module_str)
349 mod = import_item(module_str)
350 the_path = mod.__file__.replace('.pyc', '.py')
350 the_path = mod.__file__.replace('.pyc', '.py')
351 the_path = the_path.replace('.pyo', '.py')
351 the_path = the_path.replace('.pyo', '.py')
352 return py3compat.cast_unicode(the_path, fs_encoding)
352 return py3compat.cast_unicode(the_path, fs_encoding)
353
353
354 def locate_profile(profile='default'):
354 def locate_profile(profile='default'):
355 """Find the path to the folder associated with a given profile.
355 """Find the path to the folder associated with a given profile.
356
356
357 I.e. find $IPYTHONDIR/profile_whatever.
357 I.e. find $IPYTHONDIR/profile_whatever.
358 """
358 """
359 from IPython.core.profiledir import ProfileDir, ProfileDirError
359 from IPython.core.profiledir import ProfileDir, ProfileDirError
360 try:
360 try:
361 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
361 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
362 except ProfileDirError:
362 except ProfileDirError:
363 # IOError makes more sense when people are expecting a path
363 # IOError makes more sense when people are expecting a path
364 raise IOError("Couldn't find profile %r" % profile)
364 raise IOError("Couldn't find profile %r" % profile)
365 return pd.location
365 return pd.location
366
366
367 def expand_path(s):
367 def expand_path(s):
368 """Expand $VARS and ~names in a string, like a shell
368 """Expand $VARS and ~names in a string, like a shell
369
369
370 :Examples:
370 :Examples:
371
371
372 In [2]: os.environ['FOO']='test'
372 In [2]: os.environ['FOO']='test'
373
373
374 In [3]: expand_path('variable FOO is $FOO')
374 In [3]: expand_path('variable FOO is $FOO')
375 Out[3]: 'variable FOO is test'
375 Out[3]: 'variable FOO is test'
376 """
376 """
377 # This is a pretty subtle hack. When expand user is given a UNC path
377 # This is a pretty subtle hack. When expand user is given a UNC path
378 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
378 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
379 # the $ to get (\\server\share\%username%). I think it considered $
379 # the $ to get (\\server\share\%username%). I think it considered $
380 # alone an empty var. But, we need the $ to remains there (it indicates
380 # alone an empty var. But, we need the $ to remains there (it indicates
381 # a hidden share).
381 # a hidden share).
382 if os.name=='nt':
382 if os.name=='nt':
383 s = s.replace('$\\', 'IPYTHON_TEMP')
383 s = s.replace('$\\', 'IPYTHON_TEMP')
384 s = os.path.expandvars(os.path.expanduser(s))
384 s = os.path.expandvars(os.path.expanduser(s))
385 if os.name=='nt':
385 if os.name=='nt':
386 s = s.replace('IPYTHON_TEMP', '$\\')
386 s = s.replace('IPYTHON_TEMP', '$\\')
387 return s
387 return s
388
388
389
389
390 def unescape_glob(string):
390 def unescape_glob(string):
391 """Unescape glob pattern in `string`."""
391 """Unescape glob pattern in `string`."""
392 def unescape(s):
392 def unescape(s):
393 for pattern in '*[]!?':
393 for pattern in '*[]!?':
394 s = s.replace(r'\{0}'.format(pattern), pattern)
394 s = s.replace(r'\{0}'.format(pattern), pattern)
395 return s
395 return s
396 return '\\'.join(map(unescape, string.split('\\\\')))
396 return '\\'.join(map(unescape, string.split('\\\\')))
397
397
398
398
399 def shellglob(args):
399 def shellglob(args):
400 """
400 """
401 Do glob expansion for each element in `args` and return a flattened list.
401 Do glob expansion for each element in `args` and return a flattened list.
402
402
403 Unmatched glob pattern will remain as-is in the returned list.
403 Unmatched glob pattern will remain as-is in the returned list.
404
404
405 """
405 """
406 expanded = []
406 expanded = []
407 # Do not unescape backslash in Windows as it is interpreted as
407 # Do not unescape backslash in Windows as it is interpreted as
408 # path separator:
408 # path separator:
409 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
409 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
410 for a in args:
410 for a in args:
411 expanded.extend(glob.glob(a) or [unescape(a)])
411 expanded.extend(glob.glob(a) or [unescape(a)])
412 return expanded
412 return expanded
413
413
414
414
415 def target_outdated(target,deps):
415 def target_outdated(target,deps):
416 """Determine whether a target is out of date.
416 """Determine whether a target is out of date.
417
417
418 target_outdated(target,deps) -> 1/0
418 target_outdated(target,deps) -> 1/0
419
419
420 deps: list of filenames which MUST exist.
420 deps: list of filenames which MUST exist.
421 target: single filename which may or may not exist.
421 target: single filename which may or may not exist.
422
422
423 If target doesn't exist or is older than any file listed in deps, return
423 If target doesn't exist or is older than any file listed in deps, return
424 true, otherwise return false.
424 true, otherwise return false.
425 """
425 """
426 try:
426 try:
427 target_time = os.path.getmtime(target)
427 target_time = os.path.getmtime(target)
428 except os.error:
428 except os.error:
429 return 1
429 return 1
430 for dep in deps:
430 for dep in deps:
431 dep_time = os.path.getmtime(dep)
431 dep_time = os.path.getmtime(dep)
432 if dep_time > target_time:
432 if dep_time > target_time:
433 #print "For target",target,"Dep failed:",dep # dbg
433 #print "For target",target,"Dep failed:",dep # dbg
434 #print "times (dep,tar):",dep_time,target_time # dbg
434 #print "times (dep,tar):",dep_time,target_time # dbg
435 return 1
435 return 1
436 return 0
436 return 0
437
437
438
438
439 def target_update(target,deps,cmd):
439 def target_update(target,deps,cmd):
440 """Update a target with a given command given a list of dependencies.
440 """Update a target with a given command given a list of dependencies.
441
441
442 target_update(target,deps,cmd) -> runs cmd if target is outdated.
442 target_update(target,deps,cmd) -> runs cmd if target is outdated.
443
443
444 This is just a wrapper around target_outdated() which calls the given
444 This is just a wrapper around target_outdated() which calls the given
445 command if target is outdated."""
445 command if target is outdated."""
446
446
447 if target_outdated(target,deps):
447 if target_outdated(target,deps):
448 system(cmd)
448 system(cmd)
449
449
450 def filehash(path):
450 def filehash(path):
451 """Make an MD5 hash of a file, ignoring any differences in line
451 """Make an MD5 hash of a file, ignoring any differences in line
452 ending characters."""
452 ending characters."""
453 with open(path, "rU") as f:
453 with open(path, "rU") as f:
454 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
454 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
455
455
456 # If the config is unmodified from the default, we'll just delete it.
456 # If the config is unmodified from the default, we'll just delete it.
457 # These are consistent for 0.10.x, thankfully. We're not going to worry about
457 # These are consistent for 0.10.x, thankfully. We're not going to worry about
458 # older versions.
458 # older versions.
459 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
459 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
460 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
460 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
461
461
462 def check_for_old_config(ipython_dir=None):
462 def check_for_old_config(ipython_dir=None):
463 """Check for old config files, and present a warning if they exist.
463 """Check for old config files, and present a warning if they exist.
464
464
465 A link to the docs of the new config is included in the message.
465 A link to the docs of the new config is included in the message.
466
466
467 This should mitigate confusion with the transition to the new
467 This should mitigate confusion with the transition to the new
468 config system in 0.11.
468 config system in 0.11.
469 """
469 """
470 if ipython_dir is None:
470 if ipython_dir is None:
471 ipython_dir = get_ipython_dir()
471 ipython_dir = get_ipython_dir()
472
472
473 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
473 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
474 warned = False
474 warned = False
475 for cfg in old_configs:
475 for cfg in old_configs:
476 f = os.path.join(ipython_dir, cfg)
476 f = os.path.join(ipython_dir, cfg)
477 if os.path.exists(f):
477 if os.path.exists(f):
478 if filehash(f) == old_config_md5.get(cfg, ''):
478 if filehash(f) == old_config_md5.get(cfg, ''):
479 os.unlink(f)
479 os.unlink(f)
480 else:
480 else:
481 warnings.warn("Found old IPython config file %r (modified by user)"%f)
481 warnings.warn("Found old IPython config file %r (modified by user)"%f)
482 warned = True
482 warned = True
483
483
484 if warned:
484 if warned:
485 warnings.warn("""
485 warnings.warn("""
486 The IPython configuration system has changed as of 0.11, and these files will
486 The IPython configuration system has changed as of 0.11, and these files will
487 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
487 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
488 of the new config system.
488 of the new config system.
489 To start configuring IPython, do `ipython profile create`, and edit
489 To start configuring IPython, do `ipython profile create`, and edit
490 `ipython_config.py` in <ipython_dir>/profile_default.
490 `ipython_config.py` in <ipython_dir>/profile_default.
491 If you need to leave the old config files in place for an older version of
491 If you need to leave the old config files in place for an older version of
492 IPython and want to suppress this warning message, set
492 IPython and want to suppress this warning message, set
493 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
493 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
494
494
495 def get_security_file(filename, profile='default'):
495 def get_security_file(filename, profile='default'):
496 """Return the absolute path of a security file given by filename and profile
496 """Return the absolute path of a security file given by filename and profile
497
497
498 This allows users and developers to find security files without
498 This allows users and developers to find security files without
499 knowledge of the IPython directory structure. The search path
499 knowledge of the IPython directory structure. The search path
500 will be ['.', profile.security_dir]
500 will be ['.', profile.security_dir]
501
501
502 Parameters
502 Parameters
503 ----------
503 ----------
504
504
505 filename : str
505 filename : str
506 The file to be found. If it is passed as an absolute path, it will
506 The file to be found. If it is passed as an absolute path, it will
507 simply be returned.
507 simply be returned.
508 profile : str [default: 'default']
508 profile : str [default: 'default']
509 The name of the profile to search. Leaving this unspecified
509 The name of the profile to search. Leaving this unspecified
510 The file to be found. If it is passed as an absolute path, fname will
510 The file to be found. If it is passed as an absolute path, fname will
511 simply be returned.
511 simply be returned.
512
512
513 Returns
513 Returns
514 -------
514 -------
515 Raises :exc:`IOError` if file not found or returns absolute path to file.
515 Raises :exc:`IOError` if file not found or returns absolute path to file.
516 """
516 """
517 # import here, because profiledir also imports from utils.path
517 # import here, because profiledir also imports from utils.path
518 from IPython.core.profiledir import ProfileDir
518 from IPython.core.profiledir import ProfileDir
519 try:
519 try:
520 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
520 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
521 except Exception:
521 except Exception:
522 # will raise ProfileDirError if no such profile
522 # will raise ProfileDirError if no such profile
523 raise IOError("Profile %r not found")
523 raise IOError("Profile %r not found")
524 return filefind(filename, ['.', pd.security_dir])
524 return filefind(filename, ['.', pd.security_dir])
525
525
526
526
527 ENOLINK = 1998
527 ENOLINK = 1998
528
528
529 def link(src, dst):
529 def link(src, dst):
530 """Hard links ``src`` to ``dst``, returning 0 or errno.
530 """Hard links ``src`` to ``dst``, returning 0 or errno.
531
531
532 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
532 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
533 supported by the operating system.
533 supported by the operating system.
534 """
534 """
535
535
536 if not hasattr(os, "link"):
536 if not hasattr(os, "link"):
537 return ENOLINK
537 return ENOLINK
538 link_errno = 0
538 link_errno = 0
539 try:
539 try:
540 os.link(src, dst)
540 os.link(src, dst)
541 except OSError as e:
541 except OSError as e:
542 link_errno = e.errno
542 link_errno = e.errno
543 return link_errno
543 return link_errno
544
544
545
545
546 def link_or_copy(src, dst):
546 def link_or_copy(src, dst):
547 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
547 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
548
548
549 Attempts to maintain the semantics of ``shutil.copy``.
549 Attempts to maintain the semantics of ``shutil.copy``.
550
550
551 Because ``os.link`` does not overwrite files, a unique temporary file
551 Because ``os.link`` does not overwrite files, a unique temporary file
552 will be used if the target already exists, then that file will be moved
552 will be used if the target already exists, then that file will be moved
553 into place.
553 into place.
554 """
554 """
555
555
556 if os.path.isdir(dst):
556 if os.path.isdir(dst):
557 dst = os.path.join(dst, os.path.basename(src))
557 dst = os.path.join(dst, os.path.basename(src))
558
558
559 link_errno = link(src, dst)
559 link_errno = link(src, dst)
560 if link_errno == errno.EEXIST:
560 if link_errno == errno.EEXIST:
561 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
561 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
562 try:
562 try:
563 link_or_copy(src, new_dst)
563 link_or_copy(src, new_dst)
564 except:
564 except:
565 try:
565 try:
566 os.remove(new_dst)
566 os.remove(new_dst)
567 except OSError:
567 except OSError:
568 pass
568 pass
569 raise
569 raise
570 os.rename(new_dst, dst)
570 os.rename(new_dst, dst)
571 elif link_errno != 0:
571 elif link_errno != 0:
572 # Either link isn't supported, or the filesystem doesn't support
572 # Either link isn't supported, or the filesystem doesn't support
573 # linking, or 'src' and 'dst' are on different filesystems.
573 # linking, or 'src' and 'dst' are on different filesystems.
574 shutil.copy(src, dst)
574 shutil.copy(src, dst)
General Comments 0
You need to be logged in to leave comments. Login now