Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/utils/path.py: 19%

158 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1# encoding: utf-8 

2""" 

3Utilities for path handling. 

4""" 

5 

6# Copyright (c) IPython Development Team. 

7# Distributed under the terms of the Modified BSD License. 

8 

9import os 

10import sys 

11import errno 

12import shutil 

13import random 

14import glob 

15 

16from IPython.utils.process import system 

17 

18#----------------------------------------------------------------------------- 

19# Code 

20#----------------------------------------------------------------------------- 

21fs_encoding = sys.getfilesystemencoding() 

22 

23def _writable_dir(path): 

24 """Whether `path` is a directory, to which the user has write access.""" 

25 return os.path.isdir(path) and os.access(path, os.W_OK) 

26 

27if sys.platform == 'win32': 

28 def _get_long_path_name(path): 

29 """Get a long path name (expand ~) on Windows using ctypes. 

30 

31 Examples 

32 -------- 

33 

34 >>> get_long_path_name('c:\\\\docume~1') 

35 'c:\\\\Documents and Settings' 

36 

37 """ 

38 try: 

39 import ctypes 

40 except ImportError as e: 

41 raise ImportError('you need to have ctypes installed for this to work') from e 

42 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW 

43 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, 

44 ctypes.c_uint ] 

45 

46 buf = ctypes.create_unicode_buffer(260) 

47 rv = _GetLongPathName(path, buf, 260) 

48 if rv == 0 or rv > 260: 

49 return path 

50 else: 

51 return buf.value 

52else: 

53 def _get_long_path_name(path): 

54 """Dummy no-op.""" 

55 return path 

56 

57 

58 

59def get_long_path_name(path): 

60 """Expand a path into its long form. 

61 

62 On Windows this expands any ~ in the paths. On other platforms, it is 

63 a null operation. 

64 """ 

65 return _get_long_path_name(path) 

66 

67 

68def compress_user(path): 

69 """Reverse of :func:`os.path.expanduser` 

70 """ 

71 home = os.path.expanduser('~') 

72 if path.startswith(home): 

73 path = "~" + path[len(home):] 

74 return path 

75 

76def get_py_filename(name): 

77 """Return a valid python filename in the current directory. 

78 

79 If the given name is not a file, it adds '.py' and searches again. 

80 Raises IOError with an informative message if the file isn't found. 

81 """ 

82 

83 name = os.path.expanduser(name) 

84 if os.path.isfile(name): 

85 return name 

86 if not name.endswith(".py"): 

87 py_name = name + ".py" 

88 if os.path.isfile(py_name): 

89 return py_name 

90 raise IOError("File `%r` not found." % name) 

91 

92 

93def filefind(filename: str, path_dirs=None) -> str: 

94 """Find a file by looking through a sequence of paths. 

95 

96 This iterates through a sequence of paths looking for a file and returns 

97 the full, absolute path of the first occurrence of the file. If no set of 

98 path dirs is given, the filename is tested as is, after running through 

99 :func:`expandvars` and :func:`expanduser`. Thus a simple call:: 

100 

101 filefind('myfile.txt') 

102 

103 will find the file in the current working dir, but:: 

104 

105 filefind('~/myfile.txt') 

106 

107 Will find the file in the users home directory. This function does not 

108 automatically try any paths, such as the cwd or the user's home directory. 

109 

110 Parameters 

111 ---------- 

112 filename : str 

113 The filename to look for. 

114 path_dirs : str, None or sequence of str 

115 The sequence of paths to look for the file in. If None, the filename 

116 need to be absolute or be in the cwd. If a string, the string is 

117 put into a sequence and the searched. If a sequence, walk through 

118 each element and join with ``filename``, calling :func:`expandvars` 

119 and :func:`expanduser` before testing for existence. 

120 

121 Returns 

122 ------- 

123 path : str 

124 returns absolute path to file. 

125 

126 Raises 

127 ------ 

128 IOError 

129 """ 

130 

131 # If paths are quoted, abspath gets confused, strip them... 

132 filename = filename.strip('"').strip("'") 

133 # If the input is an absolute path, just check it exists 

134 if os.path.isabs(filename) and os.path.isfile(filename): 

135 return filename 

136 

137 if path_dirs is None: 

138 path_dirs = ("",) 

139 elif isinstance(path_dirs, str): 

140 path_dirs = (path_dirs,) 

141 

142 for path in path_dirs: 

143 if path == '.': path = os.getcwd() 

144 testname = expand_path(os.path.join(path, filename)) 

145 if os.path.isfile(testname): 

146 return os.path.abspath(testname) 

147 

148 raise IOError("File %r does not exist in any of the search paths: %r" % 

149 (filename, path_dirs) ) 

150 

151 

152class HomeDirError(Exception): 

153 pass 

154 

155 

156def get_home_dir(require_writable=False) -> str: 

157 """Return the 'home' directory, as a unicode string. 

158 

159 Uses os.path.expanduser('~'), and checks for writability. 

160 

161 See stdlib docs for how this is determined. 

162 For Python <3.8, $HOME is first priority on *ALL* platforms. 

163 For Python >=3.8 on Windows, %HOME% is no longer considered. 

164 

165 Parameters 

166 ---------- 

167 require_writable : bool [default: False] 

168 if True: 

169 guarantees the return value is a writable directory, otherwise 

170 raises HomeDirError 

171 if False: 

172 The path is resolved, but it is not guaranteed to exist or be writable. 

173 """ 

174 

175 homedir = os.path.expanduser('~') 

176 # Next line will make things work even when /home/ is a symlink to 

177 # /usr/home as it is on FreeBSD, for example 

178 homedir = os.path.realpath(homedir) 

179 

180 if not _writable_dir(homedir) and os.name == 'nt': 

181 # expanduser failed, use the registry to get the 'My Documents' folder. 

182 try: 

183 import winreg as wreg 

184 with wreg.OpenKey( 

185 wreg.HKEY_CURRENT_USER, 

186 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" 

187 ) as key: 

188 homedir = wreg.QueryValueEx(key,'Personal')[0] 

189 except: 

190 pass 

191 

192 if (not require_writable) or _writable_dir(homedir): 

193 assert isinstance(homedir, str), "Homedir should be unicode not bytes" 

194 return homedir 

195 else: 

196 raise HomeDirError('%s is not a writable dir, ' 

197 'set $HOME environment variable to override' % homedir) 

198 

199def get_xdg_dir(): 

200 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. 

201 

202 This is only for non-OS X posix (Linux,Unix,etc.) systems. 

203 """ 

204 

205 env = os.environ 

206 

207 if os.name == "posix": 

208 # Linux, Unix, AIX, etc. 

209 # use ~/.config if empty OR not set 

210 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') 

211 if xdg and _writable_dir(xdg): 

212 assert isinstance(xdg, str) 

213 return xdg 

214 

215 return None 

216 

217 

218def get_xdg_cache_dir(): 

219 """Return the XDG_CACHE_HOME, if it is defined and exists, else None. 

220 

221 This is only for non-OS X posix (Linux,Unix,etc.) systems. 

222 """ 

223 

224 env = os.environ 

225 

226 if os.name == "posix": 

227 # Linux, Unix, AIX, etc. 

228 # use ~/.cache if empty OR not set 

229 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache') 

230 if xdg and _writable_dir(xdg): 

231 assert isinstance(xdg, str) 

232 return xdg 

233 

234 return None 

235 

236 

237def expand_path(s): 

238 """Expand $VARS and ~names in a string, like a shell 

239 

240 :Examples: 

241 

242 In [2]: os.environ['FOO']='test' 

243 

244 In [3]: expand_path('variable FOO is $FOO') 

245 Out[3]: 'variable FOO is test' 

246 """ 

247 # This is a pretty subtle hack. When expand user is given a UNC path 

248 # on Windows (\\server\share$\%username%), os.path.expandvars, removes 

249 # the $ to get (\\server\share\%username%). I think it considered $ 

250 # alone an empty var. But, we need the $ to remains there (it indicates 

251 # a hidden share). 

252 if os.name=='nt': 

253 s = s.replace('$\\', 'IPYTHON_TEMP') 

254 s = os.path.expandvars(os.path.expanduser(s)) 

255 if os.name=='nt': 

256 s = s.replace('IPYTHON_TEMP', '$\\') 

257 return s 

258 

259 

260def unescape_glob(string): 

261 """Unescape glob pattern in `string`.""" 

262 def unescape(s): 

263 for pattern in '*[]!?': 

264 s = s.replace(r'\{0}'.format(pattern), pattern) 

265 return s 

266 return '\\'.join(map(unescape, string.split('\\\\'))) 

267 

268 

269def shellglob(args): 

270 """ 

271 Do glob expansion for each element in `args` and return a flattened list. 

272 

273 Unmatched glob pattern will remain as-is in the returned list. 

274 

275 """ 

276 expanded = [] 

277 # Do not unescape backslash in Windows as it is interpreted as 

278 # path separator: 

279 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x 

280 for a in args: 

281 expanded.extend(glob.glob(a) or [unescape(a)]) 

282 return expanded 

283 

284 

285def target_outdated(target,deps): 

286 """Determine whether a target is out of date. 

287 

288 target_outdated(target,deps) -> 1/0 

289 

290 deps: list of filenames which MUST exist. 

291 target: single filename which may or may not exist. 

292 

293 If target doesn't exist or is older than any file listed in deps, return 

294 true, otherwise return false. 

295 """ 

296 try: 

297 target_time = os.path.getmtime(target) 

298 except os.error: 

299 return 1 

300 for dep in deps: 

301 dep_time = os.path.getmtime(dep) 

302 if dep_time > target_time: 

303 #print "For target",target,"Dep failed:",dep # dbg 

304 #print "times (dep,tar):",dep_time,target_time # dbg 

305 return 1 

306 return 0 

307 

308 

309def target_update(target,deps,cmd): 

310 """Update a target with a given command given a list of dependencies. 

311 

312 target_update(target,deps,cmd) -> runs cmd if target is outdated. 

313 

314 This is just a wrapper around target_outdated() which calls the given 

315 command if target is outdated.""" 

316 

317 if target_outdated(target,deps): 

318 system(cmd) 

319 

320 

321ENOLINK = 1998 

322 

323def link(src, dst): 

324 """Hard links ``src`` to ``dst``, returning 0 or errno. 

325 

326 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't 

327 supported by the operating system. 

328 """ 

329 

330 if not hasattr(os, "link"): 

331 return ENOLINK 

332 link_errno = 0 

333 try: 

334 os.link(src, dst) 

335 except OSError as e: 

336 link_errno = e.errno 

337 return link_errno 

338 

339 

340def link_or_copy(src, dst): 

341 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails. 

342 

343 Attempts to maintain the semantics of ``shutil.copy``. 

344 

345 Because ``os.link`` does not overwrite files, a unique temporary file 

346 will be used if the target already exists, then that file will be moved 

347 into place. 

348 """ 

349 

350 if os.path.isdir(dst): 

351 dst = os.path.join(dst, os.path.basename(src)) 

352 

353 link_errno = link(src, dst) 

354 if link_errno == errno.EEXIST: 

355 if os.stat(src).st_ino == os.stat(dst).st_ino: 

356 # dst is already a hard link to the correct file, so we don't need 

357 # to do anything else. If we try to link and rename the file 

358 # anyway, we get duplicate files - see http://bugs.python.org/issue21876 

359 return 

360 

361 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), ) 

362 try: 

363 link_or_copy(src, new_dst) 

364 except: 

365 try: 

366 os.remove(new_dst) 

367 except OSError: 

368 pass 

369 raise 

370 os.rename(new_dst, dst) 

371 elif link_errno != 0: 

372 # Either link isn't supported, or the filesystem doesn't support 

373 # linking, or 'src' and 'dst' are on different filesystems. 

374 shutil.copy(src, dst) 

375 

376def ensure_dir_exists(path, mode=0o755): 

377 """ensure that a directory exists 

378 

379 If it doesn't exist, try to create it and protect against a race condition 

380 if another process is doing the same. 

381 

382 The default permissions are 755, which differ from os.makedirs default of 777. 

383 """ 

384 if not os.path.exists(path): 

385 try: 

386 os.makedirs(path, mode=mode) 

387 except OSError as e: 

388 if e.errno != errno.EEXIST: 

389 raise 

390 elif not os.path.isdir(path): 

391 raise IOError("%r exists but is not a directory" % path)