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