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