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