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