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