##// END OF EJS Templates
Various minor docs fixes
Thomas Kluyver -
Show More
@@ -1,444 +1,446 b''
1 """Generic testing tools.
1 """Generic testing tools.
2
2
3 Authors
3 Authors
4 -------
4 -------
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 """
6 """
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2009 The IPython Development Team
11 # Copyright (C) 2009 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 import os
21 import os
22 import re
22 import re
23 import sys
23 import sys
24 import tempfile
24 import tempfile
25
25
26 from contextlib import contextmanager
26 from contextlib import contextmanager
27 from io import StringIO
27 from io import StringIO
28 from subprocess import Popen, PIPE
28 from subprocess import Popen, PIPE
29
29
30 try:
30 try:
31 # These tools are used by parts of the runtime, so we make the nose
31 # These tools are used by parts of the runtime, so we make the nose
32 # dependency optional at this point. Nose is a hard dependency to run the
32 # dependency optional at this point. Nose is a hard dependency to run the
33 # test suite, but NOT to use ipython itself.
33 # test suite, but NOT to use ipython itself.
34 import nose.tools as nt
34 import nose.tools as nt
35 has_nose = True
35 has_nose = True
36 except ImportError:
36 except ImportError:
37 has_nose = False
37 has_nose = False
38
38
39 from IPython.config.loader import Config
39 from IPython.config.loader import Config
40 from IPython.utils.process import get_output_error_code
40 from IPython.utils.process import get_output_error_code
41 from IPython.utils.text import list_strings
41 from IPython.utils.text import list_strings
42 from IPython.utils.io import temp_pyfile, Tee
42 from IPython.utils.io import temp_pyfile, Tee
43 from IPython.utils import py3compat
43 from IPython.utils import py3compat
44 from IPython.utils.encoding import DEFAULT_ENCODING
44 from IPython.utils.encoding import DEFAULT_ENCODING
45
45
46 from . import decorators as dec
46 from . import decorators as dec
47 from . import skipdoctest
47 from . import skipdoctest
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Functions and classes
50 # Functions and classes
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 # The docstring for full_path doctests differently on win32 (different path
53 # The docstring for full_path doctests differently on win32 (different path
54 # separator) so just skip the doctest there. The example remains informative.
54 # separator) so just skip the doctest there. The example remains informative.
55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
56
56
57 @doctest_deco
57 @doctest_deco
58 def full_path(startPath,files):
58 def full_path(startPath,files):
59 """Make full paths for all the listed files, based on startPath.
59 """Make full paths for all the listed files, based on startPath.
60
60
61 Only the base part of startPath is kept, since this routine is typically
61 Only the base part of startPath is kept, since this routine is typically
62 used with a script's __file__ variable as startPath. The base of startPath
62 used with a script's ``__file__`` variable as startPath. The base of startPath
63 is then prepended to all the listed files, forming the output list.
63 is then prepended to all the listed files, forming the output list.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 startPath : string
67 startPath : string
68 Initial path to use as the base for the results. This path is split
68 Initial path to use as the base for the results. This path is split
69 using os.path.split() and only its first component is kept.
69 using os.path.split() and only its first component is kept.
70
70
71 files : string or list
71 files : string or list
72 One or more files.
72 One or more files.
73
73
74 Examples
74 Examples
75 --------
75 --------
76
76
77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
78 ['/foo/a.txt', '/foo/b.txt']
78 ['/foo/a.txt', '/foo/b.txt']
79
79
80 >>> full_path('/foo',['a.txt','b.txt'])
80 >>> full_path('/foo',['a.txt','b.txt'])
81 ['/a.txt', '/b.txt']
81 ['/a.txt', '/b.txt']
82
82
83 If a single file is given, the output is still a list:
83 If a single file is given, the output is still a list::
84 >>> full_path('/foo','a.txt')
84
85 ['/a.txt']
85 >>> full_path('/foo','a.txt')
86 ['/a.txt']
86 """
87 """
87
88
88 files = list_strings(files)
89 files = list_strings(files)
89 base = os.path.split(startPath)[0]
90 base = os.path.split(startPath)[0]
90 return [ os.path.join(base,f) for f in files ]
91 return [ os.path.join(base,f) for f in files ]
91
92
92
93
93 def parse_test_output(txt):
94 def parse_test_output(txt):
94 """Parse the output of a test run and return errors, failures.
95 """Parse the output of a test run and return errors, failures.
95
96
96 Parameters
97 Parameters
97 ----------
98 ----------
98 txt : str
99 txt : str
99 Text output of a test run, assumed to contain a line of one of the
100 Text output of a test run, assumed to contain a line of one of the
100 following forms::
101 following forms::
101
102
102 'FAILED (errors=1)'
103 'FAILED (errors=1)'
103 'FAILED (failures=1)'
104 'FAILED (failures=1)'
104 'FAILED (errors=1, failures=1)'
105 'FAILED (errors=1, failures=1)'
105
106
106 Returns
107 Returns
107 -------
108 -------
108 nerr, nfail: number of errors and failures.
109 nerr, nfail
110 number of errors and failures.
109 """
111 """
110
112
111 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
113 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
112 if err_m:
114 if err_m:
113 nerr = int(err_m.group(1))
115 nerr = int(err_m.group(1))
114 nfail = 0
116 nfail = 0
115 return nerr, nfail
117 return nerr, nfail
116
118
117 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
119 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
118 if fail_m:
120 if fail_m:
119 nerr = 0
121 nerr = 0
120 nfail = int(fail_m.group(1))
122 nfail = int(fail_m.group(1))
121 return nerr, nfail
123 return nerr, nfail
122
124
123 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
125 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
124 re.MULTILINE)
126 re.MULTILINE)
125 if both_m:
127 if both_m:
126 nerr = int(both_m.group(1))
128 nerr = int(both_m.group(1))
127 nfail = int(both_m.group(2))
129 nfail = int(both_m.group(2))
128 return nerr, nfail
130 return nerr, nfail
129
131
130 # If the input didn't match any of these forms, assume no error/failures
132 # If the input didn't match any of these forms, assume no error/failures
131 return 0, 0
133 return 0, 0
132
134
133
135
134 # So nose doesn't think this is a test
136 # So nose doesn't think this is a test
135 parse_test_output.__test__ = False
137 parse_test_output.__test__ = False
136
138
137
139
138 def default_argv():
140 def default_argv():
139 """Return a valid default argv for creating testing instances of ipython"""
141 """Return a valid default argv for creating testing instances of ipython"""
140
142
141 return ['--quick', # so no config file is loaded
143 return ['--quick', # so no config file is loaded
142 # Other defaults to minimize side effects on stdout
144 # Other defaults to minimize side effects on stdout
143 '--colors=NoColor', '--no-term-title','--no-banner',
145 '--colors=NoColor', '--no-term-title','--no-banner',
144 '--autocall=0']
146 '--autocall=0']
145
147
146
148
147 def default_config():
149 def default_config():
148 """Return a config object with good defaults for testing."""
150 """Return a config object with good defaults for testing."""
149 config = Config()
151 config = Config()
150 config.TerminalInteractiveShell.colors = 'NoColor'
152 config.TerminalInteractiveShell.colors = 'NoColor'
151 config.TerminalTerminalInteractiveShell.term_title = False,
153 config.TerminalTerminalInteractiveShell.term_title = False,
152 config.TerminalInteractiveShell.autocall = 0
154 config.TerminalInteractiveShell.autocall = 0
153 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
155 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
154 config.HistoryManager.db_cache_size = 10000
156 config.HistoryManager.db_cache_size = 10000
155 return config
157 return config
156
158
157
159
158 def get_ipython_cmd(as_string=False):
160 def get_ipython_cmd(as_string=False):
159 """
161 """
160 Return appropriate IPython command line name. By default, this will return
162 Return appropriate IPython command line name. By default, this will return
161 a list that can be used with subprocess.Popen, for example, but passing
163 a list that can be used with subprocess.Popen, for example, but passing
162 `as_string=True` allows for returning the IPython command as a string.
164 `as_string=True` allows for returning the IPython command as a string.
163
165
164 Parameters
166 Parameters
165 ----------
167 ----------
166 as_string: bool
168 as_string: bool
167 Flag to allow to return the command as a string.
169 Flag to allow to return the command as a string.
168 """
170 """
169 ipython_cmd = [sys.executable, "-m", "IPython"]
171 ipython_cmd = [sys.executable, "-m", "IPython"]
170
172
171 if as_string:
173 if as_string:
172 ipython_cmd = " ".join(ipython_cmd)
174 ipython_cmd = " ".join(ipython_cmd)
173
175
174 return ipython_cmd
176 return ipython_cmd
175
177
176 def ipexec(fname, options=None):
178 def ipexec(fname, options=None):
177 """Utility to call 'ipython filename'.
179 """Utility to call 'ipython filename'.
178
180
179 Starts IPython with a minimal and safe configuration to make startup as fast
181 Starts IPython with a minimal and safe configuration to make startup as fast
180 as possible.
182 as possible.
181
183
182 Note that this starts IPython in a subprocess!
184 Note that this starts IPython in a subprocess!
183
185
184 Parameters
186 Parameters
185 ----------
187 ----------
186 fname : str
188 fname : str
187 Name of file to be executed (should have .py or .ipy extension).
189 Name of file to be executed (should have .py or .ipy extension).
188
190
189 options : optional, list
191 options : optional, list
190 Extra command-line flags to be passed to IPython.
192 Extra command-line flags to be passed to IPython.
191
193
192 Returns
194 Returns
193 -------
195 -------
194 (stdout, stderr) of ipython subprocess.
196 (stdout, stderr) of ipython subprocess.
195 """
197 """
196 if options is None: options = []
198 if options is None: options = []
197
199
198 # For these subprocess calls, eliminate all prompt printing so we only see
200 # For these subprocess calls, eliminate all prompt printing so we only see
199 # output from script execution
201 # output from script execution
200 prompt_opts = [ '--PromptManager.in_template=""',
202 prompt_opts = [ '--PromptManager.in_template=""',
201 '--PromptManager.in2_template=""',
203 '--PromptManager.in2_template=""',
202 '--PromptManager.out_template=""'
204 '--PromptManager.out_template=""'
203 ]
205 ]
204 cmdargs = default_argv() + prompt_opts + options
206 cmdargs = default_argv() + prompt_opts + options
205
207
206 test_dir = os.path.dirname(__file__)
208 test_dir = os.path.dirname(__file__)
207
209
208 ipython_cmd = get_ipython_cmd()
210 ipython_cmd = get_ipython_cmd()
209 # Absolute path for filename
211 # Absolute path for filename
210 full_fname = os.path.join(test_dir, fname)
212 full_fname = os.path.join(test_dir, fname)
211 full_cmd = ipython_cmd + cmdargs + [full_fname]
213 full_cmd = ipython_cmd + cmdargs + [full_fname]
212 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
214 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
213 out, err = p.communicate()
215 out, err = p.communicate()
214 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
216 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
215 # `import readline` causes 'ESC[?1034h' to be output sometimes,
217 # `import readline` causes 'ESC[?1034h' to be output sometimes,
216 # so strip that out before doing comparisons
218 # so strip that out before doing comparisons
217 if out:
219 if out:
218 out = re.sub(r'\x1b\[[^h]+h', '', out)
220 out = re.sub(r'\x1b\[[^h]+h', '', out)
219 return out, err
221 return out, err
220
222
221
223
222 def ipexec_validate(fname, expected_out, expected_err='',
224 def ipexec_validate(fname, expected_out, expected_err='',
223 options=None):
225 options=None):
224 """Utility to call 'ipython filename' and validate output/error.
226 """Utility to call 'ipython filename' and validate output/error.
225
227
226 This function raises an AssertionError if the validation fails.
228 This function raises an AssertionError if the validation fails.
227
229
228 Note that this starts IPython in a subprocess!
230 Note that this starts IPython in a subprocess!
229
231
230 Parameters
232 Parameters
231 ----------
233 ----------
232 fname : str
234 fname : str
233 Name of the file to be executed (should have .py or .ipy extension).
235 Name of the file to be executed (should have .py or .ipy extension).
234
236
235 expected_out : str
237 expected_out : str
236 Expected stdout of the process.
238 Expected stdout of the process.
237
239
238 expected_err : optional, str
240 expected_err : optional, str
239 Expected stderr of the process.
241 Expected stderr of the process.
240
242
241 options : optional, list
243 options : optional, list
242 Extra command-line flags to be passed to IPython.
244 Extra command-line flags to be passed to IPython.
243
245
244 Returns
246 Returns
245 -------
247 -------
246 None
248 None
247 """
249 """
248
250
249 import nose.tools as nt
251 import nose.tools as nt
250
252
251 out, err = ipexec(fname, options)
253 out, err = ipexec(fname, options)
252 #print 'OUT', out # dbg
254 #print 'OUT', out # dbg
253 #print 'ERR', err # dbg
255 #print 'ERR', err # dbg
254 # If there are any errors, we must check those befor stdout, as they may be
256 # If there are any errors, we must check those befor stdout, as they may be
255 # more informative than simply having an empty stdout.
257 # more informative than simply having an empty stdout.
256 if err:
258 if err:
257 if expected_err:
259 if expected_err:
258 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
260 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
259 else:
261 else:
260 raise ValueError('Running file %r produced error: %r' %
262 raise ValueError('Running file %r produced error: %r' %
261 (fname, err))
263 (fname, err))
262 # If no errors or output on stderr was expected, match stdout
264 # If no errors or output on stderr was expected, match stdout
263 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
265 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
264
266
265
267
266 class TempFileMixin(object):
268 class TempFileMixin(object):
267 """Utility class to create temporary Python/IPython files.
269 """Utility class to create temporary Python/IPython files.
268
270
269 Meant as a mixin class for test cases."""
271 Meant as a mixin class for test cases."""
270
272
271 def mktmp(self, src, ext='.py'):
273 def mktmp(self, src, ext='.py'):
272 """Make a valid python temp file."""
274 """Make a valid python temp file."""
273 fname, f = temp_pyfile(src, ext)
275 fname, f = temp_pyfile(src, ext)
274 self.tmpfile = f
276 self.tmpfile = f
275 self.fname = fname
277 self.fname = fname
276
278
277 def tearDown(self):
279 def tearDown(self):
278 if hasattr(self, 'tmpfile'):
280 if hasattr(self, 'tmpfile'):
279 # If the tmpfile wasn't made because of skipped tests, like in
281 # If the tmpfile wasn't made because of skipped tests, like in
280 # win32, there's nothing to cleanup.
282 # win32, there's nothing to cleanup.
281 self.tmpfile.close()
283 self.tmpfile.close()
282 try:
284 try:
283 os.unlink(self.fname)
285 os.unlink(self.fname)
284 except:
286 except:
285 # On Windows, even though we close the file, we still can't
287 # On Windows, even though we close the file, we still can't
286 # delete it. I have no clue why
288 # delete it. I have no clue why
287 pass
289 pass
288
290
289 pair_fail_msg = ("Testing {0}\n\n"
291 pair_fail_msg = ("Testing {0}\n\n"
290 "In:\n"
292 "In:\n"
291 " {1!r}\n"
293 " {1!r}\n"
292 "Expected:\n"
294 "Expected:\n"
293 " {2!r}\n"
295 " {2!r}\n"
294 "Got:\n"
296 "Got:\n"
295 " {3!r}\n")
297 " {3!r}\n")
296 def check_pairs(func, pairs):
298 def check_pairs(func, pairs):
297 """Utility function for the common case of checking a function with a
299 """Utility function for the common case of checking a function with a
298 sequence of input/output pairs.
300 sequence of input/output pairs.
299
301
300 Parameters
302 Parameters
301 ----------
303 ----------
302 func : callable
304 func : callable
303 The function to be tested. Should accept a single argument.
305 The function to be tested. Should accept a single argument.
304 pairs : iterable
306 pairs : iterable
305 A list of (input, expected_output) tuples.
307 A list of (input, expected_output) tuples.
306
308
307 Returns
309 Returns
308 -------
310 -------
309 None. Raises an AssertionError if any output does not match the expected
311 None. Raises an AssertionError if any output does not match the expected
310 value.
312 value.
311 """
313 """
312 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
314 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
313 for inp, expected in pairs:
315 for inp, expected in pairs:
314 out = func(inp)
316 out = func(inp)
315 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
317 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
316
318
317
319
318 if py3compat.PY3:
320 if py3compat.PY3:
319 MyStringIO = StringIO
321 MyStringIO = StringIO
320 else:
322 else:
321 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
323 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
322 # so we need a class that can handle both.
324 # so we need a class that can handle both.
323 class MyStringIO(StringIO):
325 class MyStringIO(StringIO):
324 def write(self, s):
326 def write(self, s):
325 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
327 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
326 super(MyStringIO, self).write(s)
328 super(MyStringIO, self).write(s)
327
329
328 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
330 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
329 -------
331 -------
330 {2!s}
332 {2!s}
331 -------
333 -------
332 """
334 """
333
335
334 class AssertPrints(object):
336 class AssertPrints(object):
335 """Context manager for testing that code prints certain text.
337 """Context manager for testing that code prints certain text.
336
338
337 Examples
339 Examples
338 --------
340 --------
339 >>> with AssertPrints("abc", suppress=False):
341 >>> with AssertPrints("abc", suppress=False):
340 ... print("abcd")
342 ... print("abcd")
341 ... print("def")
343 ... print("def")
342 ...
344 ...
343 abcd
345 abcd
344 def
346 def
345 """
347 """
346 def __init__(self, s, channel='stdout', suppress=True):
348 def __init__(self, s, channel='stdout', suppress=True):
347 self.s = s
349 self.s = s
348 if isinstance(self.s, py3compat.string_types):
350 if isinstance(self.s, py3compat.string_types):
349 self.s = [self.s]
351 self.s = [self.s]
350 self.channel = channel
352 self.channel = channel
351 self.suppress = suppress
353 self.suppress = suppress
352
354
353 def __enter__(self):
355 def __enter__(self):
354 self.orig_stream = getattr(sys, self.channel)
356 self.orig_stream = getattr(sys, self.channel)
355 self.buffer = MyStringIO()
357 self.buffer = MyStringIO()
356 self.tee = Tee(self.buffer, channel=self.channel)
358 self.tee = Tee(self.buffer, channel=self.channel)
357 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
359 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
358
360
359 def __exit__(self, etype, value, traceback):
361 def __exit__(self, etype, value, traceback):
360 if value is not None:
362 if value is not None:
361 # If an error was raised, don't check anything else
363 # If an error was raised, don't check anything else
362 return False
364 return False
363 self.tee.flush()
365 self.tee.flush()
364 setattr(sys, self.channel, self.orig_stream)
366 setattr(sys, self.channel, self.orig_stream)
365 printed = self.buffer.getvalue()
367 printed = self.buffer.getvalue()
366 for s in self.s:
368 for s in self.s:
367 assert s in printed, notprinted_msg.format(s, self.channel, printed)
369 assert s in printed, notprinted_msg.format(s, self.channel, printed)
368 return False
370 return False
369
371
370 printed_msg = """Found {0!r} in printed output (on {1}):
372 printed_msg = """Found {0!r} in printed output (on {1}):
371 -------
373 -------
372 {2!s}
374 {2!s}
373 -------
375 -------
374 """
376 """
375
377
376 class AssertNotPrints(AssertPrints):
378 class AssertNotPrints(AssertPrints):
377 """Context manager for checking that certain output *isn't* produced.
379 """Context manager for checking that certain output *isn't* produced.
378
380
379 Counterpart of AssertPrints"""
381 Counterpart of AssertPrints"""
380 def __exit__(self, etype, value, traceback):
382 def __exit__(self, etype, value, traceback):
381 if value is not None:
383 if value is not None:
382 # If an error was raised, don't check anything else
384 # If an error was raised, don't check anything else
383 return False
385 return False
384 self.tee.flush()
386 self.tee.flush()
385 setattr(sys, self.channel, self.orig_stream)
387 setattr(sys, self.channel, self.orig_stream)
386 printed = self.buffer.getvalue()
388 printed = self.buffer.getvalue()
387 for s in self.s:
389 for s in self.s:
388 assert s not in printed, printed_msg.format(s, self.channel, printed)
390 assert s not in printed, printed_msg.format(s, self.channel, printed)
389 return False
391 return False
390
392
391 @contextmanager
393 @contextmanager
392 def mute_warn():
394 def mute_warn():
393 from IPython.utils import warn
395 from IPython.utils import warn
394 save_warn = warn.warn
396 save_warn = warn.warn
395 warn.warn = lambda *a, **kw: None
397 warn.warn = lambda *a, **kw: None
396 try:
398 try:
397 yield
399 yield
398 finally:
400 finally:
399 warn.warn = save_warn
401 warn.warn = save_warn
400
402
401 @contextmanager
403 @contextmanager
402 def make_tempfile(name):
404 def make_tempfile(name):
403 """ Create an empty, named, temporary file for the duration of the context.
405 """ Create an empty, named, temporary file for the duration of the context.
404 """
406 """
405 f = open(name, 'w')
407 f = open(name, 'w')
406 f.close()
408 f.close()
407 try:
409 try:
408 yield
410 yield
409 finally:
411 finally:
410 os.unlink(name)
412 os.unlink(name)
411
413
412
414
413 @contextmanager
415 @contextmanager
414 def monkeypatch(obj, name, attr):
416 def monkeypatch(obj, name, attr):
415 """
417 """
416 Context manager to replace attribute named `name` in `obj` with `attr`.
418 Context manager to replace attribute named `name` in `obj` with `attr`.
417 """
419 """
418 orig = getattr(obj, name)
420 orig = getattr(obj, name)
419 setattr(obj, name, attr)
421 setattr(obj, name, attr)
420 yield
422 yield
421 setattr(obj, name, orig)
423 setattr(obj, name, orig)
422
424
423
425
424 def help_output_test(subcommand=''):
426 def help_output_test(subcommand=''):
425 """test that `ipython [subcommand] -h` works"""
427 """test that `ipython [subcommand] -h` works"""
426 cmd = ' '.join(get_ipython_cmd() + [subcommand, '-h'])
428 cmd = ' '.join(get_ipython_cmd() + [subcommand, '-h'])
427 out, err, rc = get_output_error_code(cmd)
429 out, err, rc = get_output_error_code(cmd)
428 nt.assert_equal(rc, 0, err)
430 nt.assert_equal(rc, 0, err)
429 nt.assert_not_in("Traceback", err)
431 nt.assert_not_in("Traceback", err)
430 nt.assert_in("Options", out)
432 nt.assert_in("Options", out)
431 nt.assert_in("--help-all", out)
433 nt.assert_in("--help-all", out)
432 return out, err
434 return out, err
433
435
434
436
435 def help_all_output_test(subcommand=''):
437 def help_all_output_test(subcommand=''):
436 """test that `ipython [subcommand] --help-all` works"""
438 """test that `ipython [subcommand] --help-all` works"""
437 cmd = ' '.join(get_ipython_cmd() + [subcommand, '--help-all'])
439 cmd = ' '.join(get_ipython_cmd() + [subcommand, '--help-all'])
438 out, err, rc = get_output_error_code(cmd)
440 out, err, rc = get_output_error_code(cmd)
439 nt.assert_equal(rc, 0, err)
441 nt.assert_equal(rc, 0, err)
440 nt.assert_not_in("Traceback", err)
442 nt.assert_not_in("Traceback", err)
441 nt.assert_in("Options", out)
443 nt.assert_in("Options", out)
442 nt.assert_in("Class parameters", out)
444 nt.assert_in("Class parameters", out)
443 return out, err
445 return out, err
444
446
@@ -1,186 +1,187 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for coloring text in ANSI terminals.
2 """Tools for coloring text in ANSI terminals.
3 """
3 """
4
4
5 #*****************************************************************************
5 #*****************************************************************************
6 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #*****************************************************************************
10 #*****************************************************************************
11
11
12 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
12 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
13
13
14 import os
14 import os
15
15
16 from IPython.utils.ipstruct import Struct
16 from IPython.utils.ipstruct import Struct
17
17
18 color_templates = (
18 color_templates = (
19 # Dark colors
19 # Dark colors
20 ("Black" , "0;30"),
20 ("Black" , "0;30"),
21 ("Red" , "0;31"),
21 ("Red" , "0;31"),
22 ("Green" , "0;32"),
22 ("Green" , "0;32"),
23 ("Brown" , "0;33"),
23 ("Brown" , "0;33"),
24 ("Blue" , "0;34"),
24 ("Blue" , "0;34"),
25 ("Purple" , "0;35"),
25 ("Purple" , "0;35"),
26 ("Cyan" , "0;36"),
26 ("Cyan" , "0;36"),
27 ("LightGray" , "0;37"),
27 ("LightGray" , "0;37"),
28 # Light colors
28 # Light colors
29 ("DarkGray" , "1;30"),
29 ("DarkGray" , "1;30"),
30 ("LightRed" , "1;31"),
30 ("LightRed" , "1;31"),
31 ("LightGreen" , "1;32"),
31 ("LightGreen" , "1;32"),
32 ("Yellow" , "1;33"),
32 ("Yellow" , "1;33"),
33 ("LightBlue" , "1;34"),
33 ("LightBlue" , "1;34"),
34 ("LightPurple" , "1;35"),
34 ("LightPurple" , "1;35"),
35 ("LightCyan" , "1;36"),
35 ("LightCyan" , "1;36"),
36 ("White" , "1;37"),
36 ("White" , "1;37"),
37 # Blinking colors. Probably should not be used in anything serious.
37 # Blinking colors. Probably should not be used in anything serious.
38 ("BlinkBlack" , "5;30"),
38 ("BlinkBlack" , "5;30"),
39 ("BlinkRed" , "5;31"),
39 ("BlinkRed" , "5;31"),
40 ("BlinkGreen" , "5;32"),
40 ("BlinkGreen" , "5;32"),
41 ("BlinkYellow" , "5;33"),
41 ("BlinkYellow" , "5;33"),
42 ("BlinkBlue" , "5;34"),
42 ("BlinkBlue" , "5;34"),
43 ("BlinkPurple" , "5;35"),
43 ("BlinkPurple" , "5;35"),
44 ("BlinkCyan" , "5;36"),
44 ("BlinkCyan" , "5;36"),
45 ("BlinkLightGray", "5;37"),
45 ("BlinkLightGray", "5;37"),
46 )
46 )
47
47
48 def make_color_table(in_class):
48 def make_color_table(in_class):
49 """Build a set of color attributes in a class.
49 """Build a set of color attributes in a class.
50
50
51 Helper function for building the *TermColors classes."""
51 Helper function for building the :class:`TermColors` and
52
52 :class`InputTermColors`.
53 """
53 for name,value in color_templates:
54 for name,value in color_templates:
54 setattr(in_class,name,in_class._base % value)
55 setattr(in_class,name,in_class._base % value)
55
56
56 class TermColors:
57 class TermColors:
57 """Color escape sequences.
58 """Color escape sequences.
58
59
59 This class defines the escape sequences for all the standard (ANSI?)
60 This class defines the escape sequences for all the standard (ANSI?)
60 colors in terminals. Also defines a NoColor escape which is just the null
61 colors in terminals. Also defines a NoColor escape which is just the null
61 string, suitable for defining 'dummy' color schemes in terminals which get
62 string, suitable for defining 'dummy' color schemes in terminals which get
62 confused by color escapes.
63 confused by color escapes.
63
64
64 This class should be used as a mixin for building color schemes."""
65 This class should be used as a mixin for building color schemes."""
65
66
66 NoColor = '' # for color schemes in color-less terminals.
67 NoColor = '' # for color schemes in color-less terminals.
67 Normal = '\033[0m' # Reset normal coloring
68 Normal = '\033[0m' # Reset normal coloring
68 _base = '\033[%sm' # Template for all other colors
69 _base = '\033[%sm' # Template for all other colors
69
70
70 # Build the actual color table as a set of class attributes:
71 # Build the actual color table as a set of class attributes:
71 make_color_table(TermColors)
72 make_color_table(TermColors)
72
73
73 class InputTermColors:
74 class InputTermColors:
74 """Color escape sequences for input prompts.
75 """Color escape sequences for input prompts.
75
76
76 This class is similar to TermColors, but the escapes are wrapped in \001
77 This class is similar to TermColors, but the escapes are wrapped in \001
77 and \002 so that readline can properly know the length of each line and
78 and \002 so that readline can properly know the length of each line and
78 can wrap lines accordingly. Use this class for any colored text which
79 can wrap lines accordingly. Use this class for any colored text which
79 needs to be used in input prompts, such as in calls to raw_input().
80 needs to be used in input prompts, such as in calls to raw_input().
80
81
81 This class defines the escape sequences for all the standard (ANSI?)
82 This class defines the escape sequences for all the standard (ANSI?)
82 colors in terminals. Also defines a NoColor escape which is just the null
83 colors in terminals. Also defines a NoColor escape which is just the null
83 string, suitable for defining 'dummy' color schemes in terminals which get
84 string, suitable for defining 'dummy' color schemes in terminals which get
84 confused by color escapes.
85 confused by color escapes.
85
86
86 This class should be used as a mixin for building color schemes."""
87 This class should be used as a mixin for building color schemes."""
87
88
88 NoColor = '' # for color schemes in color-less terminals.
89 NoColor = '' # for color schemes in color-less terminals.
89
90
90 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
91 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
91 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
92 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
92 Normal = '\033[0m' # Reset normal coloring
93 Normal = '\033[0m' # Reset normal coloring
93 _base = '\033[%sm' # Template for all other colors
94 _base = '\033[%sm' # Template for all other colors
94 else:
95 else:
95 Normal = '\001\033[0m\002' # Reset normal coloring
96 Normal = '\001\033[0m\002' # Reset normal coloring
96 _base = '\001\033[%sm\002' # Template for all other colors
97 _base = '\001\033[%sm\002' # Template for all other colors
97
98
98 # Build the actual color table as a set of class attributes:
99 # Build the actual color table as a set of class attributes:
99 make_color_table(InputTermColors)
100 make_color_table(InputTermColors)
100
101
101 class NoColors:
102 class NoColors:
102 """This defines all the same names as the colour classes, but maps them to
103 """This defines all the same names as the colour classes, but maps them to
103 empty strings, so it can easily be substituted to turn off colours."""
104 empty strings, so it can easily be substituted to turn off colours."""
104 NoColor = ''
105 NoColor = ''
105 Normal = ''
106 Normal = ''
106
107
107 for name, value in color_templates:
108 for name, value in color_templates:
108 setattr(NoColors, name, '')
109 setattr(NoColors, name, '')
109
110
110 class ColorScheme:
111 class ColorScheme:
111 """Generic color scheme class. Just a name and a Struct."""
112 """Generic color scheme class. Just a name and a Struct."""
112 def __init__(self,__scheme_name_,colordict=None,**colormap):
113 def __init__(self,__scheme_name_,colordict=None,**colormap):
113 self.name = __scheme_name_
114 self.name = __scheme_name_
114 if colordict is None:
115 if colordict is None:
115 self.colors = Struct(**colormap)
116 self.colors = Struct(**colormap)
116 else:
117 else:
117 self.colors = Struct(colordict)
118 self.colors = Struct(colordict)
118
119
119 def copy(self,name=None):
120 def copy(self,name=None):
120 """Return a full copy of the object, optionally renaming it."""
121 """Return a full copy of the object, optionally renaming it."""
121 if name is None:
122 if name is None:
122 name = self.name
123 name = self.name
123 return ColorScheme(name, self.colors.dict())
124 return ColorScheme(name, self.colors.dict())
124
125
125 class ColorSchemeTable(dict):
126 class ColorSchemeTable(dict):
126 """General class to handle tables of color schemes.
127 """General class to handle tables of color schemes.
127
128
128 It's basically a dict of color schemes with a couple of shorthand
129 It's basically a dict of color schemes with a couple of shorthand
129 attributes and some convenient methods.
130 attributes and some convenient methods.
130
131
131 active_scheme_name -> obvious
132 active_scheme_name -> obvious
132 active_colors -> actual color table of the active scheme"""
133 active_colors -> actual color table of the active scheme"""
133
134
134 def __init__(self,scheme_list=None,default_scheme=''):
135 def __init__(self,scheme_list=None,default_scheme=''):
135 """Create a table of color schemes.
136 """Create a table of color schemes.
136
137
137 The table can be created empty and manually filled or it can be
138 The table can be created empty and manually filled or it can be
138 created with a list of valid color schemes AND the specification for
139 created with a list of valid color schemes AND the specification for
139 the default active scheme.
140 the default active scheme.
140 """
141 """
141
142
142 # create object attributes to be set later
143 # create object attributes to be set later
143 self.active_scheme_name = ''
144 self.active_scheme_name = ''
144 self.active_colors = None
145 self.active_colors = None
145
146
146 if scheme_list:
147 if scheme_list:
147 if default_scheme == '':
148 if default_scheme == '':
148 raise ValueError('you must specify the default color scheme')
149 raise ValueError('you must specify the default color scheme')
149 for scheme in scheme_list:
150 for scheme in scheme_list:
150 self.add_scheme(scheme)
151 self.add_scheme(scheme)
151 self.set_active_scheme(default_scheme)
152 self.set_active_scheme(default_scheme)
152
153
153 def copy(self):
154 def copy(self):
154 """Return full copy of object"""
155 """Return full copy of object"""
155 return ColorSchemeTable(self.values(),self.active_scheme_name)
156 return ColorSchemeTable(self.values(),self.active_scheme_name)
156
157
157 def add_scheme(self,new_scheme):
158 def add_scheme(self,new_scheme):
158 """Add a new color scheme to the table."""
159 """Add a new color scheme to the table."""
159 if not isinstance(new_scheme,ColorScheme):
160 if not isinstance(new_scheme,ColorScheme):
160 raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
161 raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
161 self[new_scheme.name] = new_scheme
162 self[new_scheme.name] = new_scheme
162
163
163 def set_active_scheme(self,scheme,case_sensitive=0):
164 def set_active_scheme(self,scheme,case_sensitive=0):
164 """Set the currently active scheme.
165 """Set the currently active scheme.
165
166
166 Names are by default compared in a case-insensitive way, but this can
167 Names are by default compared in a case-insensitive way, but this can
167 be changed by setting the parameter case_sensitive to true."""
168 be changed by setting the parameter case_sensitive to true."""
168
169
169 scheme_names = list(self.keys())
170 scheme_names = list(self.keys())
170 if case_sensitive:
171 if case_sensitive:
171 valid_schemes = scheme_names
172 valid_schemes = scheme_names
172 scheme_test = scheme
173 scheme_test = scheme
173 else:
174 else:
174 valid_schemes = [s.lower() for s in scheme_names]
175 valid_schemes = [s.lower() for s in scheme_names]
175 scheme_test = scheme.lower()
176 scheme_test = scheme.lower()
176 try:
177 try:
177 scheme_idx = valid_schemes.index(scheme_test)
178 scheme_idx = valid_schemes.index(scheme_test)
178 except ValueError:
179 except ValueError:
179 raise ValueError('Unrecognized color scheme: ' + scheme + \
180 raise ValueError('Unrecognized color scheme: ' + scheme + \
180 '\nValid schemes: '+str(scheme_names).replace("'', ",''))
181 '\nValid schemes: '+str(scheme_names).replace("'', ",''))
181 else:
182 else:
182 active = scheme_names[scheme_idx]
183 active = scheme_names[scheme_idx]
183 self.active_scheme_name = active
184 self.active_scheme_name = active
184 self.active_colors = self[active].colors
185 self.active_colors = self[active].colors
185 # Now allow using '' as an index for the current active scheme
186 # Now allow using '' as an index for the current active scheme
186 self[''] = self[active]
187 self[''] = self[active]
@@ -1,758 +1,759 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 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import os
22 import os
23 import re
23 import re
24 import sys
24 import sys
25 import textwrap
25 import textwrap
26 from string import Formatter
26 from string import Formatter
27
27
28 from IPython.external.path import path
28 from IPython.external.path import path
29 from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest
29 from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest
30 from IPython.utils import py3compat
30 from IPython.utils import py3compat
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Declarations
33 # Declarations
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 # datetime.strftime date format for ipython
36 # datetime.strftime date format for ipython
37 if sys.platform == 'win32':
37 if sys.platform == 'win32':
38 date_format = "%B %d, %Y"
38 date_format = "%B %d, %Y"
39 else:
39 else:
40 date_format = "%B %-d, %Y"
40 date_format = "%B %-d, %Y"
41
41
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Code
44 # Code
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 class LSString(str):
47 class LSString(str):
48 """String derivative with a special access attributes.
48 """String derivative with a special access attributes.
49
49
50 These are normal strings, but with the special attributes:
50 These are normal strings, but with the special attributes:
51
51
52 .l (or .list) : value as list (split on newlines).
52 .l (or .list) : value as list (split on newlines).
53 .n (or .nlstr): original value (the string itself).
53 .n (or .nlstr): original value (the string itself).
54 .s (or .spstr): value as whitespace-separated string.
54 .s (or .spstr): value as whitespace-separated string.
55 .p (or .paths): list of path objects
55 .p (or .paths): list of path objects
56
56
57 Any values which require transformations are computed only once and
57 Any values which require transformations are computed only once and
58 cached.
58 cached.
59
59
60 Such strings are very useful to efficiently interact with the shell, which
60 Such strings are very useful to efficiently interact with the shell, which
61 typically only understands whitespace-separated options for commands."""
61 typically only understands whitespace-separated options for commands."""
62
62
63 def get_list(self):
63 def get_list(self):
64 try:
64 try:
65 return self.__list
65 return self.__list
66 except AttributeError:
66 except AttributeError:
67 self.__list = self.split('\n')
67 self.__list = self.split('\n')
68 return self.__list
68 return self.__list
69
69
70 l = list = property(get_list)
70 l = list = property(get_list)
71
71
72 def get_spstr(self):
72 def get_spstr(self):
73 try:
73 try:
74 return self.__spstr
74 return self.__spstr
75 except AttributeError:
75 except AttributeError:
76 self.__spstr = self.replace('\n',' ')
76 self.__spstr = self.replace('\n',' ')
77 return self.__spstr
77 return self.__spstr
78
78
79 s = spstr = property(get_spstr)
79 s = spstr = property(get_spstr)
80
80
81 def get_nlstr(self):
81 def get_nlstr(self):
82 return self
82 return self
83
83
84 n = nlstr = property(get_nlstr)
84 n = nlstr = property(get_nlstr)
85
85
86 def get_paths(self):
86 def get_paths(self):
87 try:
87 try:
88 return self.__paths
88 return self.__paths
89 except AttributeError:
89 except AttributeError:
90 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
90 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
91 return self.__paths
91 return self.__paths
92
92
93 p = paths = property(get_paths)
93 p = paths = property(get_paths)
94
94
95 # FIXME: We need to reimplement type specific displayhook and then add this
95 # FIXME: We need to reimplement type specific displayhook and then add this
96 # back as a custom printer. This should also be moved outside utils into the
96 # back as a custom printer. This should also be moved outside utils into the
97 # core.
97 # core.
98
98
99 # def print_lsstring(arg):
99 # def print_lsstring(arg):
100 # """ Prettier (non-repr-like) and more informative printer for LSString """
100 # """ Prettier (non-repr-like) and more informative printer for LSString """
101 # print "LSString (.p, .n, .l, .s available). Value:"
101 # print "LSString (.p, .n, .l, .s available). Value:"
102 # print arg
102 # print arg
103 #
103 #
104 #
104 #
105 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
105 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
106
106
107
107
108 class SList(list):
108 class SList(list):
109 """List derivative with a special access attributes.
109 """List derivative with a special access attributes.
110
110
111 These are normal lists, but with the special attributes:
111 These are normal lists, but with the special attributes:
112
112
113 .l (or .list) : value as list (the list itself).
113 * .l (or .list) : value as list (the list itself).
114 .n (or .nlstr): value as a string, joined on newlines.
114 * .n (or .nlstr): value as a string, joined on newlines.
115 .s (or .spstr): value as a string, joined on spaces.
115 * .s (or .spstr): value as a string, joined on spaces.
116 .p (or .paths): list of path objects
116 * .p (or .paths): list of path objects
117
117
118 Any values which require transformations are computed only once and
118 Any values which require transformations are computed only once and
119 cached."""
119 cached."""
120
120
121 def get_list(self):
121 def get_list(self):
122 return self
122 return self
123
123
124 l = list = property(get_list)
124 l = list = property(get_list)
125
125
126 def get_spstr(self):
126 def get_spstr(self):
127 try:
127 try:
128 return self.__spstr
128 return self.__spstr
129 except AttributeError:
129 except AttributeError:
130 self.__spstr = ' '.join(self)
130 self.__spstr = ' '.join(self)
131 return self.__spstr
131 return self.__spstr
132
132
133 s = spstr = property(get_spstr)
133 s = spstr = property(get_spstr)
134
134
135 def get_nlstr(self):
135 def get_nlstr(self):
136 try:
136 try:
137 return self.__nlstr
137 return self.__nlstr
138 except AttributeError:
138 except AttributeError:
139 self.__nlstr = '\n'.join(self)
139 self.__nlstr = '\n'.join(self)
140 return self.__nlstr
140 return self.__nlstr
141
141
142 n = nlstr = property(get_nlstr)
142 n = nlstr = property(get_nlstr)
143
143
144 def get_paths(self):
144 def get_paths(self):
145 try:
145 try:
146 return self.__paths
146 return self.__paths
147 except AttributeError:
147 except AttributeError:
148 self.__paths = [path(p) for p in self if os.path.exists(p)]
148 self.__paths = [path(p) for p in self if os.path.exists(p)]
149 return self.__paths
149 return self.__paths
150
150
151 p = paths = property(get_paths)
151 p = paths = property(get_paths)
152
152
153 def grep(self, pattern, prune = False, field = None):
153 def grep(self, pattern, prune = False, field = None):
154 """ Return all strings matching 'pattern' (a regex or callable)
154 """ Return all strings matching 'pattern' (a regex or callable)
155
155
156 This is case-insensitive. If prune is true, return all items
156 This is case-insensitive. If prune is true, return all items
157 NOT matching the pattern.
157 NOT matching the pattern.
158
158
159 If field is specified, the match must occur in the specified
159 If field is specified, the match must occur in the specified
160 whitespace-separated field.
160 whitespace-separated field.
161
161
162 Examples::
162 Examples::
163
163
164 a.grep( lambda x: x.startswith('C') )
164 a.grep( lambda x: x.startswith('C') )
165 a.grep('Cha.*log', prune=1)
165 a.grep('Cha.*log', prune=1)
166 a.grep('chm', field=-1)
166 a.grep('chm', field=-1)
167 """
167 """
168
168
169 def match_target(s):
169 def match_target(s):
170 if field is None:
170 if field is None:
171 return s
171 return s
172 parts = s.split()
172 parts = s.split()
173 try:
173 try:
174 tgt = parts[field]
174 tgt = parts[field]
175 return tgt
175 return tgt
176 except IndexError:
176 except IndexError:
177 return ""
177 return ""
178
178
179 if isinstance(pattern, py3compat.string_types):
179 if isinstance(pattern, py3compat.string_types):
180 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
180 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
181 else:
181 else:
182 pred = pattern
182 pred = pattern
183 if not prune:
183 if not prune:
184 return SList([el for el in self if pred(match_target(el))])
184 return SList([el for el in self if pred(match_target(el))])
185 else:
185 else:
186 return SList([el for el in self if not pred(match_target(el))])
186 return SList([el for el in self if not pred(match_target(el))])
187
187
188 def fields(self, *fields):
188 def fields(self, *fields):
189 """ Collect whitespace-separated fields from string list
189 """ Collect whitespace-separated fields from string list
190
190
191 Allows quick awk-like usage of string lists.
191 Allows quick awk-like usage of string lists.
192
192
193 Example data (in var a, created by 'a = !ls -l')::
193 Example data (in var a, created by 'a = !ls -l')::
194
194 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
195 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
195 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
196 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
196
197
197 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
198 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
198 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
199 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
199 (note the joining by space).
200 (note the joining by space).
200 a.fields(-1) is ['ChangeLog', 'IPython']
201 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
201
202
202 IndexErrors are ignored.
203 IndexErrors are ignored.
203
204
204 Without args, fields() just split()'s the strings.
205 Without args, fields() just split()'s the strings.
205 """
206 """
206 if len(fields) == 0:
207 if len(fields) == 0:
207 return [el.split() for el in self]
208 return [el.split() for el in self]
208
209
209 res = SList()
210 res = SList()
210 for el in [f.split() for f in self]:
211 for el in [f.split() for f in self]:
211 lineparts = []
212 lineparts = []
212
213
213 for fd in fields:
214 for fd in fields:
214 try:
215 try:
215 lineparts.append(el[fd])
216 lineparts.append(el[fd])
216 except IndexError:
217 except IndexError:
217 pass
218 pass
218 if lineparts:
219 if lineparts:
219 res.append(" ".join(lineparts))
220 res.append(" ".join(lineparts))
220
221
221 return res
222 return res
222
223
223 def sort(self,field= None, nums = False):
224 def sort(self,field= None, nums = False):
224 """ sort by specified fields (see fields())
225 """ sort by specified fields (see fields())
225
226
226 Example::
227 Example::
227 a.sort(1, nums = True)
228 a.sort(1, nums = True)
228
229
229 Sorts a by second field, in numerical order (so that 21 > 3)
230 Sorts a by second field, in numerical order (so that 21 > 3)
230
231
231 """
232 """
232
233
233 #decorate, sort, undecorate
234 #decorate, sort, undecorate
234 if field is not None:
235 if field is not None:
235 dsu = [[SList([line]).fields(field), line] for line in self]
236 dsu = [[SList([line]).fields(field), line] for line in self]
236 else:
237 else:
237 dsu = [[line, line] for line in self]
238 dsu = [[line, line] for line in self]
238 if nums:
239 if nums:
239 for i in range(len(dsu)):
240 for i in range(len(dsu)):
240 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
241 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
241 try:
242 try:
242 n = int(numstr)
243 n = int(numstr)
243 except ValueError:
244 except ValueError:
244 n = 0;
245 n = 0;
245 dsu[i][0] = n
246 dsu[i][0] = n
246
247
247
248
248 dsu.sort()
249 dsu.sort()
249 return SList([t[1] for t in dsu])
250 return SList([t[1] for t in dsu])
250
251
251
252
252 # FIXME: We need to reimplement type specific displayhook and then add this
253 # FIXME: We need to reimplement type specific displayhook and then add this
253 # back as a custom printer. This should also be moved outside utils into the
254 # back as a custom printer. This should also be moved outside utils into the
254 # core.
255 # core.
255
256
256 # def print_slist(arg):
257 # def print_slist(arg):
257 # """ Prettier (non-repr-like) and more informative printer for SList """
258 # """ Prettier (non-repr-like) and more informative printer for SList """
258 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
259 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
259 # if hasattr(arg, 'hideonce') and arg.hideonce:
260 # if hasattr(arg, 'hideonce') and arg.hideonce:
260 # arg.hideonce = False
261 # arg.hideonce = False
261 # return
262 # return
262 #
263 #
263 # nlprint(arg) # This was a nested list printer, now removed.
264 # nlprint(arg) # This was a nested list printer, now removed.
264 #
265 #
265 # print_slist = result_display.when_type(SList)(print_slist)
266 # print_slist = result_display.when_type(SList)(print_slist)
266
267
267
268
268 def indent(instr,nspaces=4, ntabs=0, flatten=False):
269 def indent(instr,nspaces=4, ntabs=0, flatten=False):
269 """Indent a string a given number of spaces or tabstops.
270 """Indent a string a given number of spaces or tabstops.
270
271
271 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
272 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
272
273
273 Parameters
274 Parameters
274 ----------
275 ----------
275
276
276 instr : basestring
277 instr : basestring
277 The string to be indented.
278 The string to be indented.
278 nspaces : int (default: 4)
279 nspaces : int (default: 4)
279 The number of spaces to be indented.
280 The number of spaces to be indented.
280 ntabs : int (default: 0)
281 ntabs : int (default: 0)
281 The number of tabs to be indented.
282 The number of tabs to be indented.
282 flatten : bool (default: False)
283 flatten : bool (default: False)
283 Whether to scrub existing indentation. If True, all lines will be
284 Whether to scrub existing indentation. If True, all lines will be
284 aligned to the same indentation. If False, existing indentation will
285 aligned to the same indentation. If False, existing indentation will
285 be strictly increased.
286 be strictly increased.
286
287
287 Returns
288 Returns
288 -------
289 -------
289
290
290 str|unicode : string indented by ntabs and nspaces.
291 str|unicode : string indented by ntabs and nspaces.
291
292
292 """
293 """
293 if instr is None:
294 if instr is None:
294 return
295 return
295 ind = '\t'*ntabs+' '*nspaces
296 ind = '\t'*ntabs+' '*nspaces
296 if flatten:
297 if flatten:
297 pat = re.compile(r'^\s*', re.MULTILINE)
298 pat = re.compile(r'^\s*', re.MULTILINE)
298 else:
299 else:
299 pat = re.compile(r'^', re.MULTILINE)
300 pat = re.compile(r'^', re.MULTILINE)
300 outstr = re.sub(pat, ind, instr)
301 outstr = re.sub(pat, ind, instr)
301 if outstr.endswith(os.linesep+ind):
302 if outstr.endswith(os.linesep+ind):
302 return outstr[:-len(ind)]
303 return outstr[:-len(ind)]
303 else:
304 else:
304 return outstr
305 return outstr
305
306
306
307
307 def list_strings(arg):
308 def list_strings(arg):
308 """Always return a list of strings, given a string or list of strings
309 """Always return a list of strings, given a string or list of strings
309 as input.
310 as input.
310
311
311 :Examples:
312 :Examples:
312
313
313 In [7]: list_strings('A single string')
314 In [7]: list_strings('A single string')
314 Out[7]: ['A single string']
315 Out[7]: ['A single string']
315
316
316 In [8]: list_strings(['A single string in a list'])
317 In [8]: list_strings(['A single string in a list'])
317 Out[8]: ['A single string in a list']
318 Out[8]: ['A single string in a list']
318
319
319 In [9]: list_strings(['A','list','of','strings'])
320 In [9]: list_strings(['A','list','of','strings'])
320 Out[9]: ['A', 'list', 'of', 'strings']
321 Out[9]: ['A', 'list', 'of', 'strings']
321 """
322 """
322
323
323 if isinstance(arg, py3compat.string_types): return [arg]
324 if isinstance(arg, py3compat.string_types): return [arg]
324 else: return arg
325 else: return arg
325
326
326
327
327 def marquee(txt='',width=78,mark='*'):
328 def marquee(txt='',width=78,mark='*'):
328 """Return the input string centered in a 'marquee'.
329 """Return the input string centered in a 'marquee'.
329
330
330 :Examples:
331 :Examples:
331
332
332 In [16]: marquee('A test',40)
333 In [16]: marquee('A test',40)
333 Out[16]: '**************** A test ****************'
334 Out[16]: '**************** A test ****************'
334
335
335 In [17]: marquee('A test',40,'-')
336 In [17]: marquee('A test',40,'-')
336 Out[17]: '---------------- A test ----------------'
337 Out[17]: '---------------- A test ----------------'
337
338
338 In [18]: marquee('A test',40,' ')
339 In [18]: marquee('A test',40,' ')
339 Out[18]: ' A test '
340 Out[18]: ' A test '
340
341
341 """
342 """
342 if not txt:
343 if not txt:
343 return (mark*width)[:width]
344 return (mark*width)[:width]
344 nmark = (width-len(txt)-2)//len(mark)//2
345 nmark = (width-len(txt)-2)//len(mark)//2
345 if nmark < 0: nmark =0
346 if nmark < 0: nmark =0
346 marks = mark*nmark
347 marks = mark*nmark
347 return '%s %s %s' % (marks,txt,marks)
348 return '%s %s %s' % (marks,txt,marks)
348
349
349
350
350 ini_spaces_re = re.compile(r'^(\s+)')
351 ini_spaces_re = re.compile(r'^(\s+)')
351
352
352 def num_ini_spaces(strng):
353 def num_ini_spaces(strng):
353 """Return the number of initial spaces in a string"""
354 """Return the number of initial spaces in a string"""
354
355
355 ini_spaces = ini_spaces_re.match(strng)
356 ini_spaces = ini_spaces_re.match(strng)
356 if ini_spaces:
357 if ini_spaces:
357 return ini_spaces.end()
358 return ini_spaces.end()
358 else:
359 else:
359 return 0
360 return 0
360
361
361
362
362 def format_screen(strng):
363 def format_screen(strng):
363 """Format a string for screen printing.
364 """Format a string for screen printing.
364
365
365 This removes some latex-type format codes."""
366 This removes some latex-type format codes."""
366 # Paragraph continue
367 # Paragraph continue
367 par_re = re.compile(r'\\$',re.MULTILINE)
368 par_re = re.compile(r'\\$',re.MULTILINE)
368 strng = par_re.sub('',strng)
369 strng = par_re.sub('',strng)
369 return strng
370 return strng
370
371
371
372
372 def dedent(text):
373 def dedent(text):
373 """Equivalent of textwrap.dedent that ignores unindented first line.
374 """Equivalent of textwrap.dedent that ignores unindented first line.
374
375
375 This means it will still dedent strings like:
376 This means it will still dedent strings like:
376 '''foo
377 '''foo
377 is a bar
378 is a bar
378 '''
379 '''
379
380
380 For use in wrap_paragraphs.
381 For use in wrap_paragraphs.
381 """
382 """
382
383
383 if text.startswith('\n'):
384 if text.startswith('\n'):
384 # text starts with blank line, don't ignore the first line
385 # text starts with blank line, don't ignore the first line
385 return textwrap.dedent(text)
386 return textwrap.dedent(text)
386
387
387 # split first line
388 # split first line
388 splits = text.split('\n',1)
389 splits = text.split('\n',1)
389 if len(splits) == 1:
390 if len(splits) == 1:
390 # only one line
391 # only one line
391 return textwrap.dedent(text)
392 return textwrap.dedent(text)
392
393
393 first, rest = splits
394 first, rest = splits
394 # dedent everything but the first line
395 # dedent everything but the first line
395 rest = textwrap.dedent(rest)
396 rest = textwrap.dedent(rest)
396 return '\n'.join([first, rest])
397 return '\n'.join([first, rest])
397
398
398
399
399 def wrap_paragraphs(text, ncols=80):
400 def wrap_paragraphs(text, ncols=80):
400 """Wrap multiple paragraphs to fit a specified width.
401 """Wrap multiple paragraphs to fit a specified width.
401
402
402 This is equivalent to textwrap.wrap, but with support for multiple
403 This is equivalent to textwrap.wrap, but with support for multiple
403 paragraphs, as separated by empty lines.
404 paragraphs, as separated by empty lines.
404
405
405 Returns
406 Returns
406 -------
407 -------
407
408
408 list of complete paragraphs, wrapped to fill `ncols` columns.
409 list of complete paragraphs, wrapped to fill `ncols` columns.
409 """
410 """
410 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
411 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
411 text = dedent(text).strip()
412 text = dedent(text).strip()
412 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
413 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
413 out_ps = []
414 out_ps = []
414 indent_re = re.compile(r'\n\s+', re.MULTILINE)
415 indent_re = re.compile(r'\n\s+', re.MULTILINE)
415 for p in paragraphs:
416 for p in paragraphs:
416 # presume indentation that survives dedent is meaningful formatting,
417 # presume indentation that survives dedent is meaningful formatting,
417 # so don't fill unless text is flush.
418 # so don't fill unless text is flush.
418 if indent_re.search(p) is None:
419 if indent_re.search(p) is None:
419 # wrap paragraph
420 # wrap paragraph
420 p = textwrap.fill(p, ncols)
421 p = textwrap.fill(p, ncols)
421 out_ps.append(p)
422 out_ps.append(p)
422 return out_ps
423 return out_ps
423
424
424
425
425 def long_substr(data):
426 def long_substr(data):
426 """Return the longest common substring in a list of strings.
427 """Return the longest common substring in a list of strings.
427
428
428 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
429 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
429 """
430 """
430 substr = ''
431 substr = ''
431 if len(data) > 1 and len(data[0]) > 0:
432 if len(data) > 1 and len(data[0]) > 0:
432 for i in range(len(data[0])):
433 for i in range(len(data[0])):
433 for j in range(len(data[0])-i+1):
434 for j in range(len(data[0])-i+1):
434 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
435 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
435 substr = data[0][i:i+j]
436 substr = data[0][i:i+j]
436 elif len(data) == 1:
437 elif len(data) == 1:
437 substr = data[0]
438 substr = data[0]
438 return substr
439 return substr
439
440
440
441
441 def strip_email_quotes(text):
442 def strip_email_quotes(text):
442 """Strip leading email quotation characters ('>').
443 """Strip leading email quotation characters ('>').
443
444
444 Removes any combination of leading '>' interspersed with whitespace that
445 Removes any combination of leading '>' interspersed with whitespace that
445 appears *identically* in all lines of the input text.
446 appears *identically* in all lines of the input text.
446
447
447 Parameters
448 Parameters
448 ----------
449 ----------
449 text : str
450 text : str
450
451
451 Examples
452 Examples
452 --------
453 --------
453
454
454 Simple uses::
455 Simple uses::
455
456
456 In [2]: strip_email_quotes('> > text')
457 In [2]: strip_email_quotes('> > text')
457 Out[2]: 'text'
458 Out[2]: 'text'
458
459
459 In [3]: strip_email_quotes('> > text\\n> > more')
460 In [3]: strip_email_quotes('> > text\\n> > more')
460 Out[3]: 'text\\nmore'
461 Out[3]: 'text\\nmore'
461
462
462 Note how only the common prefix that appears in all lines is stripped::
463 Note how only the common prefix that appears in all lines is stripped::
463
464
464 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
465 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
465 Out[4]: '> text\\n> more\\nmore...'
466 Out[4]: '> text\\n> more\\nmore...'
466
467
467 So if any line has no quote marks ('>') , then none are stripped from any
468 So if any line has no quote marks ('>') , then none are stripped from any
468 of them ::
469 of them ::
469
470
470 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
471 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
471 Out[5]: '> > text\\n> > more\\nlast different'
472 Out[5]: '> > text\\n> > more\\nlast different'
472 """
473 """
473 lines = text.splitlines()
474 lines = text.splitlines()
474 matches = set()
475 matches = set()
475 for line in lines:
476 for line in lines:
476 prefix = re.match(r'^(\s*>[ >]*)', line)
477 prefix = re.match(r'^(\s*>[ >]*)', line)
477 if prefix:
478 if prefix:
478 matches.add(prefix.group(1))
479 matches.add(prefix.group(1))
479 else:
480 else:
480 break
481 break
481 else:
482 else:
482 prefix = long_substr(list(matches))
483 prefix = long_substr(list(matches))
483 if prefix:
484 if prefix:
484 strip = len(prefix)
485 strip = len(prefix)
485 text = '\n'.join([ ln[strip:] for ln in lines])
486 text = '\n'.join([ ln[strip:] for ln in lines])
486 return text
487 return text
487
488
488
489
489 class EvalFormatter(Formatter):
490 class EvalFormatter(Formatter):
490 """A String Formatter that allows evaluation of simple expressions.
491 """A String Formatter that allows evaluation of simple expressions.
491
492
492 Note that this version interprets a : as specifying a format string (as per
493 Note that this version interprets a : as specifying a format string (as per
493 standard string formatting), so if slicing is required, you must explicitly
494 standard string formatting), so if slicing is required, you must explicitly
494 create a slice.
495 create a slice.
495
496
496 This is to be used in templating cases, such as the parallel batch
497 This is to be used in templating cases, such as the parallel batch
497 script templates, where simple arithmetic on arguments is useful.
498 script templates, where simple arithmetic on arguments is useful.
498
499
499 Examples
500 Examples
500 --------
501 --------
501
502
502 In [1]: f = EvalFormatter()
503 In [1]: f = EvalFormatter()
503 In [2]: f.format('{n//4}', n=8)
504 In [2]: f.format('{n//4}', n=8)
504 Out [2]: '2'
505 Out [2]: '2'
505
506
506 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
507 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
507 Out [3]: 'll'
508 Out [3]: 'll'
508 """
509 """
509 def get_field(self, name, args, kwargs):
510 def get_field(self, name, args, kwargs):
510 v = eval(name, kwargs)
511 v = eval(name, kwargs)
511 return v, name
512 return v, name
512
513
513
514
514 @skip_doctest_py3
515 @skip_doctest_py3
515 class FullEvalFormatter(Formatter):
516 class FullEvalFormatter(Formatter):
516 """A String Formatter that allows evaluation of simple expressions.
517 """A String Formatter that allows evaluation of simple expressions.
517
518
518 Any time a format key is not found in the kwargs,
519 Any time a format key is not found in the kwargs,
519 it will be tried as an expression in the kwargs namespace.
520 it will be tried as an expression in the kwargs namespace.
520
521
521 Note that this version allows slicing using [1:2], so you cannot specify
522 Note that this version allows slicing using [1:2], so you cannot specify
522 a format string. Use :class:`EvalFormatter` to permit format strings.
523 a format string. Use :class:`EvalFormatter` to permit format strings.
523
524
524 Examples
525 Examples
525 --------
526 --------
526
527
527 In [1]: f = FullEvalFormatter()
528 In [1]: f = FullEvalFormatter()
528 In [2]: f.format('{n//4}', n=8)
529 In [2]: f.format('{n//4}', n=8)
529 Out[2]: u'2'
530 Out[2]: u'2'
530
531
531 In [3]: f.format('{list(range(5))[2:4]}')
532 In [3]: f.format('{list(range(5))[2:4]}')
532 Out[3]: u'[2, 3]'
533 Out[3]: u'[2, 3]'
533
534
534 In [4]: f.format('{3*2}')
535 In [4]: f.format('{3*2}')
535 Out[4]: u'6'
536 Out[4]: u'6'
536 """
537 """
537 # copied from Formatter._vformat with minor changes to allow eval
538 # copied from Formatter._vformat with minor changes to allow eval
538 # and replace the format_spec code with slicing
539 # and replace the format_spec code with slicing
539 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
540 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
540 if recursion_depth < 0:
541 if recursion_depth < 0:
541 raise ValueError('Max string recursion exceeded')
542 raise ValueError('Max string recursion exceeded')
542 result = []
543 result = []
543 for literal_text, field_name, format_spec, conversion in \
544 for literal_text, field_name, format_spec, conversion in \
544 self.parse(format_string):
545 self.parse(format_string):
545
546
546 # output the literal text
547 # output the literal text
547 if literal_text:
548 if literal_text:
548 result.append(literal_text)
549 result.append(literal_text)
549
550
550 # if there's a field, output it
551 # if there's a field, output it
551 if field_name is not None:
552 if field_name is not None:
552 # this is some markup, find the object and do
553 # this is some markup, find the object and do
553 # the formatting
554 # the formatting
554
555
555 if format_spec:
556 if format_spec:
556 # override format spec, to allow slicing:
557 # override format spec, to allow slicing:
557 field_name = ':'.join([field_name, format_spec])
558 field_name = ':'.join([field_name, format_spec])
558
559
559 # eval the contents of the field for the object
560 # eval the contents of the field for the object
560 # to be formatted
561 # to be formatted
561 obj = eval(field_name, kwargs)
562 obj = eval(field_name, kwargs)
562
563
563 # do any conversion on the resulting object
564 # do any conversion on the resulting object
564 obj = self.convert_field(obj, conversion)
565 obj = self.convert_field(obj, conversion)
565
566
566 # format the object and append to the result
567 # format the object and append to the result
567 result.append(self.format_field(obj, ''))
568 result.append(self.format_field(obj, ''))
568
569
569 return u''.join(py3compat.cast_unicode(s) for s in result)
570 return u''.join(py3compat.cast_unicode(s) for s in result)
570
571
571
572
572 @skip_doctest_py3
573 @skip_doctest_py3
573 class DollarFormatter(FullEvalFormatter):
574 class DollarFormatter(FullEvalFormatter):
574 """Formatter allowing Itpl style $foo replacement, for names and attribute
575 """Formatter allowing Itpl style $foo replacement, for names and attribute
575 access only. Standard {foo} replacement also works, and allows full
576 access only. Standard {foo} replacement also works, and allows full
576 evaluation of its arguments.
577 evaluation of its arguments.
577
578
578 Examples
579 Examples
579 --------
580 --------
580 In [1]: f = DollarFormatter()
581 In [1]: f = DollarFormatter()
581 In [2]: f.format('{n//4}', n=8)
582 In [2]: f.format('{n//4}', n=8)
582 Out[2]: u'2'
583 Out[2]: u'2'
583
584
584 In [3]: f.format('23 * 76 is $result', result=23*76)
585 In [3]: f.format('23 * 76 is $result', result=23*76)
585 Out[3]: u'23 * 76 is 1748'
586 Out[3]: u'23 * 76 is 1748'
586
587
587 In [4]: f.format('$a or {b}', a=1, b=2)
588 In [4]: f.format('$a or {b}', a=1, b=2)
588 Out[4]: u'1 or 2'
589 Out[4]: u'1 or 2'
589 """
590 """
590 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
591 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
591 def parse(self, fmt_string):
592 def parse(self, fmt_string):
592 for literal_txt, field_name, format_spec, conversion \
593 for literal_txt, field_name, format_spec, conversion \
593 in Formatter.parse(self, fmt_string):
594 in Formatter.parse(self, fmt_string):
594
595
595 # Find $foo patterns in the literal text.
596 # Find $foo patterns in the literal text.
596 continue_from = 0
597 continue_from = 0
597 txt = ""
598 txt = ""
598 for m in self._dollar_pattern.finditer(literal_txt):
599 for m in self._dollar_pattern.finditer(literal_txt):
599 new_txt, new_field = m.group(1,2)
600 new_txt, new_field = m.group(1,2)
600 # $$foo --> $foo
601 # $$foo --> $foo
601 if new_field.startswith("$"):
602 if new_field.startswith("$"):
602 txt += new_txt + new_field
603 txt += new_txt + new_field
603 else:
604 else:
604 yield (txt + new_txt, new_field, "", None)
605 yield (txt + new_txt, new_field, "", None)
605 txt = ""
606 txt = ""
606 continue_from = m.end()
607 continue_from = m.end()
607
608
608 # Re-yield the {foo} style pattern
609 # Re-yield the {foo} style pattern
609 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
610 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
610
611
611 #-----------------------------------------------------------------------------
612 #-----------------------------------------------------------------------------
612 # Utils to columnize a list of string
613 # Utils to columnize a list of string
613 #-----------------------------------------------------------------------------
614 #-----------------------------------------------------------------------------
614
615
615 def _chunks(l, n):
616 def _chunks(l, n):
616 """Yield successive n-sized chunks from l."""
617 """Yield successive n-sized chunks from l."""
617 for i in py3compat.xrange(0, len(l), n):
618 for i in py3compat.xrange(0, len(l), n):
618 yield l[i:i+n]
619 yield l[i:i+n]
619
620
620
621
621 def _find_optimal(rlist , separator_size=2 , displaywidth=80):
622 def _find_optimal(rlist , separator_size=2 , displaywidth=80):
622 """Calculate optimal info to columnize a list of string"""
623 """Calculate optimal info to columnize a list of string"""
623 for nrow in range(1, len(rlist)+1) :
624 for nrow in range(1, len(rlist)+1) :
624 chk = list(map(max,_chunks(rlist, nrow)))
625 chk = list(map(max,_chunks(rlist, nrow)))
625 sumlength = sum(chk)
626 sumlength = sum(chk)
626 ncols = len(chk)
627 ncols = len(chk)
627 if sumlength+separator_size*(ncols-1) <= displaywidth :
628 if sumlength+separator_size*(ncols-1) <= displaywidth :
628 break;
629 break;
629 return {'columns_numbers' : ncols,
630 return {'columns_numbers' : ncols,
630 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
631 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
631 'rows_numbers' : nrow,
632 'rows_numbers' : nrow,
632 'columns_width' : chk
633 'columns_width' : chk
633 }
634 }
634
635
635
636
636 def _get_or_default(mylist, i, default=None):
637 def _get_or_default(mylist, i, default=None):
637 """return list item number, or default if don't exist"""
638 """return list item number, or default if don't exist"""
638 if i >= len(mylist):
639 if i >= len(mylist):
639 return default
640 return default
640 else :
641 else :
641 return mylist[i]
642 return mylist[i]
642
643
643
644
644 @skip_doctest
645 @skip_doctest
645 def compute_item_matrix(items, empty=None, *args, **kwargs) :
646 def compute_item_matrix(items, empty=None, *args, **kwargs) :
646 """Returns a nested list, and info to columnize items
647 """Returns a nested list, and info to columnize items
647
648
648 Parameters
649 Parameters
649 ----------
650 ----------
650
651
651 items :
652 items :
652 list of strings to columize
653 list of strings to columize
653 empty : (default None)
654 empty : (default None)
654 default value to fill list if needed
655 default value to fill list if needed
655 separator_size : int (default=2)
656 separator_size : int (default=2)
656 How much caracters will be used as a separation between each columns.
657 How much caracters will be used as a separation between each columns.
657 displaywidth : int (default=80)
658 displaywidth : int (default=80)
658 The width of the area onto wich the columns should enter
659 The width of the area onto wich the columns should enter
659
660
660 Returns
661 Returns
661 -------
662 -------
662
663
663 Returns a tuple of (strings_matrix, dict_info)
664 Returns a tuple of (strings_matrix, dict_info)
664
665
665 strings_matrix :
666 strings_matrix :
666
667
667 nested list of string, the outer most list contains as many list as
668 nested list of string, the outer most list contains as many list as
668 rows, the innermost lists have each as many element as colums. If the
669 rows, the innermost lists have each as many element as colums. If the
669 total number of elements in `items` does not equal the product of
670 total number of elements in `items` does not equal the product of
670 rows*columns, the last element of some lists are filled with `None`.
671 rows*columns, the last element of some lists are filled with `None`.
671
672
672 dict_info :
673 dict_info :
673 some info to make columnize easier:
674 some info to make columnize easier:
674
675
675 columns_numbers : number of columns
676 columns_numbers : number of columns
676 rows_numbers : number of rows
677 rows_numbers : number of rows
677 columns_width : list of with of each columns
678 columns_width : list of with of each columns
678 optimal_separator_width : best separator width between columns
679 optimal_separator_width : best separator width between columns
679
680
680 Examples
681 Examples
681 --------
682 --------
682
683
683 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
684 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
684 ...: compute_item_matrix(l,displaywidth=12)
685 ...: compute_item_matrix(l,displaywidth=12)
685 Out[1]:
686 Out[1]:
686 ([['aaa', 'f', 'k'],
687 ([['aaa', 'f', 'k'],
687 ['b', 'g', 'l'],
688 ['b', 'g', 'l'],
688 ['cc', 'h', None],
689 ['cc', 'h', None],
689 ['d', 'i', None],
690 ['d', 'i', None],
690 ['eeeee', 'j', None]],
691 ['eeeee', 'j', None]],
691 {'columns_numbers': 3,
692 {'columns_numbers': 3,
692 'columns_width': [5, 1, 1],
693 'columns_width': [5, 1, 1],
693 'optimal_separator_width': 2,
694 'optimal_separator_width': 2,
694 'rows_numbers': 5})
695 'rows_numbers': 5})
695
696
696 """
697 """
697 info = _find_optimal(list(map(len, items)), *args, **kwargs)
698 info = _find_optimal(list(map(len, items)), *args, **kwargs)
698 nrow, ncol = info['rows_numbers'], info['columns_numbers']
699 nrow, ncol = info['rows_numbers'], info['columns_numbers']
699 return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info)
700 return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info)
700
701
701
702
702 def columnize(items, separator=' ', displaywidth=80):
703 def columnize(items, separator=' ', displaywidth=80):
703 """ Transform a list of strings into a single string with columns.
704 """ Transform a list of strings into a single string with columns.
704
705
705 Parameters
706 Parameters
706 ----------
707 ----------
707 items : sequence of strings
708 items : sequence of strings
708 The strings to process.
709 The strings to process.
709
710
710 separator : str, optional [default is two spaces]
711 separator : str, optional [default is two spaces]
711 The string that separates columns.
712 The string that separates columns.
712
713
713 displaywidth : int, optional [default is 80]
714 displaywidth : int, optional [default is 80]
714 Width of the display in number of characters.
715 Width of the display in number of characters.
715
716
716 Returns
717 Returns
717 -------
718 -------
718 The formatted string.
719 The formatted string.
719 """
720 """
720 if not items :
721 if not items :
721 return '\n'
722 return '\n'
722 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
723 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
723 fmatrix = [filter(None, x) for x in matrix]
724 fmatrix = [filter(None, x) for x in matrix]
724 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
725 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
725 return '\n'.join(map(sjoin, fmatrix))+'\n'
726 return '\n'.join(map(sjoin, fmatrix))+'\n'
726
727
727
728
728 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
729 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
729 """
730 """
730 Return a string with a natural enumeration of items
731 Return a string with a natural enumeration of items
731
732
732 >>> get_text_list(['a', 'b', 'c', 'd'])
733 >>> get_text_list(['a', 'b', 'c', 'd'])
733 'a, b, c and d'
734 'a, b, c and d'
734 >>> get_text_list(['a', 'b', 'c'], ' or ')
735 >>> get_text_list(['a', 'b', 'c'], ' or ')
735 'a, b or c'
736 'a, b or c'
736 >>> get_text_list(['a', 'b', 'c'], ', ')
737 >>> get_text_list(['a', 'b', 'c'], ', ')
737 'a, b, c'
738 'a, b, c'
738 >>> get_text_list(['a', 'b'], ' or ')
739 >>> get_text_list(['a', 'b'], ' or ')
739 'a or b'
740 'a or b'
740 >>> get_text_list(['a'])
741 >>> get_text_list(['a'])
741 'a'
742 'a'
742 >>> get_text_list([])
743 >>> get_text_list([])
743 ''
744 ''
744 >>> get_text_list(['a', 'b'], wrap_item_with="`")
745 >>> get_text_list(['a', 'b'], wrap_item_with="`")
745 '`a` and `b`'
746 '`a` and `b`'
746 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
747 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
747 'a + b + c = d'
748 'a + b + c = d'
748 """
749 """
749 if len(list_) == 0:
750 if len(list_) == 0:
750 return ''
751 return ''
751 if wrap_item_with:
752 if wrap_item_with:
752 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
753 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
753 item in list_]
754 item in list_]
754 if len(list_) == 1:
755 if len(list_) == 1:
755 return list_[0]
756 return list_[0]
756 return '%s%s%s' % (
757 return '%s%s%s' % (
757 sep.join(i for i in list_[:-1]),
758 sep.join(i for i in list_[:-1]),
758 last_sep, list_[-1]) No newline at end of file
759 last_sep, list_[-1])
@@ -1,49 +1,50 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Script to auto-generate our API docs.
2 """Script to auto-generate our API docs.
3 """
3 """
4 # stdlib imports
4 # stdlib imports
5 import os
5 import os
6 import sys
6 import sys
7
7
8 # local imports
8 # local imports
9 sys.path.append(os.path.abspath('sphinxext'))
9 sys.path.append(os.path.abspath('sphinxext'))
10 from apigen import ApiDocWriter
10 from apigen import ApiDocWriter
11
11
12 #*****************************************************************************
12 #*****************************************************************************
13 if __name__ == '__main__':
13 if __name__ == '__main__':
14 pjoin = os.path.join
14 pjoin = os.path.join
15 package = 'IPython'
15 package = 'IPython'
16 outdir = pjoin('source','api','generated')
16 outdir = pjoin('source','api','generated')
17 docwriter = ApiDocWriter(package,rst_extension='.rst')
17 docwriter = ApiDocWriter(package,rst_extension='.rst')
18 # You have to escape the . here because . is a special char for regexps.
18 # You have to escape the . here because . is a special char for regexps.
19 # You must do make clean if you change this!
19 # You must do make clean if you change this!
20 docwriter.package_skip_patterns += [r'\.external$',
20 docwriter.package_skip_patterns += [r'\.external$',
21 # Extensions are documented elsewhere.
21 # Extensions are documented elsewhere.
22 r'\.extensions',
22 r'\.extensions',
23 r'\.config\.profile',
23 r'\.config\.profile',
24 ]
24 ]
25
25
26 # The inputhook* modules often cause problems on import, such as trying to
26 # The inputhook* modules often cause problems on import, such as trying to
27 # load incompatible Qt bindings. It's easiest to leave them all out. The
27 # load incompatible Qt bindings. It's easiest to leave them all out. The
28 # main API is in the inputhook module, which is documented.
28 # main API is in the inputhook module, which is documented.
29 docwriter.module_skip_patterns += [ r'\.lib\.inputhook.+',
29 docwriter.module_skip_patterns += [ r'\.lib\.inputhook.+',
30 r'\.ipdoctest',
30 r'\.ipdoctest',
31 r'\.testing\.plugin',
31 # This just prints a deprecation msg:
32 # This just prints a deprecation msg:
32 r'\.frontend',
33 r'\.frontend',
33 ]
34 ]
34
35
35 # We're rarely working on machines with the Azure SDK installed, so we
36 # We're rarely working on machines with the Azure SDK installed, so we
36 # skip the module that needs it in that case.
37 # skip the module that needs it in that case.
37 try:
38 try:
38 import azure # analysis:ignore
39 import azure # analysis:ignore
39 except ImportError:
40 except ImportError:
40 docwriter.module_skip_patterns.append(r'\.html\.services\.notebooks\.azurenbmanager')
41 docwriter.module_skip_patterns.append(r'\.html\.services\.notebooks\.azurenbmanager')
41
42
42 # Now, generate the outputs
43 # Now, generate the outputs
43 docwriter.write_api_docs(outdir)
44 docwriter.write_api_docs(outdir)
44 # Write index with .txt extension - we can include it, but Sphinx won't try
45 # Write index with .txt extension - we can include it, but Sphinx won't try
45 # to compile it
46 # to compile it
46 docwriter.write_index(outdir, 'gen.txt',
47 docwriter.write_index(outdir, 'gen.txt',
47 relative_to = pjoin('source','api')
48 relative_to = pjoin('source','api')
48 )
49 )
49 print ('%d files written' % len(docwriter.written_modules))
50 print ('%d files written' % len(docwriter.written_modules))
General Comments 0
You need to be logged in to leave comments. Login now