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