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