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