##// END OF EJS Templates
fix symlinked /home issue for FreeBSD...
Paul Ivanov -
Show More
@@ -1,461 +1,464 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 tempfile
20 20 import warnings
21 21 from hashlib import md5
22 22
23 23 import IPython
24 24 from IPython.utils.process import system
25 25 from IPython.utils.importstring import import_item
26 26 from IPython.utils import py3compat
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Code
30 30 #-----------------------------------------------------------------------------
31 31
32 32 fs_encoding = sys.getfilesystemencoding()
33 33
34 34 def _get_long_path_name(path):
35 35 """Dummy no-op."""
36 36 return path
37 37
38 38 def _writable_dir(path):
39 39 """Whether `path` is a directory, to which the user has write access."""
40 40 return os.path.isdir(path) and os.access(path, os.W_OK)
41 41
42 42 if sys.platform == 'win32':
43 43 def _get_long_path_name(path):
44 44 """Get a long path name (expand ~) on Windows using ctypes.
45 45
46 46 Examples
47 47 --------
48 48
49 49 >>> get_long_path_name('c:\\docume~1')
50 50 u'c:\\\\Documents and Settings'
51 51
52 52 """
53 53 try:
54 54 import ctypes
55 55 except ImportError:
56 56 raise ImportError('you need to have ctypes installed for this to work')
57 57 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
58 58 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
59 59 ctypes.c_uint ]
60 60
61 61 buf = ctypes.create_unicode_buffer(260)
62 62 rv = _GetLongPathName(path, buf, 260)
63 63 if rv == 0 or rv > 260:
64 64 return path
65 65 else:
66 66 return buf.value
67 67
68 68
69 69 def get_long_path_name(path):
70 70 """Expand a path into its long form.
71 71
72 72 On Windows this expands any ~ in the paths. On other platforms, it is
73 73 a null operation.
74 74 """
75 75 return _get_long_path_name(path)
76 76
77 77
78 78 def unquote_filename(name, win32=(sys.platform=='win32')):
79 79 """ On Windows, remove leading and trailing quotes from filenames.
80 80 """
81 81 if win32:
82 82 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
83 83 name = name[1:-1]
84 84 return name
85 85
86 86
87 87 def get_py_filename(name, force_win32=None):
88 88 """Return a valid python filename in the current directory.
89 89
90 90 If the given name is not a file, it adds '.py' and searches again.
91 91 Raises IOError with an informative message if the file isn't found.
92 92
93 93 On Windows, apply Windows semantics to the filename. In particular, remove
94 94 any quoting that has been applied to it. This option can be forced for
95 95 testing purposes.
96 96 """
97 97
98 98 name = os.path.expanduser(name)
99 99 if force_win32 is None:
100 100 win32 = (sys.platform == 'win32')
101 101 else:
102 102 win32 = force_win32
103 103 name = unquote_filename(name, win32=win32)
104 104 if not os.path.isfile(name) and not name.endswith('.py'):
105 105 name += '.py'
106 106 if os.path.isfile(name):
107 107 return name
108 108 else:
109 109 raise IOError,'File `%s` not found.' % name
110 110
111 111
112 112 def filefind(filename, path_dirs=None):
113 113 """Find a file by looking through a sequence of paths.
114 114
115 115 This iterates through a sequence of paths looking for a file and returns
116 116 the full, absolute path of the first occurence of the file. If no set of
117 117 path dirs is given, the filename is tested as is, after running through
118 118 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
119 119
120 120 filefind('myfile.txt')
121 121
122 122 will find the file in the current working dir, but::
123 123
124 124 filefind('~/myfile.txt')
125 125
126 126 Will find the file in the users home directory. This function does not
127 127 automatically try any paths, such as the cwd or the user's home directory.
128 128
129 129 Parameters
130 130 ----------
131 131 filename : str
132 132 The filename to look for.
133 133 path_dirs : str, None or sequence of str
134 134 The sequence of paths to look for the file in. If None, the filename
135 135 need to be absolute or be in the cwd. If a string, the string is
136 136 put into a sequence and the searched. If a sequence, walk through
137 137 each element and join with ``filename``, calling :func:`expandvars`
138 138 and :func:`expanduser` before testing for existence.
139 139
140 140 Returns
141 141 -------
142 142 Raises :exc:`IOError` or returns absolute path to file.
143 143 """
144 144
145 145 # If paths are quoted, abspath gets confused, strip them...
146 146 filename = filename.strip('"').strip("'")
147 147 # If the input is an absolute path, just check it exists
148 148 if os.path.isabs(filename) and os.path.isfile(filename):
149 149 return filename
150 150
151 151 if path_dirs is None:
152 152 path_dirs = ("",)
153 153 elif isinstance(path_dirs, basestring):
154 154 path_dirs = (path_dirs,)
155 155
156 156 for path in path_dirs:
157 157 if path == '.': path = os.getcwdu()
158 158 testname = expand_path(os.path.join(path, filename))
159 159 if os.path.isfile(testname):
160 160 return os.path.abspath(testname)
161 161
162 162 raise IOError("File %r does not exist in any of the search paths: %r" %
163 163 (filename, path_dirs) )
164 164
165 165
166 166 class HomeDirError(Exception):
167 167 pass
168 168
169 169
170 170 def get_home_dir(require_writable=False):
171 171 """Return the 'home' directory, as a unicode string.
172 172
173 173 * First, check for frozen env in case of py2exe
174 174 * Otherwise, defer to os.path.expanduser('~')
175 175
176 176 See stdlib docs for how this is determined.
177 177 $HOME is first priority on *ALL* platforms.
178 178
179 179 Parameters
180 180 ----------
181 181
182 182 require_writable : bool [default: False]
183 183 if True:
184 184 guarantees the return value is a writable directory, otherwise
185 185 raises HomeDirError
186 186 if False:
187 187 The path is resolved, but it is not guaranteed to exist or be writable.
188 188 """
189 189
190 190 # first, check py2exe distribution root directory for _ipython.
191 191 # This overrides all. Normally does not exist.
192 192
193 193 if hasattr(sys, "frozen"): #Is frozen by py2exe
194 194 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
195 195 root, rest = IPython.__file__.lower().split('library.zip')
196 196 else:
197 197 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
198 198 root=os.path.abspath(root).rstrip('\\')
199 199 if _writable_dir(os.path.join(root, '_ipython')):
200 200 os.environ["IPYKITROOT"] = root
201 201 return py3compat.cast_unicode(root, fs_encoding)
202 202
203 203 homedir = os.path.expanduser('~')
204 # Next line will make things work even when /home/ is a symlink to
205 # /usr/home as it is on FreeBSD, for example
206 homedir = os.path.realpath(homedir)
204 207
205 208 if not _writable_dir(homedir) and os.name == 'nt':
206 209 # expanduser failed, use the registry to get the 'My Documents' folder.
207 210 try:
208 211 import _winreg as wreg
209 212 key = wreg.OpenKey(
210 213 wreg.HKEY_CURRENT_USER,
211 214 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
212 215 )
213 216 homedir = wreg.QueryValueEx(key,'Personal')[0]
214 217 key.Close()
215 218 except:
216 219 pass
217 220
218 221 if (not require_writable) or _writable_dir(homedir):
219 222 return py3compat.cast_unicode(homedir, fs_encoding)
220 223 else:
221 224 raise HomeDirError('%s is not a writable dir, '
222 225 'set $HOME environment variable to override' % homedir)
223 226
224 227 def get_xdg_dir():
225 228 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
226 229
227 230 This is only for posix (Linux,Unix,OS X, etc) systems.
228 231 """
229 232
230 233 env = os.environ
231 234
232 235 if os.name == 'posix':
233 236 # Linux, Unix, AIX, OS X
234 237 # use ~/.config if empty OR not set
235 238 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
236 239 if xdg and _writable_dir(xdg):
237 240 return py3compat.cast_unicode(xdg, fs_encoding)
238 241
239 242 return None
240 243
241 244
242 245 def get_ipython_dir():
243 246 """Get the IPython directory for this platform and user.
244 247
245 248 This uses the logic in `get_home_dir` to find the home directory
246 249 and then adds .ipython to the end of the path.
247 250 """
248 251
249 252 env = os.environ
250 253 pjoin = os.path.join
251 254
252 255
253 256 ipdir_def = '.ipython'
254 257 xdg_def = 'ipython'
255 258
256 259 home_dir = get_home_dir()
257 260 xdg_dir = get_xdg_dir()
258 261
259 262 # import pdb; pdb.set_trace() # dbg
260 263 ipdir = env.get('IPYTHON_DIR', env.get('IPYTHONDIR', None))
261 264 if ipdir is None:
262 265 # not set explicitly, use XDG_CONFIG_HOME or HOME
263 266 home_ipdir = pjoin(home_dir, ipdir_def)
264 267 if xdg_dir:
265 268 # use XDG, as long as the user isn't already
266 269 # using $HOME/.ipython and *not* XDG/ipython
267 270
268 271 xdg_ipdir = pjoin(xdg_dir, xdg_def)
269 272
270 273 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
271 274 ipdir = xdg_ipdir
272 275
273 276 if ipdir is None:
274 277 # not using XDG
275 278 ipdir = home_ipdir
276 279
277 280 ipdir = os.path.normpath(os.path.expanduser(ipdir))
278 281
279 282 if os.path.exists(ipdir) and not _writable_dir(ipdir):
280 283 # ipdir exists, but is not writable
281 284 warnings.warn("IPython dir '%s' is not a writable location,"
282 285 " using a temp directory."%ipdir)
283 286 ipdir = tempfile.mkdtemp()
284 287 elif not os.path.exists(ipdir):
285 288 parent = ipdir.rsplit(os.path.sep, 1)[0]
286 289 if not _writable_dir(parent):
287 290 # ipdir does not exist and parent isn't writable
288 291 warnings.warn("IPython parent '%s' is not a writable location,"
289 292 " using a temp directory."%parent)
290 293 ipdir = tempfile.mkdtemp()
291 294
292 295 return py3compat.cast_unicode(ipdir, fs_encoding)
293 296
294 297
295 298 def get_ipython_package_dir():
296 299 """Get the base directory where IPython itself is installed."""
297 300 ipdir = os.path.dirname(IPython.__file__)
298 301 return py3compat.cast_unicode(ipdir, fs_encoding)
299 302
300 303
301 304 def get_ipython_module_path(module_str):
302 305 """Find the path to an IPython module in this version of IPython.
303 306
304 307 This will always find the version of the module that is in this importable
305 308 IPython package. This will always return the path to the ``.py``
306 309 version of the module.
307 310 """
308 311 if module_str == 'IPython':
309 312 return os.path.join(get_ipython_package_dir(), '__init__.py')
310 313 mod = import_item(module_str)
311 314 the_path = mod.__file__.replace('.pyc', '.py')
312 315 the_path = the_path.replace('.pyo', '.py')
313 316 return py3compat.cast_unicode(the_path, fs_encoding)
314 317
315 318 def locate_profile(profile='default'):
316 319 """Find the path to the folder associated with a given profile.
317 320
318 321 I.e. find $IPYTHON_DIR/profile_whatever.
319 322 """
320 323 from IPython.core.profiledir import ProfileDir, ProfileDirError
321 324 try:
322 325 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
323 326 except ProfileDirError:
324 327 # IOError makes more sense when people are expecting a path
325 328 raise IOError("Couldn't find profile %r" % profile)
326 329 return pd.location
327 330
328 331 def expand_path(s):
329 332 """Expand $VARS and ~names in a string, like a shell
330 333
331 334 :Examples:
332 335
333 336 In [2]: os.environ['FOO']='test'
334 337
335 338 In [3]: expand_path('variable FOO is $FOO')
336 339 Out[3]: 'variable FOO is test'
337 340 """
338 341 # This is a pretty subtle hack. When expand user is given a UNC path
339 342 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
340 343 # the $ to get (\\server\share\%username%). I think it considered $
341 344 # alone an empty var. But, we need the $ to remains there (it indicates
342 345 # a hidden share).
343 346 if os.name=='nt':
344 347 s = s.replace('$\\', 'IPYTHON_TEMP')
345 348 s = os.path.expandvars(os.path.expanduser(s))
346 349 if os.name=='nt':
347 350 s = s.replace('IPYTHON_TEMP', '$\\')
348 351 return s
349 352
350 353
351 354 def target_outdated(target,deps):
352 355 """Determine whether a target is out of date.
353 356
354 357 target_outdated(target,deps) -> 1/0
355 358
356 359 deps: list of filenames which MUST exist.
357 360 target: single filename which may or may not exist.
358 361
359 362 If target doesn't exist or is older than any file listed in deps, return
360 363 true, otherwise return false.
361 364 """
362 365 try:
363 366 target_time = os.path.getmtime(target)
364 367 except os.error:
365 368 return 1
366 369 for dep in deps:
367 370 dep_time = os.path.getmtime(dep)
368 371 if dep_time > target_time:
369 372 #print "For target",target,"Dep failed:",dep # dbg
370 373 #print "times (dep,tar):",dep_time,target_time # dbg
371 374 return 1
372 375 return 0
373 376
374 377
375 378 def target_update(target,deps,cmd):
376 379 """Update a target with a given command given a list of dependencies.
377 380
378 381 target_update(target,deps,cmd) -> runs cmd if target is outdated.
379 382
380 383 This is just a wrapper around target_outdated() which calls the given
381 384 command if target is outdated."""
382 385
383 386 if target_outdated(target,deps):
384 387 system(cmd)
385 388
386 389 def filehash(path):
387 390 """Make an MD5 hash of a file, ignoring any differences in line
388 391 ending characters."""
389 392 with open(path, "rU") as f:
390 393 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
391 394
392 395 # If the config is unmodified from the default, we'll just delete it.
393 396 # These are consistent for 0.10.x, thankfully. We're not going to worry about
394 397 # older versions.
395 398 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
396 399 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
397 400
398 401 def check_for_old_config(ipython_dir=None):
399 402 """Check for old config files, and present a warning if they exist.
400 403
401 404 A link to the docs of the new config is included in the message.
402 405
403 406 This should mitigate confusion with the transition to the new
404 407 config system in 0.11.
405 408 """
406 409 if ipython_dir is None:
407 410 ipython_dir = get_ipython_dir()
408 411
409 412 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
410 413 warned = False
411 414 for cfg in old_configs:
412 415 f = os.path.join(ipython_dir, cfg)
413 416 if os.path.exists(f):
414 417 if filehash(f) == old_config_md5.get(cfg, ''):
415 418 os.unlink(f)
416 419 else:
417 420 warnings.warn("Found old IPython config file %r (modified by user)"%f)
418 421 warned = True
419 422
420 423 if warned:
421 424 warnings.warn("""
422 425 The IPython configuration system has changed as of 0.11, and these files will
423 426 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
424 427 of the new config system.
425 428 To start configuring IPython, do `ipython profile create`, and edit
426 429 `ipython_config.py` in <ipython_dir>/profile_default.
427 430 If you need to leave the old config files in place for an older version of
428 431 IPython and want to suppress this warning message, set
429 432 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
430 433
431 434 def get_security_file(filename, profile='default'):
432 435 """Return the absolute path of a security file given by filename and profile
433 436
434 437 This allows users and developers to find security files without
435 438 knowledge of the IPython directory structure. The search path
436 439 will be ['.', profile.security_dir]
437 440
438 441 Parameters
439 442 ----------
440 443
441 444 filename : str
442 445 The file to be found. If it is passed as an absolute path, it will
443 446 simply be returned.
444 447 profile : str [default: 'default']
445 448 The name of the profile to search. Leaving this unspecified
446 449 The file to be found. If it is passed as an absolute path, fname will
447 450 simply be returned.
448 451
449 452 Returns
450 453 -------
451 454 Raises :exc:`IOError` if file not found or returns absolute path to file.
452 455 """
453 456 # import here, because profiledir also imports from utils.path
454 457 from IPython.core.profiledir import ProfileDir
455 458 try:
456 459 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
457 460 except Exception:
458 461 # will raise ProfileDirError if no such profile
459 462 raise IOError("Profile %r not found")
460 463 return filefind(filename, ['.', pd.security_dir])
461 464
General Comments 0
You need to be logged in to leave comments. Login now