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