##// END OF EJS Templates
remove unicode specifier
Srinivas Reddy Thatiparthy -
Show More
@@ -1,446 +1,446 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 from hashlib import md5
16 from hashlib import md5
17
17
18 from IPython.utils.process import system
18 from IPython.utils.process import system
19 from IPython.utils import py3compat
19 from IPython.utils import py3compat
20 from IPython.utils.decorators import undoc
20 from IPython.utils.decorators import undoc
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Code
23 # Code
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 fs_encoding = sys.getfilesystemencoding()
26 fs_encoding = sys.getfilesystemencoding()
27
27
28 def _writable_dir(path):
28 def _writable_dir(path):
29 """Whether `path` is a directory, to which the user has write access."""
29 """Whether `path` is a directory, to which the user has write access."""
30 return os.path.isdir(path) and os.access(path, os.W_OK)
30 return os.path.isdir(path) and os.access(path, os.W_OK)
31
31
32 if sys.platform == 'win32':
32 if sys.platform == 'win32':
33 def _get_long_path_name(path):
33 def _get_long_path_name(path):
34 """Get a long path name (expand ~) on Windows using ctypes.
34 """Get a long path name (expand ~) on Windows using ctypes.
35
35
36 Examples
36 Examples
37 --------
37 --------
38
38
39 >>> get_long_path_name('c:\\docume~1')
39 >>> get_long_path_name('c:\\docume~1')
40 u'c:\\\\Documents and Settings'
40 'c:\\\\Documents and Settings'
41
41
42 """
42 """
43 try:
43 try:
44 import ctypes
44 import ctypes
45 except ImportError:
45 except ImportError:
46 raise ImportError('you need to have ctypes installed for this to work')
46 raise ImportError('you need to have ctypes installed for this to work')
47 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
47 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
48 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
48 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
49 ctypes.c_uint ]
49 ctypes.c_uint ]
50
50
51 buf = ctypes.create_unicode_buffer(260)
51 buf = ctypes.create_unicode_buffer(260)
52 rv = _GetLongPathName(path, buf, 260)
52 rv = _GetLongPathName(path, buf, 260)
53 if rv == 0 or rv > 260:
53 if rv == 0 or rv > 260:
54 return path
54 return path
55 else:
55 else:
56 return buf.value
56 return buf.value
57 else:
57 else:
58 def _get_long_path_name(path):
58 def _get_long_path_name(path):
59 """Dummy no-op."""
59 """Dummy no-op."""
60 return path
60 return path
61
61
62
62
63
63
64 def get_long_path_name(path):
64 def get_long_path_name(path):
65 """Expand a path into its long form.
65 """Expand a path into its long form.
66
66
67 On Windows this expands any ~ in the paths. On other platforms, it is
67 On Windows this expands any ~ in the paths. On other platforms, it is
68 a null operation.
68 a null operation.
69 """
69 """
70 return _get_long_path_name(path)
70 return _get_long_path_name(path)
71
71
72
72
73 def unquote_filename(name, win32=(sys.platform=='win32')):
73 def unquote_filename(name, win32=(sys.platform=='win32')):
74 """ On Windows, remove leading and trailing quotes from filenames.
74 """ On Windows, remove leading and trailing quotes from filenames.
75
75
76 This function has been deprecated and should not be used any more:
76 This function has been deprecated and should not be used any more:
77 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
77 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
78 """
78 """
79 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
79 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
80 "be used anymore", DeprecationWarning, stacklevel=2)
80 "be used anymore", DeprecationWarning, stacklevel=2)
81 if win32:
81 if win32:
82 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
82 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
83 name = name[1:-1]
83 name = name[1:-1]
84 return name
84 return name
85
85
86
86
87 def compress_user(path):
87 def compress_user(path):
88 """Reverse of :func:`os.path.expanduser`
88 """Reverse of :func:`os.path.expanduser`
89 """
89 """
90 home = os.path.expanduser('~')
90 home = os.path.expanduser('~')
91 if path.startswith(home):
91 if path.startswith(home):
92 path = "~" + path[len(home):]
92 path = "~" + path[len(home):]
93 return path
93 return path
94
94
95 def get_py_filename(name, force_win32=None):
95 def get_py_filename(name, force_win32=None):
96 """Return a valid python filename in the current directory.
96 """Return a valid python filename in the current directory.
97
97
98 If the given name is not a file, it adds '.py' and searches again.
98 If the given name is not a file, it adds '.py' and searches again.
99 Raises IOError with an informative message if the file isn't found.
99 Raises IOError with an informative message if the file isn't found.
100 """
100 """
101
101
102 name = os.path.expanduser(name)
102 name = os.path.expanduser(name)
103 if force_win32 is not None:
103 if force_win32 is not None:
104 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
104 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
105 "since IPython 5.0 and should not be used anymore",
105 "since IPython 5.0 and should not be used anymore",
106 DeprecationWarning, stacklevel=2)
106 DeprecationWarning, stacklevel=2)
107 if not os.path.isfile(name) and not name.endswith('.py'):
107 if not os.path.isfile(name) and not name.endswith('.py'):
108 name += '.py'
108 name += '.py'
109 if os.path.isfile(name):
109 if os.path.isfile(name):
110 return name
110 return name
111 else:
111 else:
112 raise IOError('File `%r` not found.' % name)
112 raise IOError('File `%r` not found.' % name)
113
113
114
114
115 def filefind(filename, path_dirs=None):
115 def filefind(filename, path_dirs=None):
116 """Find a file by looking through a sequence of paths.
116 """Find a file by looking through a sequence of paths.
117
117
118 This iterates through a sequence of paths looking for a file and returns
118 This iterates through a sequence of paths looking for a file and returns
119 the full, absolute path of the first occurence of the file. If no set of
119 the full, absolute path of the first occurence of the file. If no set of
120 path dirs is given, the filename is tested as is, after running through
120 path dirs is given, the filename is tested as is, after running through
121 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
121 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
122
122
123 filefind('myfile.txt')
123 filefind('myfile.txt')
124
124
125 will find the file in the current working dir, but::
125 will find the file in the current working dir, but::
126
126
127 filefind('~/myfile.txt')
127 filefind('~/myfile.txt')
128
128
129 Will find the file in the users home directory. This function does not
129 Will find the file in the users home directory. This function does not
130 automatically try any paths, such as the cwd or the user's home directory.
130 automatically try any paths, such as the cwd or the user's home directory.
131
131
132 Parameters
132 Parameters
133 ----------
133 ----------
134 filename : str
134 filename : str
135 The filename to look for.
135 The filename to look for.
136 path_dirs : str, None or sequence of str
136 path_dirs : str, None or sequence of str
137 The sequence of paths to look for the file in. If None, the filename
137 The sequence of paths to look for the file in. If None, the filename
138 need to be absolute or be in the cwd. If a string, the string is
138 need to be absolute or be in the cwd. If a string, the string is
139 put into a sequence and the searched. If a sequence, walk through
139 put into a sequence and the searched. If a sequence, walk through
140 each element and join with ``filename``, calling :func:`expandvars`
140 each element and join with ``filename``, calling :func:`expandvars`
141 and :func:`expanduser` before testing for existence.
141 and :func:`expanduser` before testing for existence.
142
142
143 Returns
143 Returns
144 -------
144 -------
145 Raises :exc:`IOError` or returns absolute path to file.
145 Raises :exc:`IOError` or returns absolute path to file.
146 """
146 """
147
147
148 # If paths are quoted, abspath gets confused, strip them...
148 # If paths are quoted, abspath gets confused, strip them...
149 filename = filename.strip('"').strip("'")
149 filename = filename.strip('"').strip("'")
150 # If the input is an absolute path, just check it exists
150 # If the input is an absolute path, just check it exists
151 if os.path.isabs(filename) and os.path.isfile(filename):
151 if os.path.isabs(filename) and os.path.isfile(filename):
152 return filename
152 return filename
153
153
154 if path_dirs is None:
154 if path_dirs is None:
155 path_dirs = ("",)
155 path_dirs = ("",)
156 elif isinstance(path_dirs, str):
156 elif isinstance(path_dirs, str):
157 path_dirs = (path_dirs,)
157 path_dirs = (path_dirs,)
158
158
159 for path in path_dirs:
159 for path in path_dirs:
160 if path == '.': path = os.getcwd()
160 if path == '.': path = os.getcwd()
161 testname = expand_path(os.path.join(path, filename))
161 testname = expand_path(os.path.join(path, filename))
162 if os.path.isfile(testname):
162 if os.path.isfile(testname):
163 return os.path.abspath(testname)
163 return os.path.abspath(testname)
164
164
165 raise IOError("File %r does not exist in any of the search paths: %r" %
165 raise IOError("File %r does not exist in any of the search paths: %r" %
166 (filename, path_dirs) )
166 (filename, path_dirs) )
167
167
168
168
169 class HomeDirError(Exception):
169 class HomeDirError(Exception):
170 pass
170 pass
171
171
172
172
173 def get_home_dir(require_writable=False):
173 def get_home_dir(require_writable=False):
174 """Return the 'home' directory, as a unicode string.
174 """Return the 'home' directory, as a unicode string.
175
175
176 Uses os.path.expanduser('~'), and checks for writability.
176 Uses os.path.expanduser('~'), and checks for writability.
177
177
178 See stdlib docs for how this is determined.
178 See stdlib docs for how this is determined.
179 $HOME is first priority on *ALL* platforms.
179 $HOME is first priority on *ALL* platforms.
180
180
181 Parameters
181 Parameters
182 ----------
182 ----------
183
183
184 require_writable : bool [default: False]
184 require_writable : bool [default: False]
185 if True:
185 if True:
186 guarantees the return value is a writable directory, otherwise
186 guarantees the return value is a writable directory, otherwise
187 raises HomeDirError
187 raises HomeDirError
188 if False:
188 if False:
189 The path is resolved, but it is not guaranteed to exist or be writable.
189 The path is resolved, but it is not guaranteed to exist or be writable.
190 """
190 """
191
191
192 homedir = os.path.expanduser('~')
192 homedir = os.path.expanduser('~')
193 # Next line will make things work even when /home/ is a symlink to
193 # Next line will make things work even when /home/ is a symlink to
194 # /usr/home as it is on FreeBSD, for example
194 # /usr/home as it is on FreeBSD, for example
195 homedir = os.path.realpath(homedir)
195 homedir = os.path.realpath(homedir)
196
196
197 if not _writable_dir(homedir) and os.name == 'nt':
197 if not _writable_dir(homedir) and os.name == 'nt':
198 # expanduser failed, use the registry to get the 'My Documents' folder.
198 # expanduser failed, use the registry to get the 'My Documents' folder.
199 try:
199 try:
200 try:
200 try:
201 import winreg as wreg # Py 3
201 import winreg as wreg # Py 3
202 except ImportError:
202 except ImportError:
203 import _winreg as wreg # Py 2
203 import _winreg as wreg # Py 2
204 key = wreg.OpenKey(
204 key = wreg.OpenKey(
205 wreg.HKEY_CURRENT_USER,
205 wreg.HKEY_CURRENT_USER,
206 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
206 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
207 )
207 )
208 homedir = wreg.QueryValueEx(key,'Personal')[0]
208 homedir = wreg.QueryValueEx(key,'Personal')[0]
209 key.Close()
209 key.Close()
210 except:
210 except:
211 pass
211 pass
212
212
213 if (not require_writable) or _writable_dir(homedir):
213 if (not require_writable) or _writable_dir(homedir):
214 return py3compat.cast_unicode(homedir, fs_encoding)
214 return py3compat.cast_unicode(homedir, fs_encoding)
215 else:
215 else:
216 raise HomeDirError('%s is not a writable dir, '
216 raise HomeDirError('%s is not a writable dir, '
217 'set $HOME environment variable to override' % homedir)
217 'set $HOME environment variable to override' % homedir)
218
218
219 def get_xdg_dir():
219 def get_xdg_dir():
220 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
220 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
221
221
222 This is only for non-OS X posix (Linux,Unix,etc.) systems.
222 This is only for non-OS X posix (Linux,Unix,etc.) systems.
223 """
223 """
224
224
225 env = os.environ
225 env = os.environ
226
226
227 if os.name == 'posix' and sys.platform != 'darwin':
227 if os.name == 'posix' and sys.platform != 'darwin':
228 # Linux, Unix, AIX, etc.
228 # Linux, Unix, AIX, etc.
229 # use ~/.config if empty OR not set
229 # use ~/.config if empty OR not set
230 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
230 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
231 if xdg and _writable_dir(xdg):
231 if xdg and _writable_dir(xdg):
232 return py3compat.cast_unicode(xdg, fs_encoding)
232 return py3compat.cast_unicode(xdg, fs_encoding)
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 return py3compat.cast_unicode(xdg, fs_encoding)
250 return py3compat.cast_unicode(xdg, fs_encoding)
251
251
252 return None
252 return None
253
253
254
254
255 @undoc
255 @undoc
256 def get_ipython_dir():
256 def get_ipython_dir():
257 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
257 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
258 from IPython.paths import get_ipython_dir
258 from IPython.paths import get_ipython_dir
259 return get_ipython_dir()
259 return get_ipython_dir()
260
260
261 @undoc
261 @undoc
262 def get_ipython_cache_dir():
262 def get_ipython_cache_dir():
263 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
263 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
264 from IPython.paths import get_ipython_cache_dir
264 from IPython.paths import get_ipython_cache_dir
265 return get_ipython_cache_dir()
265 return get_ipython_cache_dir()
266
266
267 @undoc
267 @undoc
268 def get_ipython_package_dir():
268 def get_ipython_package_dir():
269 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
269 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
270 from IPython.paths import get_ipython_package_dir
270 from IPython.paths import get_ipython_package_dir
271 return get_ipython_package_dir()
271 return get_ipython_package_dir()
272
272
273 @undoc
273 @undoc
274 def get_ipython_module_path(module_str):
274 def get_ipython_module_path(module_str):
275 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
275 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
276 from IPython.paths import get_ipython_module_path
276 from IPython.paths import get_ipython_module_path
277 return get_ipython_module_path(module_str)
277 return get_ipython_module_path(module_str)
278
278
279 @undoc
279 @undoc
280 def locate_profile(profile='default'):
280 def locate_profile(profile='default'):
281 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
281 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
282 from IPython.paths import locate_profile
282 from IPython.paths import locate_profile
283 return locate_profile(profile=profile)
283 return locate_profile(profile=profile)
284
284
285 def expand_path(s):
285 def expand_path(s):
286 """Expand $VARS and ~names in a string, like a shell
286 """Expand $VARS and ~names in a string, like a shell
287
287
288 :Examples:
288 :Examples:
289
289
290 In [2]: os.environ['FOO']='test'
290 In [2]: os.environ['FOO']='test'
291
291
292 In [3]: expand_path('variable FOO is $FOO')
292 In [3]: expand_path('variable FOO is $FOO')
293 Out[3]: 'variable FOO is test'
293 Out[3]: 'variable FOO is test'
294 """
294 """
295 # This is a pretty subtle hack. When expand user is given a UNC path
295 # This is a pretty subtle hack. When expand user is given a UNC path
296 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
296 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
297 # the $ to get (\\server\share\%username%). I think it considered $
297 # the $ to get (\\server\share\%username%). I think it considered $
298 # alone an empty var. But, we need the $ to remains there (it indicates
298 # alone an empty var. But, we need the $ to remains there (it indicates
299 # a hidden share).
299 # a hidden share).
300 if os.name=='nt':
300 if os.name=='nt':
301 s = s.replace('$\\', 'IPYTHON_TEMP')
301 s = s.replace('$\\', 'IPYTHON_TEMP')
302 s = os.path.expandvars(os.path.expanduser(s))
302 s = os.path.expandvars(os.path.expanduser(s))
303 if os.name=='nt':
303 if os.name=='nt':
304 s = s.replace('IPYTHON_TEMP', '$\\')
304 s = s.replace('IPYTHON_TEMP', '$\\')
305 return s
305 return s
306
306
307
307
308 def unescape_glob(string):
308 def unescape_glob(string):
309 """Unescape glob pattern in `string`."""
309 """Unescape glob pattern in `string`."""
310 def unescape(s):
310 def unescape(s):
311 for pattern in '*[]!?':
311 for pattern in '*[]!?':
312 s = s.replace(r'\{0}'.format(pattern), pattern)
312 s = s.replace(r'\{0}'.format(pattern), pattern)
313 return s
313 return s
314 return '\\'.join(map(unescape, string.split('\\\\')))
314 return '\\'.join(map(unescape, string.split('\\\\')))
315
315
316
316
317 def shellglob(args):
317 def shellglob(args):
318 """
318 """
319 Do glob expansion for each element in `args` and return a flattened list.
319 Do glob expansion for each element in `args` and return a flattened list.
320
320
321 Unmatched glob pattern will remain as-is in the returned list.
321 Unmatched glob pattern will remain as-is in the returned list.
322
322
323 """
323 """
324 expanded = []
324 expanded = []
325 # Do not unescape backslash in Windows as it is interpreted as
325 # Do not unescape backslash in Windows as it is interpreted as
326 # path separator:
326 # path separator:
327 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
327 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
328 for a in args:
328 for a in args:
329 expanded.extend(glob.glob(a) or [unescape(a)])
329 expanded.extend(glob.glob(a) or [unescape(a)])
330 return expanded
330 return expanded
331
331
332
332
333 def target_outdated(target,deps):
333 def target_outdated(target,deps):
334 """Determine whether a target is out of date.
334 """Determine whether a target is out of date.
335
335
336 target_outdated(target,deps) -> 1/0
336 target_outdated(target,deps) -> 1/0
337
337
338 deps: list of filenames which MUST exist.
338 deps: list of filenames which MUST exist.
339 target: single filename which may or may not exist.
339 target: single filename which may or may not exist.
340
340
341 If target doesn't exist or is older than any file listed in deps, return
341 If target doesn't exist or is older than any file listed in deps, return
342 true, otherwise return false.
342 true, otherwise return false.
343 """
343 """
344 try:
344 try:
345 target_time = os.path.getmtime(target)
345 target_time = os.path.getmtime(target)
346 except os.error:
346 except os.error:
347 return 1
347 return 1
348 for dep in deps:
348 for dep in deps:
349 dep_time = os.path.getmtime(dep)
349 dep_time = os.path.getmtime(dep)
350 if dep_time > target_time:
350 if dep_time > target_time:
351 #print "For target",target,"Dep failed:",dep # dbg
351 #print "For target",target,"Dep failed:",dep # dbg
352 #print "times (dep,tar):",dep_time,target_time # dbg
352 #print "times (dep,tar):",dep_time,target_time # dbg
353 return 1
353 return 1
354 return 0
354 return 0
355
355
356
356
357 def target_update(target,deps,cmd):
357 def target_update(target,deps,cmd):
358 """Update a target with a given command given a list of dependencies.
358 """Update a target with a given command given a list of dependencies.
359
359
360 target_update(target,deps,cmd) -> runs cmd if target is outdated.
360 target_update(target,deps,cmd) -> runs cmd if target is outdated.
361
361
362 This is just a wrapper around target_outdated() which calls the given
362 This is just a wrapper around target_outdated() which calls the given
363 command if target is outdated."""
363 command if target is outdated."""
364
364
365 if target_outdated(target,deps):
365 if target_outdated(target,deps):
366 system(cmd)
366 system(cmd)
367
367
368 @undoc
368 @undoc
369 def filehash(path):
369 def filehash(path):
370 """Make an MD5 hash of a file, ignoring any differences in line
370 """Make an MD5 hash of a file, ignoring any differences in line
371 ending characters."""
371 ending characters."""
372 warn("filehash() is deprecated since IPython 4.0", DeprecationWarning, stacklevel=2)
372 warn("filehash() is deprecated since IPython 4.0", DeprecationWarning, stacklevel=2)
373 with open(path, "rU") as f:
373 with open(path, "rU") as f:
374 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
374 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
375
375
376 ENOLINK = 1998
376 ENOLINK = 1998
377
377
378 def link(src, dst):
378 def link(src, dst):
379 """Hard links ``src`` to ``dst``, returning 0 or errno.
379 """Hard links ``src`` to ``dst``, returning 0 or errno.
380
380
381 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
381 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
382 supported by the operating system.
382 supported by the operating system.
383 """
383 """
384
384
385 if not hasattr(os, "link"):
385 if not hasattr(os, "link"):
386 return ENOLINK
386 return ENOLINK
387 link_errno = 0
387 link_errno = 0
388 try:
388 try:
389 os.link(src, dst)
389 os.link(src, dst)
390 except OSError as e:
390 except OSError as e:
391 link_errno = e.errno
391 link_errno = e.errno
392 return link_errno
392 return link_errno
393
393
394
394
395 def link_or_copy(src, dst):
395 def link_or_copy(src, dst):
396 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
396 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
397
397
398 Attempts to maintain the semantics of ``shutil.copy``.
398 Attempts to maintain the semantics of ``shutil.copy``.
399
399
400 Because ``os.link`` does not overwrite files, a unique temporary file
400 Because ``os.link`` does not overwrite files, a unique temporary file
401 will be used if the target already exists, then that file will be moved
401 will be used if the target already exists, then that file will be moved
402 into place.
402 into place.
403 """
403 """
404
404
405 if os.path.isdir(dst):
405 if os.path.isdir(dst):
406 dst = os.path.join(dst, os.path.basename(src))
406 dst = os.path.join(dst, os.path.basename(src))
407
407
408 link_errno = link(src, dst)
408 link_errno = link(src, dst)
409 if link_errno == errno.EEXIST:
409 if link_errno == errno.EEXIST:
410 if os.stat(src).st_ino == os.stat(dst).st_ino:
410 if os.stat(src).st_ino == os.stat(dst).st_ino:
411 # dst is already a hard link to the correct file, so we don't need
411 # dst is already a hard link to the correct file, so we don't need
412 # to do anything else. If we try to link and rename the file
412 # to do anything else. If we try to link and rename the file
413 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
413 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
414 return
414 return
415
415
416 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
416 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
417 try:
417 try:
418 link_or_copy(src, new_dst)
418 link_or_copy(src, new_dst)
419 except:
419 except:
420 try:
420 try:
421 os.remove(new_dst)
421 os.remove(new_dst)
422 except OSError:
422 except OSError:
423 pass
423 pass
424 raise
424 raise
425 os.rename(new_dst, dst)
425 os.rename(new_dst, dst)
426 elif link_errno != 0:
426 elif link_errno != 0:
427 # Either link isn't supported, or the filesystem doesn't support
427 # Either link isn't supported, or the filesystem doesn't support
428 # linking, or 'src' and 'dst' are on different filesystems.
428 # linking, or 'src' and 'dst' are on different filesystems.
429 shutil.copy(src, dst)
429 shutil.copy(src, dst)
430
430
431 def ensure_dir_exists(path, mode=0o755):
431 def ensure_dir_exists(path, mode=0o755):
432 """ensure that a directory exists
432 """ensure that a directory exists
433
433
434 If it doesn't exist, try to create it and protect against a race condition
434 If it doesn't exist, try to create it and protect against a race condition
435 if another process is doing the same.
435 if another process is doing the same.
436
436
437 The default permissions are 755, which differ from os.makedirs default of 777.
437 The default permissions are 755, which differ from os.makedirs default of 777.
438 """
438 """
439 if not os.path.exists(path):
439 if not os.path.exists(path):
440 try:
440 try:
441 os.makedirs(path, mode=mode)
441 os.makedirs(path, mode=mode)
442 except OSError as e:
442 except OSError as e:
443 if e.errno != errno.EEXIST:
443 if e.errno != errno.EEXIST:
444 raise
444 raise
445 elif not os.path.isdir(path):
445 elif not os.path.isdir(path):
446 raise IOError("%r exists but is not a directory" % path)
446 raise IOError("%r exists but is not a directory" % path)
@@ -1,167 +1,167 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for getting information about IPython and the system it's running in.
3 Utilities for getting information about IPython and the system it's running in.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18 import platform
18 import platform
19 import pprint
19 import pprint
20 import sys
20 import sys
21 import subprocess
21 import subprocess
22
22
23 from IPython.core import release
23 from IPython.core import release
24 from IPython.utils import py3compat, _sysinfo, encoding
24 from IPython.utils import py3compat, _sysinfo, encoding
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Code
27 # Code
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 def pkg_commit_hash(pkg_path):
30 def pkg_commit_hash(pkg_path):
31 """Get short form of commit hash given directory `pkg_path`
31 """Get short form of commit hash given directory `pkg_path`
32
32
33 We get the commit hash from (in order of preference):
33 We get the commit hash from (in order of preference):
34
34
35 * IPython.utils._sysinfo.commit
35 * IPython.utils._sysinfo.commit
36 * git output, if we are in a git repository
36 * git output, if we are in a git repository
37
37
38 If these fail, we return a not-found placeholder tuple
38 If these fail, we return a not-found placeholder tuple
39
39
40 Parameters
40 Parameters
41 ----------
41 ----------
42 pkg_path : str
42 pkg_path : str
43 directory containing package
43 directory containing package
44 only used for getting commit from active repo
44 only used for getting commit from active repo
45
45
46 Returns
46 Returns
47 -------
47 -------
48 hash_from : str
48 hash_from : str
49 Where we got the hash from - description
49 Where we got the hash from - description
50 hash_str : str
50 hash_str : str
51 short form of hash
51 short form of hash
52 """
52 """
53 # Try and get commit from written commit text file
53 # Try and get commit from written commit text file
54 if _sysinfo.commit:
54 if _sysinfo.commit:
55 return "installation", _sysinfo.commit
55 return "installation", _sysinfo.commit
56
56
57 # maybe we are in a repository
57 # maybe we are in a repository
58 proc = subprocess.Popen('git rev-parse --short HEAD',
58 proc = subprocess.Popen('git rev-parse --short HEAD',
59 stdout=subprocess.PIPE,
59 stdout=subprocess.PIPE,
60 stderr=subprocess.PIPE,
60 stderr=subprocess.PIPE,
61 cwd=pkg_path, shell=True)
61 cwd=pkg_path, shell=True)
62 repo_commit, _ = proc.communicate()
62 repo_commit, _ = proc.communicate()
63 if repo_commit:
63 if repo_commit:
64 return 'repository', repo_commit.strip().decode('ascii')
64 return 'repository', repo_commit.strip().decode('ascii')
65 return '(none found)', u'<not found>'
65 return '(none found)', '<not found>'
66
66
67
67
68 def pkg_info(pkg_path):
68 def pkg_info(pkg_path):
69 """Return dict describing the context of this package
69 """Return dict describing the context of this package
70
70
71 Parameters
71 Parameters
72 ----------
72 ----------
73 pkg_path : str
73 pkg_path : str
74 path containing __init__.py for package
74 path containing __init__.py for package
75
75
76 Returns
76 Returns
77 -------
77 -------
78 context : dict
78 context : dict
79 with named parameters of interest
79 with named parameters of interest
80 """
80 """
81 src, hsh = pkg_commit_hash(pkg_path)
81 src, hsh = pkg_commit_hash(pkg_path)
82 return dict(
82 return dict(
83 ipython_version=release.version,
83 ipython_version=release.version,
84 ipython_path=pkg_path,
84 ipython_path=pkg_path,
85 commit_source=src,
85 commit_source=src,
86 commit_hash=hsh,
86 commit_hash=hsh,
87 sys_version=sys.version,
87 sys_version=sys.version,
88 sys_executable=sys.executable,
88 sys_executable=sys.executable,
89 sys_platform=sys.platform,
89 sys_platform=sys.platform,
90 platform=platform.platform(),
90 platform=platform.platform(),
91 os_name=os.name,
91 os_name=os.name,
92 default_encoding=encoding.DEFAULT_ENCODING,
92 default_encoding=encoding.DEFAULT_ENCODING,
93 )
93 )
94
94
95 def get_sys_info():
95 def get_sys_info():
96 """Return useful information about IPython and the system, as a dict."""
96 """Return useful information about IPython and the system, as a dict."""
97 p = os.path
97 p = os.path
98 path = p.realpath(p.dirname(p.abspath(p.join(__file__, '..'))))
98 path = p.realpath(p.dirname(p.abspath(p.join(__file__, '..'))))
99 return pkg_info(path)
99 return pkg_info(path)
100
100
101 @py3compat.doctest_refactor_print
101 @py3compat.doctest_refactor_print
102 def sys_info():
102 def sys_info():
103 """Return useful information about IPython and the system, as a string.
103 """Return useful information about IPython and the system, as a string.
104
104
105 Examples
105 Examples
106 --------
106 --------
107 ::
107 ::
108
108
109 In [2]: print sys_info()
109 In [2]: print sys_info()
110 {'commit_hash': '144fdae', # random
110 {'commit_hash': '144fdae', # random
111 'commit_source': 'repository',
111 'commit_source': 'repository',
112 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
112 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
113 'ipython_version': '0.11.dev',
113 'ipython_version': '0.11.dev',
114 'os_name': 'posix',
114 'os_name': 'posix',
115 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
115 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
116 'sys_executable': '/usr/bin/python',
116 'sys_executable': '/usr/bin/python',
117 'sys_platform': 'linux2',
117 'sys_platform': 'linux2',
118 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
118 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
119 """
119 """
120 return pprint.pformat(get_sys_info())
120 return pprint.pformat(get_sys_info())
121
121
122 def _num_cpus_unix():
122 def _num_cpus_unix():
123 """Return the number of active CPUs on a Unix system."""
123 """Return the number of active CPUs on a Unix system."""
124 return os.sysconf("SC_NPROCESSORS_ONLN")
124 return os.sysconf("SC_NPROCESSORS_ONLN")
125
125
126
126
127 def _num_cpus_darwin():
127 def _num_cpus_darwin():
128 """Return the number of active CPUs on a Darwin system."""
128 """Return the number of active CPUs on a Darwin system."""
129 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
129 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
130 return p.stdout.read()
130 return p.stdout.read()
131
131
132
132
133 def _num_cpus_windows():
133 def _num_cpus_windows():
134 """Return the number of active CPUs on a Windows system."""
134 """Return the number of active CPUs on a Windows system."""
135 return os.environ.get("NUMBER_OF_PROCESSORS")
135 return os.environ.get("NUMBER_OF_PROCESSORS")
136
136
137
137
138 def num_cpus():
138 def num_cpus():
139 """Return the effective number of CPUs in the system as an integer.
139 """Return the effective number of CPUs in the system as an integer.
140
140
141 This cross-platform function makes an attempt at finding the total number of
141 This cross-platform function makes an attempt at finding the total number of
142 available CPUs in the system, as returned by various underlying system and
142 available CPUs in the system, as returned by various underlying system and
143 python calls.
143 python calls.
144
144
145 If it can't find a sensible answer, it returns 1 (though an error *may* make
145 If it can't find a sensible answer, it returns 1 (though an error *may* make
146 it return a large positive number that's actually incorrect).
146 it return a large positive number that's actually incorrect).
147 """
147 """
148
148
149 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
149 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
150 # for the names of the keys we needed to look up for this function. This
150 # for the names of the keys we needed to look up for this function. This
151 # code was inspired by their equivalent function.
151 # code was inspired by their equivalent function.
152
152
153 ncpufuncs = {'Linux':_num_cpus_unix,
153 ncpufuncs = {'Linux':_num_cpus_unix,
154 'Darwin':_num_cpus_darwin,
154 'Darwin':_num_cpus_darwin,
155 'Windows':_num_cpus_windows
155 'Windows':_num_cpus_windows
156 }
156 }
157
157
158 ncpufunc = ncpufuncs.get(platform.system(),
158 ncpufunc = ncpufuncs.get(platform.system(),
159 # default to unix version (Solaris, AIX, etc)
159 # default to unix version (Solaris, AIX, etc)
160 _num_cpus_unix)
160 _num_cpus_unix)
161
161
162 try:
162 try:
163 ncpus = max(1,int(ncpufunc()))
163 ncpus = max(1,int(ncpufunc()))
164 except:
164 except:
165 ncpus = 1
165 ncpus = 1
166 return ncpus
166 return ncpus
167
167
@@ -1,482 +1,482 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import errno
7 import errno
8 import os
8 import os
9 import shutil
9 import shutil
10 import sys
10 import sys
11 import tempfile
11 import tempfile
12 import warnings
12 import warnings
13 from contextlib import contextmanager
13 from contextlib import contextmanager
14 from unittest.mock import patch
14 from unittest.mock import patch
15 from os.path import join, abspath, split
15 from os.path import join, abspath, split
16 from imp import reload
16 from imp import reload
17
17
18 from nose import SkipTest, with_setup
18 from nose import SkipTest, with_setup
19 import nose.tools as nt
19 import nose.tools as nt
20
20
21 import IPython
21 import IPython
22 from IPython import paths
22 from IPython import paths
23 from IPython.testing import decorators as dec
23 from IPython.testing import decorators as dec
24 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
24 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
25 onlyif_unicode_paths,)
25 onlyif_unicode_paths,)
26 from IPython.testing.tools import make_tempfile, AssertPrints
26 from IPython.testing.tools import make_tempfile, AssertPrints
27 from IPython.utils import path
27 from IPython.utils import path
28 from IPython.utils import py3compat
28 from IPython.utils import py3compat
29 from IPython.utils.tempdir import TemporaryDirectory
29 from IPython.utils.tempdir import TemporaryDirectory
30
30
31 # Platform-dependent imports
31 # Platform-dependent imports
32 try:
32 try:
33 import winreg as wreg
33 import winreg as wreg
34 except ImportError:
34 except ImportError:
35 #Fake _winreg module on non-windows platforms
35 #Fake _winreg module on non-windows platforms
36 import types
36 import types
37 wr_name = "winreg"
37 wr_name = "winreg"
38 sys.modules[wr_name] = types.ModuleType(wr_name)
38 sys.modules[wr_name] = types.ModuleType(wr_name)
39 try:
39 try:
40 import winreg as wreg
40 import winreg as wreg
41 except ImportError:
41 except ImportError:
42 import _winreg as wreg
42 import _winreg as wreg
43 #Add entries that needs to be stubbed by the testing code
43 #Add entries that needs to be stubbed by the testing code
44 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
44 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Globals
47 # Globals
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 env = os.environ
49 env = os.environ
50 TMP_TEST_DIR = tempfile.mkdtemp()
50 TMP_TEST_DIR = tempfile.mkdtemp()
51 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
51 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
52 #
52 #
53 # Setup/teardown functions/decorators
53 # Setup/teardown functions/decorators
54 #
54 #
55
55
56 def setup():
56 def setup():
57 """Setup testenvironment for the module:
57 """Setup testenvironment for the module:
58
58
59 - Adds dummy home dir tree
59 - Adds dummy home dir tree
60 """
60 """
61 # Do not mask exceptions here. In particular, catching WindowsError is a
61 # Do not mask exceptions here. In particular, catching WindowsError is a
62 # problem because that exception is only defined on Windows...
62 # problem because that exception is only defined on Windows...
63 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
63 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
64
64
65
65
66 def teardown():
66 def teardown():
67 """Teardown testenvironment for the module:
67 """Teardown testenvironment for the module:
68
68
69 - Remove dummy home dir tree
69 - Remove dummy home dir tree
70 """
70 """
71 # Note: we remove the parent test dir, which is the root of all test
71 # Note: we remove the parent test dir, which is the root of all test
72 # subdirs we may have created. Use shutil instead of os.removedirs, so
72 # subdirs we may have created. Use shutil instead of os.removedirs, so
73 # that non-empty directories are all recursively removed.
73 # that non-empty directories are all recursively removed.
74 shutil.rmtree(TMP_TEST_DIR)
74 shutil.rmtree(TMP_TEST_DIR)
75
75
76
76
77 def setup_environment():
77 def setup_environment():
78 """Setup testenvironment for some functions that are tested
78 """Setup testenvironment for some functions that are tested
79 in this module. In particular this functions stores attributes
79 in this module. In particular this functions stores attributes
80 and other things that we need to stub in some test functions.
80 and other things that we need to stub in some test functions.
81 This needs to be done on a function level and not module level because
81 This needs to be done on a function level and not module level because
82 each testfunction needs a pristine environment.
82 each testfunction needs a pristine environment.
83 """
83 """
84 global oldstuff, platformstuff
84 global oldstuff, platformstuff
85 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
85 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
86
86
87 def teardown_environment():
87 def teardown_environment():
88 """Restore things that were remembered by the setup_environment function
88 """Restore things that were remembered by the setup_environment function
89 """
89 """
90 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
90 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
91 os.chdir(old_wd)
91 os.chdir(old_wd)
92 reload(path)
92 reload(path)
93
93
94 for key in list(env):
94 for key in list(env):
95 if key not in oldenv:
95 if key not in oldenv:
96 del env[key]
96 del env[key]
97 env.update(oldenv)
97 env.update(oldenv)
98 if hasattr(sys, 'frozen'):
98 if hasattr(sys, 'frozen'):
99 del sys.frozen
99 del sys.frozen
100
100
101 # Build decorator that uses the setup_environment/setup_environment
101 # Build decorator that uses the setup_environment/setup_environment
102 with_environment = with_setup(setup_environment, teardown_environment)
102 with_environment = with_setup(setup_environment, teardown_environment)
103
103
104 @skip_if_not_win32
104 @skip_if_not_win32
105 @with_environment
105 @with_environment
106 def test_get_home_dir_1():
106 def test_get_home_dir_1():
107 """Testcase for py2exe logic, un-compressed lib
107 """Testcase for py2exe logic, un-compressed lib
108 """
108 """
109 unfrozen = path.get_home_dir()
109 unfrozen = path.get_home_dir()
110 sys.frozen = True
110 sys.frozen = True
111
111
112 #fake filename for IPython.__init__
112 #fake filename for IPython.__init__
113 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
113 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
114
114
115 home_dir = path.get_home_dir()
115 home_dir = path.get_home_dir()
116 nt.assert_equal(home_dir, unfrozen)
116 nt.assert_equal(home_dir, unfrozen)
117
117
118
118
119 @skip_if_not_win32
119 @skip_if_not_win32
120 @with_environment
120 @with_environment
121 def test_get_home_dir_2():
121 def test_get_home_dir_2():
122 """Testcase for py2exe logic, compressed lib
122 """Testcase for py2exe logic, compressed lib
123 """
123 """
124 unfrozen = path.get_home_dir()
124 unfrozen = path.get_home_dir()
125 sys.frozen = True
125 sys.frozen = True
126 #fake filename for IPython.__init__
126 #fake filename for IPython.__init__
127 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
127 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
128
128
129 home_dir = path.get_home_dir(True)
129 home_dir = path.get_home_dir(True)
130 nt.assert_equal(home_dir, unfrozen)
130 nt.assert_equal(home_dir, unfrozen)
131
131
132
132
133 @with_environment
133 @with_environment
134 def test_get_home_dir_3():
134 def test_get_home_dir_3():
135 """get_home_dir() uses $HOME if set"""
135 """get_home_dir() uses $HOME if set"""
136 env["HOME"] = HOME_TEST_DIR
136 env["HOME"] = HOME_TEST_DIR
137 home_dir = path.get_home_dir(True)
137 home_dir = path.get_home_dir(True)
138 # get_home_dir expands symlinks
138 # get_home_dir expands symlinks
139 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
139 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
140
140
141
141
142 @with_environment
142 @with_environment
143 def test_get_home_dir_4():
143 def test_get_home_dir_4():
144 """get_home_dir() still works if $HOME is not set"""
144 """get_home_dir() still works if $HOME is not set"""
145
145
146 if 'HOME' in env: del env['HOME']
146 if 'HOME' in env: del env['HOME']
147 # this should still succeed, but we don't care what the answer is
147 # this should still succeed, but we don't care what the answer is
148 home = path.get_home_dir(False)
148 home = path.get_home_dir(False)
149
149
150 @with_environment
150 @with_environment
151 def test_get_home_dir_5():
151 def test_get_home_dir_5():
152 """raise HomeDirError if $HOME is specified, but not a writable dir"""
152 """raise HomeDirError if $HOME is specified, but not a writable dir"""
153 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
153 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
154 # set os.name = posix, to prevent My Documents fallback on Windows
154 # set os.name = posix, to prevent My Documents fallback on Windows
155 os.name = 'posix'
155 os.name = 'posix'
156 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
156 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
157
157
158 # Should we stub wreg fully so we can run the test on all platforms?
158 # Should we stub wreg fully so we can run the test on all platforms?
159 @skip_if_not_win32
159 @skip_if_not_win32
160 @with_environment
160 @with_environment
161 def test_get_home_dir_8():
161 def test_get_home_dir_8():
162 """Using registry hack for 'My Documents', os=='nt'
162 """Using registry hack for 'My Documents', os=='nt'
163
163
164 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
164 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
165 """
165 """
166 os.name = 'nt'
166 os.name = 'nt'
167 # Remove from stub environment all keys that may be set
167 # Remove from stub environment all keys that may be set
168 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
168 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
169 env.pop(key, None)
169 env.pop(key, None)
170
170
171 class key:
171 class key:
172 def Close(self):
172 def Close(self):
173 pass
173 pass
174
174
175 with patch.object(wreg, 'OpenKey', return_value=key()), \
175 with patch.object(wreg, 'OpenKey', return_value=key()), \
176 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
176 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
177 home_dir = path.get_home_dir()
177 home_dir = path.get_home_dir()
178 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
178 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
179
179
180 @with_environment
180 @with_environment
181 def test_get_xdg_dir_0():
181 def test_get_xdg_dir_0():
182 """test_get_xdg_dir_0, check xdg_dir"""
182 """test_get_xdg_dir_0, check xdg_dir"""
183 reload(path)
183 reload(path)
184 path._writable_dir = lambda path: True
184 path._writable_dir = lambda path: True
185 path.get_home_dir = lambda : 'somewhere'
185 path.get_home_dir = lambda : 'somewhere'
186 os.name = "posix"
186 os.name = "posix"
187 sys.platform = "linux2"
187 sys.platform = "linux2"
188 env.pop('IPYTHON_DIR', None)
188 env.pop('IPYTHON_DIR', None)
189 env.pop('IPYTHONDIR', None)
189 env.pop('IPYTHONDIR', None)
190 env.pop('XDG_CONFIG_HOME', None)
190 env.pop('XDG_CONFIG_HOME', None)
191
191
192 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
192 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
193
193
194
194
195 @with_environment
195 @with_environment
196 def test_get_xdg_dir_1():
196 def test_get_xdg_dir_1():
197 """test_get_xdg_dir_1, check nonexistant xdg_dir"""
197 """test_get_xdg_dir_1, check nonexistant xdg_dir"""
198 reload(path)
198 reload(path)
199 path.get_home_dir = lambda : HOME_TEST_DIR
199 path.get_home_dir = lambda : HOME_TEST_DIR
200 os.name = "posix"
200 os.name = "posix"
201 sys.platform = "linux2"
201 sys.platform = "linux2"
202 env.pop('IPYTHON_DIR', None)
202 env.pop('IPYTHON_DIR', None)
203 env.pop('IPYTHONDIR', None)
203 env.pop('IPYTHONDIR', None)
204 env.pop('XDG_CONFIG_HOME', None)
204 env.pop('XDG_CONFIG_HOME', None)
205 nt.assert_equal(path.get_xdg_dir(), None)
205 nt.assert_equal(path.get_xdg_dir(), None)
206
206
207 @with_environment
207 @with_environment
208 def test_get_xdg_dir_2():
208 def test_get_xdg_dir_2():
209 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
209 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
210 reload(path)
210 reload(path)
211 path.get_home_dir = lambda : HOME_TEST_DIR
211 path.get_home_dir = lambda : HOME_TEST_DIR
212 os.name = "posix"
212 os.name = "posix"
213 sys.platform = "linux2"
213 sys.platform = "linux2"
214 env.pop('IPYTHON_DIR', None)
214 env.pop('IPYTHON_DIR', None)
215 env.pop('IPYTHONDIR', None)
215 env.pop('IPYTHONDIR', None)
216 env.pop('XDG_CONFIG_HOME', None)
216 env.pop('XDG_CONFIG_HOME', None)
217 cfgdir=os.path.join(path.get_home_dir(), '.config')
217 cfgdir=os.path.join(path.get_home_dir(), '.config')
218 if not os.path.exists(cfgdir):
218 if not os.path.exists(cfgdir):
219 os.makedirs(cfgdir)
219 os.makedirs(cfgdir)
220
220
221 nt.assert_equal(path.get_xdg_dir(), cfgdir)
221 nt.assert_equal(path.get_xdg_dir(), cfgdir)
222
222
223 @with_environment
223 @with_environment
224 def test_get_xdg_dir_3():
224 def test_get_xdg_dir_3():
225 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
225 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
226 reload(path)
226 reload(path)
227 path.get_home_dir = lambda : HOME_TEST_DIR
227 path.get_home_dir = lambda : HOME_TEST_DIR
228 os.name = "posix"
228 os.name = "posix"
229 sys.platform = "darwin"
229 sys.platform = "darwin"
230 env.pop('IPYTHON_DIR', None)
230 env.pop('IPYTHON_DIR', None)
231 env.pop('IPYTHONDIR', None)
231 env.pop('IPYTHONDIR', None)
232 env.pop('XDG_CONFIG_HOME', None)
232 env.pop('XDG_CONFIG_HOME', None)
233 cfgdir=os.path.join(path.get_home_dir(), '.config')
233 cfgdir=os.path.join(path.get_home_dir(), '.config')
234 if not os.path.exists(cfgdir):
234 if not os.path.exists(cfgdir):
235 os.makedirs(cfgdir)
235 os.makedirs(cfgdir)
236
236
237 nt.assert_equal(path.get_xdg_dir(), None)
237 nt.assert_equal(path.get_xdg_dir(), None)
238
238
239 def test_filefind():
239 def test_filefind():
240 """Various tests for filefind"""
240 """Various tests for filefind"""
241 f = tempfile.NamedTemporaryFile()
241 f = tempfile.NamedTemporaryFile()
242 # print 'fname:',f.name
242 # print 'fname:',f.name
243 alt_dirs = paths.get_ipython_dir()
243 alt_dirs = paths.get_ipython_dir()
244 t = path.filefind(f.name, alt_dirs)
244 t = path.filefind(f.name, alt_dirs)
245 # print 'found:',t
245 # print 'found:',t
246
246
247
247
248 @dec.skip_if_not_win32
248 @dec.skip_if_not_win32
249 def test_get_long_path_name_win32():
249 def test_get_long_path_name_win32():
250 with TemporaryDirectory() as tmpdir:
250 with TemporaryDirectory() as tmpdir:
251
251
252 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
252 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
253 # path component, so ensure we include the long form of it
253 # path component, so ensure we include the long form of it
254 long_path = os.path.join(path.get_long_path_name(tmpdir), u'this is my long path name')
254 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
255 os.makedirs(long_path)
255 os.makedirs(long_path)
256
256
257 # Test to see if the short path evaluates correctly.
257 # Test to see if the short path evaluates correctly.
258 short_path = os.path.join(tmpdir, u'THISIS~1')
258 short_path = os.path.join(tmpdir, 'THISIS~1')
259 evaluated_path = path.get_long_path_name(short_path)
259 evaluated_path = path.get_long_path_name(short_path)
260 nt.assert_equal(evaluated_path.lower(), long_path.lower())
260 nt.assert_equal(evaluated_path.lower(), long_path.lower())
261
261
262
262
263 @dec.skip_win32
263 @dec.skip_win32
264 def test_get_long_path_name():
264 def test_get_long_path_name():
265 p = path.get_long_path_name('/usr/local')
265 p = path.get_long_path_name('/usr/local')
266 nt.assert_equal(p,'/usr/local')
266 nt.assert_equal(p,'/usr/local')
267
267
268 @dec.skip_win32 # can't create not-user-writable dir on win
268 @dec.skip_win32 # can't create not-user-writable dir on win
269 @with_environment
269 @with_environment
270 def test_not_writable_ipdir():
270 def test_not_writable_ipdir():
271 tmpdir = tempfile.mkdtemp()
271 tmpdir = tempfile.mkdtemp()
272 os.name = "posix"
272 os.name = "posix"
273 env.pop('IPYTHON_DIR', None)
273 env.pop('IPYTHON_DIR', None)
274 env.pop('IPYTHONDIR', None)
274 env.pop('IPYTHONDIR', None)
275 env.pop('XDG_CONFIG_HOME', None)
275 env.pop('XDG_CONFIG_HOME', None)
276 env['HOME'] = tmpdir
276 env['HOME'] = tmpdir
277 ipdir = os.path.join(tmpdir, '.ipython')
277 ipdir = os.path.join(tmpdir, '.ipython')
278 os.mkdir(ipdir, 0o555)
278 os.mkdir(ipdir, 0o555)
279 try:
279 try:
280 open(os.path.join(ipdir, "_foo_"), 'w').close()
280 open(os.path.join(ipdir, "_foo_"), 'w').close()
281 except IOError:
281 except IOError:
282 pass
282 pass
283 else:
283 else:
284 # I can still write to an unwritable dir,
284 # I can still write to an unwritable dir,
285 # assume I'm root and skip the test
285 # assume I'm root and skip the test
286 raise SkipTest("I can't create directories that I can't write to")
286 raise SkipTest("I can't create directories that I can't write to")
287 with AssertPrints('is not a writable location', channel='stderr'):
287 with AssertPrints('is not a writable location', channel='stderr'):
288 ipdir = paths.get_ipython_dir()
288 ipdir = paths.get_ipython_dir()
289 env.pop('IPYTHON_DIR', None)
289 env.pop('IPYTHON_DIR', None)
290
290
291 @with_environment
291 @with_environment
292 def test_get_py_filename():
292 def test_get_py_filename():
293 os.chdir(TMP_TEST_DIR)
293 os.chdir(TMP_TEST_DIR)
294 with make_tempfile('foo.py'):
294 with make_tempfile('foo.py'):
295 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
295 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
296 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
296 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
297 with make_tempfile('foo'):
297 with make_tempfile('foo'):
298 nt.assert_equal(path.get_py_filename('foo'), 'foo')
298 nt.assert_equal(path.get_py_filename('foo'), 'foo')
299 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
299 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
300 nt.assert_raises(IOError, path.get_py_filename, 'foo')
300 nt.assert_raises(IOError, path.get_py_filename, 'foo')
301 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
301 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
302 true_fn = 'foo with spaces.py'
302 true_fn = 'foo with spaces.py'
303 with make_tempfile(true_fn):
303 with make_tempfile(true_fn):
304 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
304 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
305 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
305 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
306 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
306 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
307 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
307 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
308
308
309 @onlyif_unicode_paths
309 @onlyif_unicode_paths
310 def test_unicode_in_filename():
310 def test_unicode_in_filename():
311 """When a file doesn't exist, the exception raised should be safe to call
311 """When a file doesn't exist, the exception raised should be safe to call
312 str() on - i.e. in Python 2 it must only have ASCII characters.
312 str() on - i.e. in Python 2 it must only have ASCII characters.
313
313
314 https://github.com/ipython/ipython/issues/875
314 https://github.com/ipython/ipython/issues/875
315 """
315 """
316 try:
316 try:
317 # these calls should not throw unicode encode exceptions
317 # these calls should not throw unicode encode exceptions
318 path.get_py_filename(u'fooéè.py', force_win32=False)
318 path.get_py_filename('fooéè.py', force_win32=False)
319 except IOError as ex:
319 except IOError as ex:
320 str(ex)
320 str(ex)
321
321
322
322
323 class TestShellGlob(object):
323 class TestShellGlob(object):
324
324
325 @classmethod
325 @classmethod
326 def setUpClass(cls):
326 def setUpClass(cls):
327 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
327 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
328 cls.filenames_end_with_b = ['0b', '1b', '2b']
328 cls.filenames_end_with_b = ['0b', '1b', '2b']
329 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
329 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
330 cls.tempdir = TemporaryDirectory()
330 cls.tempdir = TemporaryDirectory()
331 td = cls.tempdir.name
331 td = cls.tempdir.name
332
332
333 with cls.in_tempdir():
333 with cls.in_tempdir():
334 # Create empty files
334 # Create empty files
335 for fname in cls.filenames:
335 for fname in cls.filenames:
336 open(os.path.join(td, fname), 'w').close()
336 open(os.path.join(td, fname), 'w').close()
337
337
338 @classmethod
338 @classmethod
339 def tearDownClass(cls):
339 def tearDownClass(cls):
340 cls.tempdir.cleanup()
340 cls.tempdir.cleanup()
341
341
342 @classmethod
342 @classmethod
343 @contextmanager
343 @contextmanager
344 def in_tempdir(cls):
344 def in_tempdir(cls):
345 save = os.getcwd()
345 save = os.getcwd()
346 try:
346 try:
347 os.chdir(cls.tempdir.name)
347 os.chdir(cls.tempdir.name)
348 yield
348 yield
349 finally:
349 finally:
350 os.chdir(save)
350 os.chdir(save)
351
351
352 def check_match(self, patterns, matches):
352 def check_match(self, patterns, matches):
353 with self.in_tempdir():
353 with self.in_tempdir():
354 # glob returns unordered list. that's why sorted is required.
354 # glob returns unordered list. that's why sorted is required.
355 nt.assert_equal(sorted(path.shellglob(patterns)),
355 nt.assert_equal(sorted(path.shellglob(patterns)),
356 sorted(matches))
356 sorted(matches))
357
357
358 def common_cases(self):
358 def common_cases(self):
359 return [
359 return [
360 (['*'], self.filenames),
360 (['*'], self.filenames),
361 (['a*'], self.filenames_start_with_a),
361 (['a*'], self.filenames_start_with_a),
362 (['*c'], ['*c']),
362 (['*c'], ['*c']),
363 (['*', 'a*', '*b', '*c'], self.filenames
363 (['*', 'a*', '*b', '*c'], self.filenames
364 + self.filenames_start_with_a
364 + self.filenames_start_with_a
365 + self.filenames_end_with_b
365 + self.filenames_end_with_b
366 + ['*c']),
366 + ['*c']),
367 (['a[012]'], self.filenames_start_with_a),
367 (['a[012]'], self.filenames_start_with_a),
368 ]
368 ]
369
369
370 @skip_win32
370 @skip_win32
371 def test_match_posix(self):
371 def test_match_posix(self):
372 for (patterns, matches) in self.common_cases() + [
372 for (patterns, matches) in self.common_cases() + [
373 ([r'\*'], ['*']),
373 ([r'\*'], ['*']),
374 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
374 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
375 ([r'a\[012]'], ['a[012]']),
375 ([r'a\[012]'], ['a[012]']),
376 ]:
376 ]:
377 yield (self.check_match, patterns, matches)
377 yield (self.check_match, patterns, matches)
378
378
379 @skip_if_not_win32
379 @skip_if_not_win32
380 def test_match_windows(self):
380 def test_match_windows(self):
381 for (patterns, matches) in self.common_cases() + [
381 for (patterns, matches) in self.common_cases() + [
382 # In windows, backslash is interpreted as path
382 # In windows, backslash is interpreted as path
383 # separator. Therefore, you can't escape glob
383 # separator. Therefore, you can't escape glob
384 # using it.
384 # using it.
385 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
385 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
386 ([r'a\[012]'], [r'a\[012]']),
386 ([r'a\[012]'], [r'a\[012]']),
387 ]:
387 ]:
388 yield (self.check_match, patterns, matches)
388 yield (self.check_match, patterns, matches)
389
389
390
390
391 def test_unescape_glob():
391 def test_unescape_glob():
392 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
392 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
393 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
393 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
394 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
394 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
395 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
395 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
396 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
396 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
397
397
398
398
399 def test_ensure_dir_exists():
399 def test_ensure_dir_exists():
400 with TemporaryDirectory() as td:
400 with TemporaryDirectory() as td:
401 d = os.path.join(td, u'βˆ‚ir')
401 d = os.path.join(td, 'βˆ‚ir')
402 path.ensure_dir_exists(d) # create it
402 path.ensure_dir_exists(d) # create it
403 assert os.path.isdir(d)
403 assert os.path.isdir(d)
404 path.ensure_dir_exists(d) # no-op
404 path.ensure_dir_exists(d) # no-op
405 f = os.path.join(td, u'Ζ’ile')
405 f = os.path.join(td, 'Ζ’ile')
406 open(f, 'w').close() # touch
406 open(f, 'w').close() # touch
407 with nt.assert_raises(IOError):
407 with nt.assert_raises(IOError):
408 path.ensure_dir_exists(f)
408 path.ensure_dir_exists(f)
409
409
410 class TestLinkOrCopy(object):
410 class TestLinkOrCopy(object):
411 def setUp(self):
411 def setUp(self):
412 self.tempdir = TemporaryDirectory()
412 self.tempdir = TemporaryDirectory()
413 self.src = self.dst("src")
413 self.src = self.dst("src")
414 with open(self.src, "w") as f:
414 with open(self.src, "w") as f:
415 f.write("Hello, world!")
415 f.write("Hello, world!")
416
416
417 def tearDown(self):
417 def tearDown(self):
418 self.tempdir.cleanup()
418 self.tempdir.cleanup()
419
419
420 def dst(self, *args):
420 def dst(self, *args):
421 return os.path.join(self.tempdir.name, *args)
421 return os.path.join(self.tempdir.name, *args)
422
422
423 def assert_inode_not_equal(self, a, b):
423 def assert_inode_not_equal(self, a, b):
424 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
424 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
425 "%r and %r do reference the same indoes" %(a, b))
425 "%r and %r do reference the same indoes" %(a, b))
426
426
427 def assert_inode_equal(self, a, b):
427 def assert_inode_equal(self, a, b):
428 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
428 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
429 "%r and %r do not reference the same indoes" %(a, b))
429 "%r and %r do not reference the same indoes" %(a, b))
430
430
431 def assert_content_equal(self, a, b):
431 def assert_content_equal(self, a, b):
432 with open(a) as a_f:
432 with open(a) as a_f:
433 with open(b) as b_f:
433 with open(b) as b_f:
434 nt.assert_equal(a_f.read(), b_f.read())
434 nt.assert_equal(a_f.read(), b_f.read())
435
435
436 @skip_win32
436 @skip_win32
437 def test_link_successful(self):
437 def test_link_successful(self):
438 dst = self.dst("target")
438 dst = self.dst("target")
439 path.link_or_copy(self.src, dst)
439 path.link_or_copy(self.src, dst)
440 self.assert_inode_equal(self.src, dst)
440 self.assert_inode_equal(self.src, dst)
441
441
442 @skip_win32
442 @skip_win32
443 def test_link_into_dir(self):
443 def test_link_into_dir(self):
444 dst = self.dst("some_dir")
444 dst = self.dst("some_dir")
445 os.mkdir(dst)
445 os.mkdir(dst)
446 path.link_or_copy(self.src, dst)
446 path.link_or_copy(self.src, dst)
447 expected_dst = self.dst("some_dir", os.path.basename(self.src))
447 expected_dst = self.dst("some_dir", os.path.basename(self.src))
448 self.assert_inode_equal(self.src, expected_dst)
448 self.assert_inode_equal(self.src, expected_dst)
449
449
450 @skip_win32
450 @skip_win32
451 def test_target_exists(self):
451 def test_target_exists(self):
452 dst = self.dst("target")
452 dst = self.dst("target")
453 open(dst, "w").close()
453 open(dst, "w").close()
454 path.link_or_copy(self.src, dst)
454 path.link_or_copy(self.src, dst)
455 self.assert_inode_equal(self.src, dst)
455 self.assert_inode_equal(self.src, dst)
456
456
457 @skip_win32
457 @skip_win32
458 def test_no_link(self):
458 def test_no_link(self):
459 real_link = os.link
459 real_link = os.link
460 try:
460 try:
461 del os.link
461 del os.link
462 dst = self.dst("target")
462 dst = self.dst("target")
463 path.link_or_copy(self.src, dst)
463 path.link_or_copy(self.src, dst)
464 self.assert_content_equal(self.src, dst)
464 self.assert_content_equal(self.src, dst)
465 self.assert_inode_not_equal(self.src, dst)
465 self.assert_inode_not_equal(self.src, dst)
466 finally:
466 finally:
467 os.link = real_link
467 os.link = real_link
468
468
469 @skip_if_not_win32
469 @skip_if_not_win32
470 def test_windows(self):
470 def test_windows(self):
471 dst = self.dst("target")
471 dst = self.dst("target")
472 path.link_or_copy(self.src, dst)
472 path.link_or_copy(self.src, dst)
473 self.assert_content_equal(self.src, dst)
473 self.assert_content_equal(self.src, dst)
474
474
475 def test_link_twice(self):
475 def test_link_twice(self):
476 # Linking the same file twice shouldn't leave duplicates around.
476 # Linking the same file twice shouldn't leave duplicates around.
477 # See https://github.com/ipython/ipython/issues/6450
477 # See https://github.com/ipython/ipython/issues/6450
478 dst = self.dst('target')
478 dst = self.dst('target')
479 path.link_or_copy(self.src, dst)
479 path.link_or_copy(self.src, dst)
480 path.link_or_copy(self.src, dst)
480 path.link_or_copy(self.src, dst)
481 self.assert_inode_equal(self.src, dst)
481 self.assert_inode_equal(self.src, dst)
482 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
482 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
@@ -1,776 +1,776 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.utils.text
7 .. inheritance-diagram:: IPython.utils.text
8 :parts: 3
8 :parts: 3
9 """
9 """
10
10
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14 import textwrap
14 import textwrap
15 from string import Formatter
15 from string import Formatter
16 try:
16 try:
17 from pathlib import Path
17 from pathlib import Path
18 except ImportError:
18 except ImportError:
19 # for Python 3.3
19 # for Python 3.3
20 from pathlib2 import Path
20 from pathlib2 import Path
21
21
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23
23
24 # datetime.strftime date format for ipython
24 # datetime.strftime date format for ipython
25 if sys.platform == 'win32':
25 if sys.platform == 'win32':
26 date_format = "%B %d, %Y"
26 date_format = "%B %d, %Y"
27 else:
27 else:
28 date_format = "%B %-d, %Y"
28 date_format = "%B %-d, %Y"
29
29
30 class LSString(str):
30 class LSString(str):
31 """String derivative with a special access attributes.
31 """String derivative with a special access attributes.
32
32
33 These are normal strings, but with the special attributes:
33 These are normal strings, but with the special attributes:
34
34
35 .l (or .list) : value as list (split on newlines).
35 .l (or .list) : value as list (split on newlines).
36 .n (or .nlstr): original value (the string itself).
36 .n (or .nlstr): original value (the string itself).
37 .s (or .spstr): value as whitespace-separated string.
37 .s (or .spstr): value as whitespace-separated string.
38 .p (or .paths): list of path objects (requires path.py package)
38 .p (or .paths): list of path objects (requires path.py package)
39
39
40 Any values which require transformations are computed only once and
40 Any values which require transformations are computed only once and
41 cached.
41 cached.
42
42
43 Such strings are very useful to efficiently interact with the shell, which
43 Such strings are very useful to efficiently interact with the shell, which
44 typically only understands whitespace-separated options for commands."""
44 typically only understands whitespace-separated options for commands."""
45
45
46 def get_list(self):
46 def get_list(self):
47 try:
47 try:
48 return self.__list
48 return self.__list
49 except AttributeError:
49 except AttributeError:
50 self.__list = self.split('\n')
50 self.__list = self.split('\n')
51 return self.__list
51 return self.__list
52
52
53 l = list = property(get_list)
53 l = list = property(get_list)
54
54
55 def get_spstr(self):
55 def get_spstr(self):
56 try:
56 try:
57 return self.__spstr
57 return self.__spstr
58 except AttributeError:
58 except AttributeError:
59 self.__spstr = self.replace('\n',' ')
59 self.__spstr = self.replace('\n',' ')
60 return self.__spstr
60 return self.__spstr
61
61
62 s = spstr = property(get_spstr)
62 s = spstr = property(get_spstr)
63
63
64 def get_nlstr(self):
64 def get_nlstr(self):
65 return self
65 return self
66
66
67 n = nlstr = property(get_nlstr)
67 n = nlstr = property(get_nlstr)
68
68
69 def get_paths(self):
69 def get_paths(self):
70 try:
70 try:
71 return self.__paths
71 return self.__paths
72 except AttributeError:
72 except AttributeError:
73 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
73 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
74 return self.__paths
74 return self.__paths
75
75
76 p = paths = property(get_paths)
76 p = paths = property(get_paths)
77
77
78 # FIXME: We need to reimplement type specific displayhook and then add this
78 # FIXME: We need to reimplement type specific displayhook and then add this
79 # back as a custom printer. This should also be moved outside utils into the
79 # back as a custom printer. This should also be moved outside utils into the
80 # core.
80 # core.
81
81
82 # def print_lsstring(arg):
82 # def print_lsstring(arg):
83 # """ Prettier (non-repr-like) and more informative printer for LSString """
83 # """ Prettier (non-repr-like) and more informative printer for LSString """
84 # print "LSString (.p, .n, .l, .s available). Value:"
84 # print "LSString (.p, .n, .l, .s available). Value:"
85 # print arg
85 # print arg
86 #
86 #
87 #
87 #
88 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
88 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
89
89
90
90
91 class SList(list):
91 class SList(list):
92 """List derivative with a special access attributes.
92 """List derivative with a special access attributes.
93
93
94 These are normal lists, but with the special attributes:
94 These are normal lists, but with the special attributes:
95
95
96 * .l (or .list) : value as list (the list itself).
96 * .l (or .list) : value as list (the list itself).
97 * .n (or .nlstr): value as a string, joined on newlines.
97 * .n (or .nlstr): value as a string, joined on newlines.
98 * .s (or .spstr): value as a string, joined on spaces.
98 * .s (or .spstr): value as a string, joined on spaces.
99 * .p (or .paths): list of path objects (requires path.py package)
99 * .p (or .paths): list of path objects (requires path.py package)
100
100
101 Any values which require transformations are computed only once and
101 Any values which require transformations are computed only once and
102 cached."""
102 cached."""
103
103
104 def get_list(self):
104 def get_list(self):
105 return self
105 return self
106
106
107 l = list = property(get_list)
107 l = list = property(get_list)
108
108
109 def get_spstr(self):
109 def get_spstr(self):
110 try:
110 try:
111 return self.__spstr
111 return self.__spstr
112 except AttributeError:
112 except AttributeError:
113 self.__spstr = ' '.join(self)
113 self.__spstr = ' '.join(self)
114 return self.__spstr
114 return self.__spstr
115
115
116 s = spstr = property(get_spstr)
116 s = spstr = property(get_spstr)
117
117
118 def get_nlstr(self):
118 def get_nlstr(self):
119 try:
119 try:
120 return self.__nlstr
120 return self.__nlstr
121 except AttributeError:
121 except AttributeError:
122 self.__nlstr = '\n'.join(self)
122 self.__nlstr = '\n'.join(self)
123 return self.__nlstr
123 return self.__nlstr
124
124
125 n = nlstr = property(get_nlstr)
125 n = nlstr = property(get_nlstr)
126
126
127 def get_paths(self):
127 def get_paths(self):
128 try:
128 try:
129 return self.__paths
129 return self.__paths
130 except AttributeError:
130 except AttributeError:
131 self.__paths = [Path(p) for p in self if os.path.exists(p)]
131 self.__paths = [Path(p) for p in self if os.path.exists(p)]
132 return self.__paths
132 return self.__paths
133
133
134 p = paths = property(get_paths)
134 p = paths = property(get_paths)
135
135
136 def grep(self, pattern, prune = False, field = None):
136 def grep(self, pattern, prune = False, field = None):
137 """ Return all strings matching 'pattern' (a regex or callable)
137 """ Return all strings matching 'pattern' (a regex or callable)
138
138
139 This is case-insensitive. If prune is true, return all items
139 This is case-insensitive. If prune is true, return all items
140 NOT matching the pattern.
140 NOT matching the pattern.
141
141
142 If field is specified, the match must occur in the specified
142 If field is specified, the match must occur in the specified
143 whitespace-separated field.
143 whitespace-separated field.
144
144
145 Examples::
145 Examples::
146
146
147 a.grep( lambda x: x.startswith('C') )
147 a.grep( lambda x: x.startswith('C') )
148 a.grep('Cha.*log', prune=1)
148 a.grep('Cha.*log', prune=1)
149 a.grep('chm', field=-1)
149 a.grep('chm', field=-1)
150 """
150 """
151
151
152 def match_target(s):
152 def match_target(s):
153 if field is None:
153 if field is None:
154 return s
154 return s
155 parts = s.split()
155 parts = s.split()
156 try:
156 try:
157 tgt = parts[field]
157 tgt = parts[field]
158 return tgt
158 return tgt
159 except IndexError:
159 except IndexError:
160 return ""
160 return ""
161
161
162 if isinstance(pattern, str):
162 if isinstance(pattern, str):
163 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
163 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
164 else:
164 else:
165 pred = pattern
165 pred = pattern
166 if not prune:
166 if not prune:
167 return SList([el for el in self if pred(match_target(el))])
167 return SList([el for el in self if pred(match_target(el))])
168 else:
168 else:
169 return SList([el for el in self if not pred(match_target(el))])
169 return SList([el for el in self if not pred(match_target(el))])
170
170
171 def fields(self, *fields):
171 def fields(self, *fields):
172 """ Collect whitespace-separated fields from string list
172 """ Collect whitespace-separated fields from string list
173
173
174 Allows quick awk-like usage of string lists.
174 Allows quick awk-like usage of string lists.
175
175
176 Example data (in var a, created by 'a = !ls -l')::
176 Example data (in var a, created by 'a = !ls -l')::
177
177
178 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
178 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
179 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
179 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
180
180
181 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
181 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
182 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
182 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
183 (note the joining by space).
183 (note the joining by space).
184 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
184 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
185
185
186 IndexErrors are ignored.
186 IndexErrors are ignored.
187
187
188 Without args, fields() just split()'s the strings.
188 Without args, fields() just split()'s the strings.
189 """
189 """
190 if len(fields) == 0:
190 if len(fields) == 0:
191 return [el.split() for el in self]
191 return [el.split() for el in self]
192
192
193 res = SList()
193 res = SList()
194 for el in [f.split() for f in self]:
194 for el in [f.split() for f in self]:
195 lineparts = []
195 lineparts = []
196
196
197 for fd in fields:
197 for fd in fields:
198 try:
198 try:
199 lineparts.append(el[fd])
199 lineparts.append(el[fd])
200 except IndexError:
200 except IndexError:
201 pass
201 pass
202 if lineparts:
202 if lineparts:
203 res.append(" ".join(lineparts))
203 res.append(" ".join(lineparts))
204
204
205 return res
205 return res
206
206
207 def sort(self,field= None, nums = False):
207 def sort(self,field= None, nums = False):
208 """ sort by specified fields (see fields())
208 """ sort by specified fields (see fields())
209
209
210 Example::
210 Example::
211
211
212 a.sort(1, nums = True)
212 a.sort(1, nums = True)
213
213
214 Sorts a by second field, in numerical order (so that 21 > 3)
214 Sorts a by second field, in numerical order (so that 21 > 3)
215
215
216 """
216 """
217
217
218 #decorate, sort, undecorate
218 #decorate, sort, undecorate
219 if field is not None:
219 if field is not None:
220 dsu = [[SList([line]).fields(field), line] for line in self]
220 dsu = [[SList([line]).fields(field), line] for line in self]
221 else:
221 else:
222 dsu = [[line, line] for line in self]
222 dsu = [[line, line] for line in self]
223 if nums:
223 if nums:
224 for i in range(len(dsu)):
224 for i in range(len(dsu)):
225 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
225 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
226 try:
226 try:
227 n = int(numstr)
227 n = int(numstr)
228 except ValueError:
228 except ValueError:
229 n = 0
229 n = 0
230 dsu[i][0] = n
230 dsu[i][0] = n
231
231
232
232
233 dsu.sort()
233 dsu.sort()
234 return SList([t[1] for t in dsu])
234 return SList([t[1] for t in dsu])
235
235
236
236
237 # FIXME: We need to reimplement type specific displayhook and then add this
237 # FIXME: We need to reimplement type specific displayhook and then add this
238 # back as a custom printer. This should also be moved outside utils into the
238 # back as a custom printer. This should also be moved outside utils into the
239 # core.
239 # core.
240
240
241 # def print_slist(arg):
241 # def print_slist(arg):
242 # """ Prettier (non-repr-like) and more informative printer for SList """
242 # """ Prettier (non-repr-like) and more informative printer for SList """
243 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
243 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
244 # if hasattr(arg, 'hideonce') and arg.hideonce:
244 # if hasattr(arg, 'hideonce') and arg.hideonce:
245 # arg.hideonce = False
245 # arg.hideonce = False
246 # return
246 # return
247 #
247 #
248 # nlprint(arg) # This was a nested list printer, now removed.
248 # nlprint(arg) # This was a nested list printer, now removed.
249 #
249 #
250 # print_slist = result_display.when_type(SList)(print_slist)
250 # print_slist = result_display.when_type(SList)(print_slist)
251
251
252
252
253 def indent(instr,nspaces=4, ntabs=0, flatten=False):
253 def indent(instr,nspaces=4, ntabs=0, flatten=False):
254 """Indent a string a given number of spaces or tabstops.
254 """Indent a string a given number of spaces or tabstops.
255
255
256 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
256 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
257
257
258 Parameters
258 Parameters
259 ----------
259 ----------
260
260
261 instr : basestring
261 instr : basestring
262 The string to be indented.
262 The string to be indented.
263 nspaces : int (default: 4)
263 nspaces : int (default: 4)
264 The number of spaces to be indented.
264 The number of spaces to be indented.
265 ntabs : int (default: 0)
265 ntabs : int (default: 0)
266 The number of tabs to be indented.
266 The number of tabs to be indented.
267 flatten : bool (default: False)
267 flatten : bool (default: False)
268 Whether to scrub existing indentation. If True, all lines will be
268 Whether to scrub existing indentation. If True, all lines will be
269 aligned to the same indentation. If False, existing indentation will
269 aligned to the same indentation. If False, existing indentation will
270 be strictly increased.
270 be strictly increased.
271
271
272 Returns
272 Returns
273 -------
273 -------
274
274
275 str|unicode : string indented by ntabs and nspaces.
275 str|unicode : string indented by ntabs and nspaces.
276
276
277 """
277 """
278 if instr is None:
278 if instr is None:
279 return
279 return
280 ind = '\t'*ntabs+' '*nspaces
280 ind = '\t'*ntabs+' '*nspaces
281 if flatten:
281 if flatten:
282 pat = re.compile(r'^\s*', re.MULTILINE)
282 pat = re.compile(r'^\s*', re.MULTILINE)
283 else:
283 else:
284 pat = re.compile(r'^', re.MULTILINE)
284 pat = re.compile(r'^', re.MULTILINE)
285 outstr = re.sub(pat, ind, instr)
285 outstr = re.sub(pat, ind, instr)
286 if outstr.endswith(os.linesep+ind):
286 if outstr.endswith(os.linesep+ind):
287 return outstr[:-len(ind)]
287 return outstr[:-len(ind)]
288 else:
288 else:
289 return outstr
289 return outstr
290
290
291
291
292 def list_strings(arg):
292 def list_strings(arg):
293 """Always return a list of strings, given a string or list of strings
293 """Always return a list of strings, given a string or list of strings
294 as input.
294 as input.
295
295
296 Examples
296 Examples
297 --------
297 --------
298 ::
298 ::
299
299
300 In [7]: list_strings('A single string')
300 In [7]: list_strings('A single string')
301 Out[7]: ['A single string']
301 Out[7]: ['A single string']
302
302
303 In [8]: list_strings(['A single string in a list'])
303 In [8]: list_strings(['A single string in a list'])
304 Out[8]: ['A single string in a list']
304 Out[8]: ['A single string in a list']
305
305
306 In [9]: list_strings(['A','list','of','strings'])
306 In [9]: list_strings(['A','list','of','strings'])
307 Out[9]: ['A', 'list', 'of', 'strings']
307 Out[9]: ['A', 'list', 'of', 'strings']
308 """
308 """
309
309
310 if isinstance(arg, str):
310 if isinstance(arg, str):
311 return [arg]
311 return [arg]
312 else:
312 else:
313 return arg
313 return arg
314
314
315
315
316 def marquee(txt='',width=78,mark='*'):
316 def marquee(txt='',width=78,mark='*'):
317 """Return the input string centered in a 'marquee'.
317 """Return the input string centered in a 'marquee'.
318
318
319 Examples
319 Examples
320 --------
320 --------
321 ::
321 ::
322
322
323 In [16]: marquee('A test',40)
323 In [16]: marquee('A test',40)
324 Out[16]: '**************** A test ****************'
324 Out[16]: '**************** A test ****************'
325
325
326 In [17]: marquee('A test',40,'-')
326 In [17]: marquee('A test',40,'-')
327 Out[17]: '---------------- A test ----------------'
327 Out[17]: '---------------- A test ----------------'
328
328
329 In [18]: marquee('A test',40,' ')
329 In [18]: marquee('A test',40,' ')
330 Out[18]: ' A test '
330 Out[18]: ' A test '
331
331
332 """
332 """
333 if not txt:
333 if not txt:
334 return (mark*width)[:width]
334 return (mark*width)[:width]
335 nmark = (width-len(txt)-2)//len(mark)//2
335 nmark = (width-len(txt)-2)//len(mark)//2
336 if nmark < 0: nmark =0
336 if nmark < 0: nmark =0
337 marks = mark*nmark
337 marks = mark*nmark
338 return '%s %s %s' % (marks,txt,marks)
338 return '%s %s %s' % (marks,txt,marks)
339
339
340
340
341 ini_spaces_re = re.compile(r'^(\s+)')
341 ini_spaces_re = re.compile(r'^(\s+)')
342
342
343 def num_ini_spaces(strng):
343 def num_ini_spaces(strng):
344 """Return the number of initial spaces in a string"""
344 """Return the number of initial spaces in a string"""
345
345
346 ini_spaces = ini_spaces_re.match(strng)
346 ini_spaces = ini_spaces_re.match(strng)
347 if ini_spaces:
347 if ini_spaces:
348 return ini_spaces.end()
348 return ini_spaces.end()
349 else:
349 else:
350 return 0
350 return 0
351
351
352
352
353 def format_screen(strng):
353 def format_screen(strng):
354 """Format a string for screen printing.
354 """Format a string for screen printing.
355
355
356 This removes some latex-type format codes."""
356 This removes some latex-type format codes."""
357 # Paragraph continue
357 # Paragraph continue
358 par_re = re.compile(r'\\$',re.MULTILINE)
358 par_re = re.compile(r'\\$',re.MULTILINE)
359 strng = par_re.sub('',strng)
359 strng = par_re.sub('',strng)
360 return strng
360 return strng
361
361
362
362
363 def dedent(text):
363 def dedent(text):
364 """Equivalent of textwrap.dedent that ignores unindented first line.
364 """Equivalent of textwrap.dedent that ignores unindented first line.
365
365
366 This means it will still dedent strings like:
366 This means it will still dedent strings like:
367 '''foo
367 '''foo
368 is a bar
368 is a bar
369 '''
369 '''
370
370
371 For use in wrap_paragraphs.
371 For use in wrap_paragraphs.
372 """
372 """
373
373
374 if text.startswith('\n'):
374 if text.startswith('\n'):
375 # text starts with blank line, don't ignore the first line
375 # text starts with blank line, don't ignore the first line
376 return textwrap.dedent(text)
376 return textwrap.dedent(text)
377
377
378 # split first line
378 # split first line
379 splits = text.split('\n',1)
379 splits = text.split('\n',1)
380 if len(splits) == 1:
380 if len(splits) == 1:
381 # only one line
381 # only one line
382 return textwrap.dedent(text)
382 return textwrap.dedent(text)
383
383
384 first, rest = splits
384 first, rest = splits
385 # dedent everything but the first line
385 # dedent everything but the first line
386 rest = textwrap.dedent(rest)
386 rest = textwrap.dedent(rest)
387 return '\n'.join([first, rest])
387 return '\n'.join([first, rest])
388
388
389
389
390 def wrap_paragraphs(text, ncols=80):
390 def wrap_paragraphs(text, ncols=80):
391 """Wrap multiple paragraphs to fit a specified width.
391 """Wrap multiple paragraphs to fit a specified width.
392
392
393 This is equivalent to textwrap.wrap, but with support for multiple
393 This is equivalent to textwrap.wrap, but with support for multiple
394 paragraphs, as separated by empty lines.
394 paragraphs, as separated by empty lines.
395
395
396 Returns
396 Returns
397 -------
397 -------
398
398
399 list of complete paragraphs, wrapped to fill `ncols` columns.
399 list of complete paragraphs, wrapped to fill `ncols` columns.
400 """
400 """
401 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
401 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
402 text = dedent(text).strip()
402 text = dedent(text).strip()
403 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
403 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
404 out_ps = []
404 out_ps = []
405 indent_re = re.compile(r'\n\s+', re.MULTILINE)
405 indent_re = re.compile(r'\n\s+', re.MULTILINE)
406 for p in paragraphs:
406 for p in paragraphs:
407 # presume indentation that survives dedent is meaningful formatting,
407 # presume indentation that survives dedent is meaningful formatting,
408 # so don't fill unless text is flush.
408 # so don't fill unless text is flush.
409 if indent_re.search(p) is None:
409 if indent_re.search(p) is None:
410 # wrap paragraph
410 # wrap paragraph
411 p = textwrap.fill(p, ncols)
411 p = textwrap.fill(p, ncols)
412 out_ps.append(p)
412 out_ps.append(p)
413 return out_ps
413 return out_ps
414
414
415
415
416 def long_substr(data):
416 def long_substr(data):
417 """Return the longest common substring in a list of strings.
417 """Return the longest common substring in a list of strings.
418
418
419 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
419 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
420 """
420 """
421 substr = ''
421 substr = ''
422 if len(data) > 1 and len(data[0]) > 0:
422 if len(data) > 1 and len(data[0]) > 0:
423 for i in range(len(data[0])):
423 for i in range(len(data[0])):
424 for j in range(len(data[0])-i+1):
424 for j in range(len(data[0])-i+1):
425 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
425 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
426 substr = data[0][i:i+j]
426 substr = data[0][i:i+j]
427 elif len(data) == 1:
427 elif len(data) == 1:
428 substr = data[0]
428 substr = data[0]
429 return substr
429 return substr
430
430
431
431
432 def strip_email_quotes(text):
432 def strip_email_quotes(text):
433 """Strip leading email quotation characters ('>').
433 """Strip leading email quotation characters ('>').
434
434
435 Removes any combination of leading '>' interspersed with whitespace that
435 Removes any combination of leading '>' interspersed with whitespace that
436 appears *identically* in all lines of the input text.
436 appears *identically* in all lines of the input text.
437
437
438 Parameters
438 Parameters
439 ----------
439 ----------
440 text : str
440 text : str
441
441
442 Examples
442 Examples
443 --------
443 --------
444
444
445 Simple uses::
445 Simple uses::
446
446
447 In [2]: strip_email_quotes('> > text')
447 In [2]: strip_email_quotes('> > text')
448 Out[2]: 'text'
448 Out[2]: 'text'
449
449
450 In [3]: strip_email_quotes('> > text\\n> > more')
450 In [3]: strip_email_quotes('> > text\\n> > more')
451 Out[3]: 'text\\nmore'
451 Out[3]: 'text\\nmore'
452
452
453 Note how only the common prefix that appears in all lines is stripped::
453 Note how only the common prefix that appears in all lines is stripped::
454
454
455 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
455 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
456 Out[4]: '> text\\n> more\\nmore...'
456 Out[4]: '> text\\n> more\\nmore...'
457
457
458 So if any line has no quote marks ('>') , then none are stripped from any
458 So if any line has no quote marks ('>') , then none are stripped from any
459 of them ::
459 of them ::
460
460
461 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
461 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
462 Out[5]: '> > text\\n> > more\\nlast different'
462 Out[5]: '> > text\\n> > more\\nlast different'
463 """
463 """
464 lines = text.splitlines()
464 lines = text.splitlines()
465 matches = set()
465 matches = set()
466 for line in lines:
466 for line in lines:
467 prefix = re.match(r'^(\s*>[ >]*)', line)
467 prefix = re.match(r'^(\s*>[ >]*)', line)
468 if prefix:
468 if prefix:
469 matches.add(prefix.group(1))
469 matches.add(prefix.group(1))
470 else:
470 else:
471 break
471 break
472 else:
472 else:
473 prefix = long_substr(list(matches))
473 prefix = long_substr(list(matches))
474 if prefix:
474 if prefix:
475 strip = len(prefix)
475 strip = len(prefix)
476 text = '\n'.join([ ln[strip:] for ln in lines])
476 text = '\n'.join([ ln[strip:] for ln in lines])
477 return text
477 return text
478
478
479 def strip_ansi(source):
479 def strip_ansi(source):
480 """
480 """
481 Remove ansi escape codes from text.
481 Remove ansi escape codes from text.
482
482
483 Parameters
483 Parameters
484 ----------
484 ----------
485 source : str
485 source : str
486 Source to remove the ansi from
486 Source to remove the ansi from
487 """
487 """
488 return re.sub(r'\033\[(\d|;)+?m', '', source)
488 return re.sub(r'\033\[(\d|;)+?m', '', source)
489
489
490
490
491 class EvalFormatter(Formatter):
491 class EvalFormatter(Formatter):
492 """A String Formatter that allows evaluation of simple expressions.
492 """A String Formatter that allows evaluation of simple expressions.
493
493
494 Note that this version interprets a : as specifying a format string (as per
494 Note that this version interprets a : as specifying a format string (as per
495 standard string formatting), so if slicing is required, you must explicitly
495 standard string formatting), so if slicing is required, you must explicitly
496 create a slice.
496 create a slice.
497
497
498 This is to be used in templating cases, such as the parallel batch
498 This is to be used in templating cases, such as the parallel batch
499 script templates, where simple arithmetic on arguments is useful.
499 script templates, where simple arithmetic on arguments is useful.
500
500
501 Examples
501 Examples
502 --------
502 --------
503 ::
503 ::
504
504
505 In [1]: f = EvalFormatter()
505 In [1]: f = EvalFormatter()
506 In [2]: f.format('{n//4}', n=8)
506 In [2]: f.format('{n//4}', n=8)
507 Out[2]: '2'
507 Out[2]: '2'
508
508
509 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
509 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
510 Out[3]: 'll'
510 Out[3]: 'll'
511 """
511 """
512 def get_field(self, name, args, kwargs):
512 def get_field(self, name, args, kwargs):
513 v = eval(name, kwargs)
513 v = eval(name, kwargs)
514 return v, name
514 return v, name
515
515
516 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
516 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
517 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
517 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
518 # above, it should be possible to remove FullEvalFormatter.
518 # above, it should be possible to remove FullEvalFormatter.
519
519
520 class FullEvalFormatter(Formatter):
520 class FullEvalFormatter(Formatter):
521 """A String Formatter that allows evaluation of simple expressions.
521 """A String Formatter that allows evaluation of simple expressions.
522
522
523 Any time a format key is not found in the kwargs,
523 Any time a format key is not found in the kwargs,
524 it will be tried as an expression in the kwargs namespace.
524 it will be tried as an expression in the kwargs namespace.
525
525
526 Note that this version allows slicing using [1:2], so you cannot specify
526 Note that this version allows slicing using [1:2], so you cannot specify
527 a format string. Use :class:`EvalFormatter` to permit format strings.
527 a format string. Use :class:`EvalFormatter` to permit format strings.
528
528
529 Examples
529 Examples
530 --------
530 --------
531 ::
531 ::
532
532
533 In [1]: f = FullEvalFormatter()
533 In [1]: f = FullEvalFormatter()
534 In [2]: f.format('{n//4}', n=8)
534 In [2]: f.format('{n//4}', n=8)
535 Out[2]: '2'
535 Out[2]: '2'
536
536
537 In [3]: f.format('{list(range(5))[2:4]}')
537 In [3]: f.format('{list(range(5))[2:4]}')
538 Out[3]: '[2, 3]'
538 Out[3]: '[2, 3]'
539
539
540 In [4]: f.format('{3*2}')
540 In [4]: f.format('{3*2}')
541 Out[4]: '6'
541 Out[4]: '6'
542 """
542 """
543 # copied from Formatter._vformat with minor changes to allow eval
543 # copied from Formatter._vformat with minor changes to allow eval
544 # and replace the format_spec code with slicing
544 # and replace the format_spec code with slicing
545 def vformat(self, format_string, args, kwargs):
545 def vformat(self, format_string, args, kwargs):
546 result = []
546 result = []
547 for literal_text, field_name, format_spec, conversion in \
547 for literal_text, field_name, format_spec, conversion in \
548 self.parse(format_string):
548 self.parse(format_string):
549
549
550 # output the literal text
550 # output the literal text
551 if literal_text:
551 if literal_text:
552 result.append(literal_text)
552 result.append(literal_text)
553
553
554 # if there's a field, output it
554 # if there's a field, output it
555 if field_name is not None:
555 if field_name is not None:
556 # this is some markup, find the object and do
556 # this is some markup, find the object and do
557 # the formatting
557 # the formatting
558
558
559 if format_spec:
559 if format_spec:
560 # override format spec, to allow slicing:
560 # override format spec, to allow slicing:
561 field_name = ':'.join([field_name, format_spec])
561 field_name = ':'.join([field_name, format_spec])
562
562
563 # eval the contents of the field for the object
563 # eval the contents of the field for the object
564 # to be formatted
564 # to be formatted
565 obj = eval(field_name, kwargs)
565 obj = eval(field_name, kwargs)
566
566
567 # do any conversion on the resulting object
567 # do any conversion on the resulting object
568 obj = self.convert_field(obj, conversion)
568 obj = self.convert_field(obj, conversion)
569
569
570 # format the object and append to the result
570 # format the object and append to the result
571 result.append(self.format_field(obj, ''))
571 result.append(self.format_field(obj, ''))
572
572
573 return u''.join(py3compat.cast_unicode(s) for s in result)
573 return ''.join(py3compat.cast_unicode(s) for s in result)
574
574
575
575
576 class DollarFormatter(FullEvalFormatter):
576 class DollarFormatter(FullEvalFormatter):
577 """Formatter allowing Itpl style $foo replacement, for names and attribute
577 """Formatter allowing Itpl style $foo replacement, for names and attribute
578 access only. Standard {foo} replacement also works, and allows full
578 access only. Standard {foo} replacement also works, and allows full
579 evaluation of its arguments.
579 evaluation of its arguments.
580
580
581 Examples
581 Examples
582 --------
582 --------
583 ::
583 ::
584
584
585 In [1]: f = DollarFormatter()
585 In [1]: f = DollarFormatter()
586 In [2]: f.format('{n//4}', n=8)
586 In [2]: f.format('{n//4}', n=8)
587 Out[2]: '2'
587 Out[2]: '2'
588
588
589 In [3]: f.format('23 * 76 is $result', result=23*76)
589 In [3]: f.format('23 * 76 is $result', result=23*76)
590 Out[3]: '23 * 76 is 1748'
590 Out[3]: '23 * 76 is 1748'
591
591
592 In [4]: f.format('$a or {b}', a=1, b=2)
592 In [4]: f.format('$a or {b}', a=1, b=2)
593 Out[4]: '1 or 2'
593 Out[4]: '1 or 2'
594 """
594 """
595 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
595 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
596 def parse(self, fmt_string):
596 def parse(self, fmt_string):
597 for literal_txt, field_name, format_spec, conversion \
597 for literal_txt, field_name, format_spec, conversion \
598 in Formatter.parse(self, fmt_string):
598 in Formatter.parse(self, fmt_string):
599
599
600 # Find $foo patterns in the literal text.
600 # Find $foo patterns in the literal text.
601 continue_from = 0
601 continue_from = 0
602 txt = ""
602 txt = ""
603 for m in self._dollar_pattern.finditer(literal_txt):
603 for m in self._dollar_pattern.finditer(literal_txt):
604 new_txt, new_field = m.group(1,2)
604 new_txt, new_field = m.group(1,2)
605 # $$foo --> $foo
605 # $$foo --> $foo
606 if new_field.startswith("$"):
606 if new_field.startswith("$"):
607 txt += new_txt + new_field
607 txt += new_txt + new_field
608 else:
608 else:
609 yield (txt + new_txt, new_field, "", None)
609 yield (txt + new_txt, new_field, "", None)
610 txt = ""
610 txt = ""
611 continue_from = m.end()
611 continue_from = m.end()
612
612
613 # Re-yield the {foo} style pattern
613 # Re-yield the {foo} style pattern
614 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
614 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
615
615
616 #-----------------------------------------------------------------------------
616 #-----------------------------------------------------------------------------
617 # Utils to columnize a list of string
617 # Utils to columnize a list of string
618 #-----------------------------------------------------------------------------
618 #-----------------------------------------------------------------------------
619
619
620 def _col_chunks(l, max_rows, row_first=False):
620 def _col_chunks(l, max_rows, row_first=False):
621 """Yield successive max_rows-sized column chunks from l."""
621 """Yield successive max_rows-sized column chunks from l."""
622 if row_first:
622 if row_first:
623 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
623 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
624 for i in range(ncols):
624 for i in range(ncols):
625 yield [l[j] for j in range(i, len(l), ncols)]
625 yield [l[j] for j in range(i, len(l), ncols)]
626 else:
626 else:
627 for i in range(0, len(l), max_rows):
627 for i in range(0, len(l), max_rows):
628 yield l[i:(i + max_rows)]
628 yield l[i:(i + max_rows)]
629
629
630
630
631 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
631 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
632 """Calculate optimal info to columnize a list of string"""
632 """Calculate optimal info to columnize a list of string"""
633 for max_rows in range(1, len(rlist) + 1):
633 for max_rows in range(1, len(rlist) + 1):
634 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
634 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
635 sumlength = sum(col_widths)
635 sumlength = sum(col_widths)
636 ncols = len(col_widths)
636 ncols = len(col_widths)
637 if sumlength + separator_size * (ncols - 1) <= displaywidth:
637 if sumlength + separator_size * (ncols - 1) <= displaywidth:
638 break
638 break
639 return {'num_columns': ncols,
639 return {'num_columns': ncols,
640 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
640 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
641 'max_rows': max_rows,
641 'max_rows': max_rows,
642 'column_widths': col_widths
642 'column_widths': col_widths
643 }
643 }
644
644
645
645
646 def _get_or_default(mylist, i, default=None):
646 def _get_or_default(mylist, i, default=None):
647 """return list item number, or default if don't exist"""
647 """return list item number, or default if don't exist"""
648 if i >= len(mylist):
648 if i >= len(mylist):
649 return default
649 return default
650 else :
650 else :
651 return mylist[i]
651 return mylist[i]
652
652
653
653
654 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
654 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
655 """Returns a nested list, and info to columnize items
655 """Returns a nested list, and info to columnize items
656
656
657 Parameters
657 Parameters
658 ----------
658 ----------
659
659
660 items
660 items
661 list of strings to columize
661 list of strings to columize
662 row_first : (default False)
662 row_first : (default False)
663 Whether to compute columns for a row-first matrix instead of
663 Whether to compute columns for a row-first matrix instead of
664 column-first (default).
664 column-first (default).
665 empty : (default None)
665 empty : (default None)
666 default value to fill list if needed
666 default value to fill list if needed
667 separator_size : int (default=2)
667 separator_size : int (default=2)
668 How much caracters will be used as a separation between each columns.
668 How much caracters will be used as a separation between each columns.
669 displaywidth : int (default=80)
669 displaywidth : int (default=80)
670 The width of the area onto wich the columns should enter
670 The width of the area onto wich the columns should enter
671
671
672 Returns
672 Returns
673 -------
673 -------
674
674
675 strings_matrix
675 strings_matrix
676
676
677 nested list of string, the outer most list contains as many list as
677 nested list of string, the outer most list contains as many list as
678 rows, the innermost lists have each as many element as colums. If the
678 rows, the innermost lists have each as many element as colums. If the
679 total number of elements in `items` does not equal the product of
679 total number of elements in `items` does not equal the product of
680 rows*columns, the last element of some lists are filled with `None`.
680 rows*columns, the last element of some lists are filled with `None`.
681
681
682 dict_info
682 dict_info
683 some info to make columnize easier:
683 some info to make columnize easier:
684
684
685 num_columns
685 num_columns
686 number of columns
686 number of columns
687 max_rows
687 max_rows
688 maximum number of rows (final number may be less)
688 maximum number of rows (final number may be less)
689 column_widths
689 column_widths
690 list of with of each columns
690 list of with of each columns
691 optimal_separator_width
691 optimal_separator_width
692 best separator width between columns
692 best separator width between columns
693
693
694 Examples
694 Examples
695 --------
695 --------
696 ::
696 ::
697
697
698 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
698 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
699 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
699 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
700 In [3]: list
700 In [3]: list
701 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
701 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
702 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
702 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
703 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
703 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
704 Out[5]: True
704 Out[5]: True
705 """
705 """
706 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
706 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
707 nrow, ncol = info['max_rows'], info['num_columns']
707 nrow, ncol = info['max_rows'], info['num_columns']
708 if row_first:
708 if row_first:
709 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
709 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
710 else:
710 else:
711 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
711 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
712
712
713
713
714 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
714 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
715 """ Transform a list of strings into a single string with columns.
715 """ Transform a list of strings into a single string with columns.
716
716
717 Parameters
717 Parameters
718 ----------
718 ----------
719 items : sequence of strings
719 items : sequence of strings
720 The strings to process.
720 The strings to process.
721
721
722 row_first : (default False)
722 row_first : (default False)
723 Whether to compute columns for a row-first matrix instead of
723 Whether to compute columns for a row-first matrix instead of
724 column-first (default).
724 column-first (default).
725
725
726 separator : str, optional [default is two spaces]
726 separator : str, optional [default is two spaces]
727 The string that separates columns.
727 The string that separates columns.
728
728
729 displaywidth : int, optional [default is 80]
729 displaywidth : int, optional [default is 80]
730 Width of the display in number of characters.
730 Width of the display in number of characters.
731
731
732 Returns
732 Returns
733 -------
733 -------
734 The formatted string.
734 The formatted string.
735 """
735 """
736 if not items:
736 if not items:
737 return '\n'
737 return '\n'
738 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
738 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
739 if spread:
739 if spread:
740 separator = separator.ljust(int(info['optimal_separator_width']))
740 separator = separator.ljust(int(info['optimal_separator_width']))
741 fmatrix = [filter(None, x) for x in matrix]
741 fmatrix = [filter(None, x) for x in matrix]
742 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
742 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
743 return '\n'.join(map(sjoin, fmatrix))+'\n'
743 return '\n'.join(map(sjoin, fmatrix))+'\n'
744
744
745
745
746 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
746 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
747 """
747 """
748 Return a string with a natural enumeration of items
748 Return a string with a natural enumeration of items
749
749
750 >>> get_text_list(['a', 'b', 'c', 'd'])
750 >>> get_text_list(['a', 'b', 'c', 'd'])
751 'a, b, c and d'
751 'a, b, c and d'
752 >>> get_text_list(['a', 'b', 'c'], ' or ')
752 >>> get_text_list(['a', 'b', 'c'], ' or ')
753 'a, b or c'
753 'a, b or c'
754 >>> get_text_list(['a', 'b', 'c'], ', ')
754 >>> get_text_list(['a', 'b', 'c'], ', ')
755 'a, b, c'
755 'a, b, c'
756 >>> get_text_list(['a', 'b'], ' or ')
756 >>> get_text_list(['a', 'b'], ' or ')
757 'a or b'
757 'a or b'
758 >>> get_text_list(['a'])
758 >>> get_text_list(['a'])
759 'a'
759 'a'
760 >>> get_text_list([])
760 >>> get_text_list([])
761 ''
761 ''
762 >>> get_text_list(['a', 'b'], wrap_item_with="`")
762 >>> get_text_list(['a', 'b'], wrap_item_with="`")
763 '`a` and `b`'
763 '`a` and `b`'
764 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
764 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
765 'a + b + c = d'
765 'a + b + c = d'
766 """
766 """
767 if len(list_) == 0:
767 if len(list_) == 0:
768 return ''
768 return ''
769 if wrap_item_with:
769 if wrap_item_with:
770 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
770 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
771 item in list_]
771 item in list_]
772 if len(list_) == 1:
772 if len(list_) == 1:
773 return list_[0]
773 return list_[0]
774 return '%s%s%s' % (
774 return '%s%s%s' % (
775 sep.join(i for i in list_[:-1]),
775 sep.join(i for i in list_[:-1]),
776 last_sep, list_[-1])
776 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now