##// END OF EJS Templates
add get_security_file() function to utils.path...
MinRK -
Show More
@@ -1,478 +1,509 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-2009 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():
171 171 """Return the closest possible equivalent to a 'home' directory.
172 172
173 173 * On POSIX, we try $HOME.
174 174 * On Windows we try:
175 175 - %HOMESHARE%
176 176 - %HOMEDRIVE\%HOMEPATH%
177 177 - %USERPROFILE%
178 178 - Registry hack for My Documents
179 179 - %HOME%: rare, but some people with unix-like setups may have defined it
180 180 * On Dos C:\
181 181
182 182 Currently only Posix and NT are implemented, a HomeDirError exception is
183 183 raised for all other OSes.
184 184 """
185 185
186 186 env = os.environ
187 187
188 188 # first, check py2exe distribution root directory for _ipython.
189 189 # This overrides all. Normally does not exist.
190 190
191 191 if hasattr(sys, "frozen"): #Is frozen by py2exe
192 192 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
193 193 root, rest = IPython.__file__.lower().split('library.zip')
194 194 else:
195 195 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
196 196 root=os.path.abspath(root).rstrip('\\')
197 197 if _writable_dir(os.path.join(root, '_ipython')):
198 198 os.environ["IPYKITROOT"] = root
199 199 return py3compat.cast_unicode(root, fs_encoding)
200 200
201 201 if os.name == 'posix':
202 202 # Linux, Unix, AIX, OS X
203 203 try:
204 204 homedir = env['HOME']
205 205 except KeyError:
206 206 # Last-ditch attempt at finding a suitable $HOME, on systems where
207 207 # it may not be defined in the environment but the system shell
208 208 # still knows it - reported once as:
209 209 # https://github.com/ipython/ipython/issues/154
210 210 from subprocess import Popen, PIPE
211 211 homedir = Popen('echo $HOME', shell=True,
212 212 stdout=PIPE).communicate()[0].strip()
213 213 if homedir:
214 214 return py3compat.cast_unicode(homedir, fs_encoding)
215 215 else:
216 216 raise HomeDirError('Undefined $HOME, IPython cannot proceed.')
217 217 else:
218 218 return py3compat.cast_unicode(homedir, fs_encoding)
219 219 elif os.name == 'nt':
220 220 # Now for win9x, XP, Vista, 7?
221 221 # For some strange reason all of these return 'nt' for os.name.
222 222 # First look for a network home directory. This will return the UNC
223 223 # path (\\server\\Users\%username%) not the mapped path (Z:\). This
224 224 # is needed when running IPython on cluster where all paths have to
225 225 # be UNC.
226 226 try:
227 227 homedir = env['HOMESHARE']
228 228 except KeyError:
229 229 pass
230 230 else:
231 231 if _writable_dir(homedir):
232 232 return py3compat.cast_unicode(homedir, fs_encoding)
233 233
234 234 # Now look for a local home directory
235 235 try:
236 236 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
237 237 except KeyError:
238 238 pass
239 239 else:
240 240 if _writable_dir(homedir):
241 241 return py3compat.cast_unicode(homedir, fs_encoding)
242 242
243 243 # Now the users profile directory
244 244 try:
245 245 homedir = os.path.join(env['USERPROFILE'])
246 246 except KeyError:
247 247 pass
248 248 else:
249 249 if _writable_dir(homedir):
250 250 return py3compat.cast_unicode(homedir, fs_encoding)
251 251
252 252 # Use the registry to get the 'My Documents' folder.
253 253 try:
254 254 import _winreg as wreg
255 255 key = wreg.OpenKey(
256 256 wreg.HKEY_CURRENT_USER,
257 257 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
258 258 )
259 259 homedir = wreg.QueryValueEx(key,'Personal')[0]
260 260 key.Close()
261 261 except:
262 262 pass
263 263 else:
264 264 if _writable_dir(homedir):
265 265 return py3compat.cast_unicode(homedir, fs_encoding)
266 266
267 267 # A user with a lot of unix tools in win32 may have defined $HOME.
268 268 # Try this as a last ditch option.
269 269 try:
270 270 homedir = env['HOME']
271 271 except KeyError:
272 272 pass
273 273 else:
274 274 if _writable_dir(homedir):
275 275 return py3compat.cast_unicode(homedir, fs_encoding)
276 276
277 277 # If all else fails, raise HomeDirError
278 278 raise HomeDirError('No valid home directory could be found')
279 279 elif os.name == 'dos':
280 280 # Desperate, may do absurd things in classic MacOS. May work under DOS.
281 281 return u'C:\\'
282 282 else:
283 283 raise HomeDirError('No valid home directory could be found for your OS')
284 284
285 285 def get_xdg_dir():
286 286 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
287 287
288 288 This is only for posix (Linux,Unix,OS X, etc) systems.
289 289 """
290 290
291 291 env = os.environ
292 292
293 293 if os.name == 'posix':
294 294 # Linux, Unix, AIX, OS X
295 295 # use ~/.config if not set OR empty
296 296 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
297 297 if xdg and _writable_dir(xdg):
298 298 return py3compat.cast_unicode(xdg, fs_encoding)
299 299
300 300 return None
301 301
302 302
303 303 def get_ipython_dir():
304 304 """Get the IPython directory for this platform and user.
305 305
306 306 This uses the logic in `get_home_dir` to find the home directory
307 307 and then adds .ipython to the end of the path.
308 308 """
309 309
310 310 env = os.environ
311 311 pjoin = os.path.join
312 312
313 313
314 314 ipdir_def = '.ipython'
315 315 xdg_def = 'ipython'
316 316
317 317 home_dir = get_home_dir()
318 318 xdg_dir = get_xdg_dir()
319 319 # import pdb; pdb.set_trace() # dbg
320 320 ipdir = env.get('IPYTHON_DIR', env.get('IPYTHONDIR', None))
321 321 if ipdir is None:
322 322 # not set explicitly, use XDG_CONFIG_HOME or HOME
323 323 home_ipdir = pjoin(home_dir, ipdir_def)
324 324 if xdg_dir:
325 325 # use XDG, as long as the user isn't already
326 326 # using $HOME/.ipython and *not* XDG/ipython
327 327
328 328 xdg_ipdir = pjoin(xdg_dir, xdg_def)
329 329
330 330 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
331 331 ipdir = xdg_ipdir
332 332
333 333 if ipdir is None:
334 334 # not using XDG
335 335 ipdir = home_ipdir
336 336
337 337 ipdir = os.path.normpath(os.path.expanduser(ipdir))
338 338
339 339 if os.path.exists(ipdir) and not _writable_dir(ipdir):
340 340 # ipdir exists, but is not writable
341 341 warnings.warn("IPython dir '%s' is not a writable location,"
342 342 " using a temp directory."%ipdir)
343 343 ipdir = tempfile.mkdtemp()
344 344 elif not os.path.exists(ipdir):
345 345 parent = ipdir.rsplit(os.path.sep, 1)[0]
346 346 if not _writable_dir(parent):
347 347 # ipdir does not exist and parent isn't writable
348 348 warnings.warn("IPython parent '%s' is not a writable location,"
349 349 " using a temp directory."%parent)
350 350 ipdir = tempfile.mkdtemp()
351 351
352 352 return py3compat.cast_unicode(ipdir, fs_encoding)
353 353
354 354
355 355 def get_ipython_package_dir():
356 356 """Get the base directory where IPython itself is installed."""
357 357 ipdir = os.path.dirname(IPython.__file__)
358 358 return py3compat.cast_unicode(ipdir, fs_encoding)
359 359
360 360
361 361 def get_ipython_module_path(module_str):
362 362 """Find the path to an IPython module in this version of IPython.
363 363
364 364 This will always find the version of the module that is in this importable
365 365 IPython package. This will always return the path to the ``.py``
366 366 version of the module.
367 367 """
368 368 if module_str == 'IPython':
369 369 return os.path.join(get_ipython_package_dir(), '__init__.py')
370 370 mod = import_item(module_str)
371 371 the_path = mod.__file__.replace('.pyc', '.py')
372 372 the_path = the_path.replace('.pyo', '.py')
373 373 return py3compat.cast_unicode(the_path, fs_encoding)
374 374
375 375
376 376 def expand_path(s):
377 377 """Expand $VARS and ~names in a string, like a shell
378 378
379 379 :Examples:
380 380
381 381 In [2]: os.environ['FOO']='test'
382 382
383 383 In [3]: expand_path('variable FOO is $FOO')
384 384 Out[3]: 'variable FOO is test'
385 385 """
386 386 # This is a pretty subtle hack. When expand user is given a UNC path
387 387 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
388 388 # the $ to get (\\server\share\%username%). I think it considered $
389 389 # alone an empty var. But, we need the $ to remains there (it indicates
390 390 # a hidden share).
391 391 if os.name=='nt':
392 392 s = s.replace('$\\', 'IPYTHON_TEMP')
393 393 s = os.path.expandvars(os.path.expanduser(s))
394 394 if os.name=='nt':
395 395 s = s.replace('IPYTHON_TEMP', '$\\')
396 396 return s
397 397
398 398
399 399 def target_outdated(target,deps):
400 400 """Determine whether a target is out of date.
401 401
402 402 target_outdated(target,deps) -> 1/0
403 403
404 404 deps: list of filenames which MUST exist.
405 405 target: single filename which may or may not exist.
406 406
407 407 If target doesn't exist or is older than any file listed in deps, return
408 408 true, otherwise return false.
409 409 """
410 410 try:
411 411 target_time = os.path.getmtime(target)
412 412 except os.error:
413 413 return 1
414 414 for dep in deps:
415 415 dep_time = os.path.getmtime(dep)
416 416 if dep_time > target_time:
417 417 #print "For target",target,"Dep failed:",dep # dbg
418 418 #print "times (dep,tar):",dep_time,target_time # dbg
419 419 return 1
420 420 return 0
421 421
422 422
423 423 def target_update(target,deps,cmd):
424 424 """Update a target with a given command given a list of dependencies.
425 425
426 426 target_update(target,deps,cmd) -> runs cmd if target is outdated.
427 427
428 428 This is just a wrapper around target_outdated() which calls the given
429 429 command if target is outdated."""
430 430
431 431 if target_outdated(target,deps):
432 432 system(cmd)
433 433
434 434 def filehash(path):
435 435 """Make an MD5 hash of a file, ignoring any differences in line
436 436 ending characters."""
437 437 with open(path, "rU") as f:
438 438 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
439 439
440 440 # If the config is unmodified from the default, we'll just delete it.
441 441 # These are consistent for 0.10.x, thankfully. We're not going to worry about
442 442 # older versions.
443 443 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
444 444 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
445 445
446 446 def check_for_old_config(ipython_dir=None):
447 447 """Check for old config files, and present a warning if they exist.
448 448
449 449 A link to the docs of the new config is included in the message.
450 450
451 451 This should mitigate confusion with the transition to the new
452 452 config system in 0.11.
453 453 """
454 454 if ipython_dir is None:
455 455 ipython_dir = get_ipython_dir()
456 456
457 457 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
458 458 warned = False
459 459 for cfg in old_configs:
460 460 f = os.path.join(ipython_dir, cfg)
461 461 if os.path.exists(f):
462 462 if filehash(f) == old_config_md5.get(cfg, ''):
463 463 os.unlink(f)
464 464 else:
465 465 warnings.warn("Found old IPython config file %r (modified by user)"%f)
466 466 warned = True
467 467
468 468 if warned:
469 469 warnings.warn("""
470 470 The IPython configuration system has changed as of 0.11, and these files will
471 471 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
472 472 of the new config system.
473 473 To start configuring IPython, do `ipython profile create`, and edit
474 474 `ipython_config.py` in <ipython_dir>/profile_default.
475 475 If you need to leave the old config files in place for an older version of
476 476 IPython and want to suppress this warning message, set
477 477 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
478 478
479 def get_security_file(filename, profile='default'):
480 """Return the absolute path of a security file given by filename and profile
481
482 This allows users and developers to find security files without
483 knowledge of the IPython directory structure. The search path
484 will be ['.', profile.security_dir]
485
486 Parameters
487 ----------
488
489 filename : str
490 The file to be found. If it is passed as an absolute path, it will
491 simply be returned.
492 profile : str [default: 'default']
493 The name of the profile to search. Leaving this unspecified
494 The file to be found. If it is passed as an absolute path, fname will
495 simply be returned.
496
497 Returns
498 -------
499 Raises :exc:`IOError` if file not found or returns absolute path to file.
500 """
501 # import here, because profiledir also imports from utils.path
502 from IPython.core.profiledir import ProfileDir
503 try:
504 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
505 except Exception:
506 # will raise ProfileDirError if no such profile
507 raise IOError("Profile %r not found")
508 return filefind(filename, ['.', pd.security_dir])
509
General Comments 0
You need to be logged in to leave comments. Login now