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