##// END OF EJS Templates
Strange wondows regression
Matthias Bussonnier -
Show More
@@ -1,436 +1,437 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import os
10 10 import sys
11 11 import errno
12 12 import shutil
13 13 import random
14 14 import glob
15 15 from warnings import warn
16 16
17 17 from IPython.utils.process import system
18 18 from IPython.utils import py3compat
19 19 from IPython.utils.decorators import undoc
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Code
23 23 #-----------------------------------------------------------------------------
24 24
25 25 fs_encoding = sys.getfilesystemencoding()
26 26
27 27 def _writable_dir(path):
28 28 """Whether `path` is a directory, to which the user has write access."""
29 29 return os.path.isdir(path) and os.access(path, os.W_OK)
30 30
31 31 if sys.platform == 'win32':
32 32 def _get_long_path_name(path):
33 33 """Get a long path name (expand ~) on Windows using ctypes.
34 34
35 35 Examples
36 36 --------
37 37
38 38 >>> get_long_path_name('c:\\docume~1')
39 39 'c:\\\\Documents and Settings'
40 40
41 41 """
42 42 try:
43 43 import ctypes
44 44 except ImportError:
45 45 raise ImportError('you need to have ctypes installed for this to work')
46 46 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
47 47 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
48 48 ctypes.c_uint ]
49 49
50 50 buf = ctypes.create_unicode_buffer(260)
51 51 rv = _GetLongPathName(path, buf, 260)
52 52 if rv == 0 or rv > 260:
53 53 return path
54 54 else:
55 55 return buf.value
56 56 else:
57 57 def _get_long_path_name(path):
58 58 """Dummy no-op."""
59 59 return path
60 60
61 61
62 62
63 63 def get_long_path_name(path):
64 64 """Expand a path into its long form.
65 65
66 66 On Windows this expands any ~ in the paths. On other platforms, it is
67 67 a null operation.
68 68 """
69 69 return _get_long_path_name(path)
70 70
71 71
72 72 def unquote_filename(name, win32=(sys.platform=='win32')):
73 73 """ On Windows, remove leading and trailing quotes from filenames.
74 74
75 75 This function has been deprecated and should not be used any more:
76 76 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
77 77 """
78 78 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
79 79 "be used anymore", DeprecationWarning, stacklevel=2)
80 80 if win32:
81 81 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
82 82 name = name[1:-1]
83 83 return name
84 84
85 85
86 86 def compress_user(path):
87 87 """Reverse of :func:`os.path.expanduser`
88 88 """
89 89 home = os.path.expanduser('~')
90 90 if path.startswith(home):
91 91 path = "~" + path[len(home):]
92 92 return path
93 93
94 94 def get_py_filename(name, force_win32=None):
95 95 """Return a valid python filename in the current directory.
96 96
97 97 If the given name is not a file, it adds '.py' and searches again.
98 98 Raises IOError with an informative message if the file isn't found.
99 99 """
100 100
101 101 name = os.path.expanduser(name)
102 102 if force_win32 is not None:
103 103 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
104 104 "since IPython 5.0 and should not be used anymore",
105 105 DeprecationWarning, stacklevel=2)
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 occurrence 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, str):
156 156 path_dirs = (path_dirs,)
157 157
158 158 for path in path_dirs:
159 159 if path == '.': path = os.getcwd()
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) -> str:
173 173 """Return the 'home' directory, as a unicode string.
174 174
175 175 Uses os.path.expanduser('~'), and checks for writability.
176 176
177 177 See stdlib docs for how this is determined.
178 178 For Python <3.8, $HOME is first priority on *ALL* platforms.
179 179 For Python >=3.8 on Windows, %HOME% is no longer considered.
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 homedir = os.path.expanduser('~')
193 193 # Next line will make things work even when /home/ is a symlink to
194 194 # /usr/home as it is on FreeBSD, for example
195 195 homedir = os.path.realpath(homedir)
196 196
197 197 if not _writable_dir(homedir) and os.name == 'nt':
198 198 # expanduser failed, use the registry to get the 'My Documents' folder.
199 199 try:
200 200 import winreg as wreg
201 with wreg.OpenKey(
201 key = wreg.OpenKey(
202 202 wreg.HKEY_CURRENT_USER,
203 203 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
204 ) as key:
205 homedir = wreg.QueryValueEx(key,'Personal')[0]
204 )
205 homedir = wreg.QueryValueEx(key,'Personal')[0]
206 key.Close()
206 207 except:
207 208 pass
208 209
209 210 if (not require_writable) or _writable_dir(homedir):
210 211 assert isinstance(homedir, str), "Homedir shoudl be unicode not bytes"
211 212 return homedir
212 213 else:
213 214 raise HomeDirError('%s is not a writable dir, '
214 215 'set $HOME environment variable to override' % homedir)
215 216
216 217 def get_xdg_dir():
217 218 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
218 219
219 220 This is only for non-OS X posix (Linux,Unix,etc.) systems.
220 221 """
221 222
222 223 env = os.environ
223 224
224 225 if os.name == 'posix' and sys.platform != 'darwin':
225 226 # Linux, Unix, AIX, etc.
226 227 # use ~/.config if empty OR not set
227 228 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
228 229 if xdg and _writable_dir(xdg):
229 230 return py3compat.cast_unicode(xdg, fs_encoding)
230 231
231 232 return None
232 233
233 234
234 235 def get_xdg_cache_dir():
235 236 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
236 237
237 238 This is only for non-OS X posix (Linux,Unix,etc.) systems.
238 239 """
239 240
240 241 env = os.environ
241 242
242 243 if os.name == 'posix' and sys.platform != 'darwin':
243 244 # Linux, Unix, AIX, etc.
244 245 # use ~/.cache if empty OR not set
245 246 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
246 247 if xdg and _writable_dir(xdg):
247 248 return py3compat.cast_unicode(xdg, fs_encoding)
248 249
249 250 return None
250 251
251 252
252 253 @undoc
253 254 def get_ipython_dir():
254 255 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
255 256 from IPython.paths import get_ipython_dir
256 257 return get_ipython_dir()
257 258
258 259 @undoc
259 260 def get_ipython_cache_dir():
260 261 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
261 262 from IPython.paths import get_ipython_cache_dir
262 263 return get_ipython_cache_dir()
263 264
264 265 @undoc
265 266 def get_ipython_package_dir():
266 267 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
267 268 from IPython.paths import get_ipython_package_dir
268 269 return get_ipython_package_dir()
269 270
270 271 @undoc
271 272 def get_ipython_module_path(module_str):
272 273 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
273 274 from IPython.paths import get_ipython_module_path
274 275 return get_ipython_module_path(module_str)
275 276
276 277 @undoc
277 278 def locate_profile(profile='default'):
278 279 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2)
279 280 from IPython.paths import locate_profile
280 281 return locate_profile(profile=profile)
281 282
282 283 def expand_path(s):
283 284 """Expand $VARS and ~names in a string, like a shell
284 285
285 286 :Examples:
286 287
287 288 In [2]: os.environ['FOO']='test'
288 289
289 290 In [3]: expand_path('variable FOO is $FOO')
290 291 Out[3]: 'variable FOO is test'
291 292 """
292 293 # This is a pretty subtle hack. When expand user is given a UNC path
293 294 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
294 295 # the $ to get (\\server\share\%username%). I think it considered $
295 296 # alone an empty var. But, we need the $ to remains there (it indicates
296 297 # a hidden share).
297 298 if os.name=='nt':
298 299 s = s.replace('$\\', 'IPYTHON_TEMP')
299 300 s = os.path.expandvars(os.path.expanduser(s))
300 301 if os.name=='nt':
301 302 s = s.replace('IPYTHON_TEMP', '$\\')
302 303 return s
303 304
304 305
305 306 def unescape_glob(string):
306 307 """Unescape glob pattern in `string`."""
307 308 def unescape(s):
308 309 for pattern in '*[]!?':
309 310 s = s.replace(r'\{0}'.format(pattern), pattern)
310 311 return s
311 312 return '\\'.join(map(unescape, string.split('\\\\')))
312 313
313 314
314 315 def shellglob(args):
315 316 """
316 317 Do glob expansion for each element in `args` and return a flattened list.
317 318
318 319 Unmatched glob pattern will remain as-is in the returned list.
319 320
320 321 """
321 322 expanded = []
322 323 # Do not unescape backslash in Windows as it is interpreted as
323 324 # path separator:
324 325 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
325 326 for a in args:
326 327 expanded.extend(glob.glob(a) or [unescape(a)])
327 328 return expanded
328 329
329 330
330 331 def target_outdated(target,deps):
331 332 """Determine whether a target is out of date.
332 333
333 334 target_outdated(target,deps) -> 1/0
334 335
335 336 deps: list of filenames which MUST exist.
336 337 target: single filename which may or may not exist.
337 338
338 339 If target doesn't exist or is older than any file listed in deps, return
339 340 true, otherwise return false.
340 341 """
341 342 try:
342 343 target_time = os.path.getmtime(target)
343 344 except os.error:
344 345 return 1
345 346 for dep in deps:
346 347 dep_time = os.path.getmtime(dep)
347 348 if dep_time > target_time:
348 349 #print "For target",target,"Dep failed:",dep # dbg
349 350 #print "times (dep,tar):",dep_time,target_time # dbg
350 351 return 1
351 352 return 0
352 353
353 354
354 355 def target_update(target,deps,cmd):
355 356 """Update a target with a given command given a list of dependencies.
356 357
357 358 target_update(target,deps,cmd) -> runs cmd if target is outdated.
358 359
359 360 This is just a wrapper around target_outdated() which calls the given
360 361 command if target is outdated."""
361 362
362 363 if target_outdated(target,deps):
363 364 system(cmd)
364 365
365 366
366 367 ENOLINK = 1998
367 368
368 369 def link(src, dst):
369 370 """Hard links ``src`` to ``dst``, returning 0 or errno.
370 371
371 372 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
372 373 supported by the operating system.
373 374 """
374 375
375 376 if not hasattr(os, "link"):
376 377 return ENOLINK
377 378 link_errno = 0
378 379 try:
379 380 os.link(src, dst)
380 381 except OSError as e:
381 382 link_errno = e.errno
382 383 return link_errno
383 384
384 385
385 386 def link_or_copy(src, dst):
386 387 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
387 388
388 389 Attempts to maintain the semantics of ``shutil.copy``.
389 390
390 391 Because ``os.link`` does not overwrite files, a unique temporary file
391 392 will be used if the target already exists, then that file will be moved
392 393 into place.
393 394 """
394 395
395 396 if os.path.isdir(dst):
396 397 dst = os.path.join(dst, os.path.basename(src))
397 398
398 399 link_errno = link(src, dst)
399 400 if link_errno == errno.EEXIST:
400 401 if os.stat(src).st_ino == os.stat(dst).st_ino:
401 402 # dst is already a hard link to the correct file, so we don't need
402 403 # to do anything else. If we try to link and rename the file
403 404 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
404 405 return
405 406
406 407 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
407 408 try:
408 409 link_or_copy(src, new_dst)
409 410 except:
410 411 try:
411 412 os.remove(new_dst)
412 413 except OSError:
413 414 pass
414 415 raise
415 416 os.rename(new_dst, dst)
416 417 elif link_errno != 0:
417 418 # Either link isn't supported, or the filesystem doesn't support
418 419 # linking, or 'src' and 'dst' are on different filesystems.
419 420 shutil.copy(src, dst)
420 421
421 422 def ensure_dir_exists(path, mode=0o755):
422 423 """ensure that a directory exists
423 424
424 425 If it doesn't exist, try to create it and protect against a race condition
425 426 if another process is doing the same.
426 427
427 428 The default permissions are 755, which differ from os.makedirs default of 777.
428 429 """
429 430 if not os.path.exists(path):
430 431 try:
431 432 os.makedirs(path, mode=mode)
432 433 except OSError as e:
433 434 if e.errno != errno.EEXIST:
434 435 raise
435 436 elif not os.path.isdir(path):
436 437 raise IOError("%r exists but is not a directory" % path)
General Comments 0
You need to be logged in to leave comments. Login now