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