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