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