##// END OF EJS Templates
Use AST parser to find python multilines with function handling
Skipper Seabold -
Show More
@@ -1,813 +1,808 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Sphinx directive to support embedded IPython code.
3 3
4 4 This directive allows pasting of entire interactive IPython sessions, prompts
5 5 and all, and their code will actually get re-executed at doc build time, with
6 6 all prompts renumbered sequentially. It also allows you to input code as a pure
7 7 python input by giving the argument python to the directive. The output looks
8 8 like an interactive ipython section.
9 9
10 10 To enable this directive, simply list it in your Sphinx ``conf.py`` file
11 11 (making sure the directory where you placed it is visible to sphinx, as is
12 12 needed for all Sphinx directives).
13 13
14 14 By default this directive assumes that your prompts are unchanged IPython ones,
15 15 but this can be customized. The configurable options that can be placed in
16 16 conf.py are
17 17
18 18 ipython_savefig_dir:
19 19 The directory in which to save the figures. This is relative to the
20 20 Sphinx source directory. The default is `html_static_path`.
21 21 ipython_rgxin:
22 22 The compiled regular expression to denote the start of IPython input
23 23 lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You
24 24 shouldn't need to change this.
25 25 ipython_rgxout:
26 26 The compiled regular expression to denote the start of IPython output
27 27 lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You
28 28 shouldn't need to change this.
29 29 ipython_promptin:
30 30 The string to represent the IPython input prompt in the generated ReST.
31 31 The default is 'In [%d]:'. This expects that the line numbers are used
32 32 in the prompt.
33 33 ipython_promptout:
34 34
35 35 The string to represent the IPython prompt in the generated ReST. The
36 36 default is 'Out [%d]:'. This expects that the line numbers are used
37 37 in the prompt.
38 38
39 39 ToDo
40 40 ----
41 41
42 42 - Turn the ad-hoc test() function into a real test suite.
43 43 - Break up ipython-specific functionality from matplotlib stuff into better
44 44 separated code.
45 45
46 46 Authors
47 47 -------
48 48
49 49 - John D Hunter: orignal author.
50 50 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
51 51 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
52 52 - Skipper Seabold, refactoring, cleanups, pure python addition
53 53 """
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Imports
57 57 #-----------------------------------------------------------------------------
58 58
59 59 # Stdlib
60 60 import cStringIO
61 61 import os
62 62 import re
63 63 import sys
64 64 import tempfile
65 65
66 66 # To keep compatibility with various python versions
67 67 try:
68 68 from hashlib import md5
69 69 except ImportError:
70 70 from md5 import md5
71 71
72 72 # Third-party
73 73 import matplotlib
74 74 import sphinx
75 75 from docutils.parsers.rst import directives
76 76 from docutils import nodes
77 77 from sphinx.util.compat import Directive
78 78
79 79 matplotlib.use('Agg')
80 80
81 81 # Our own
82 82 from IPython import Config, InteractiveShell
83 83 from IPython.core.profiledir import ProfileDir
84 84 from IPython.utils import io
85 85
86 86 #-----------------------------------------------------------------------------
87 87 # Globals
88 88 #-----------------------------------------------------------------------------
89 89 # for tokenizing blocks
90 90 COMMENT, INPUT, OUTPUT = range(3)
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # Functions and class declarations
94 94 #-----------------------------------------------------------------------------
95 95 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
96 96 """
97 97 part is a string of ipython text, comprised of at most one
98 98 input, one ouput, comments, and blank lines. The block parser
99 99 parses the text into a list of::
100 100
101 101 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
102 102
103 103 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
104 104 data is, depending on the type of token::
105 105
106 106 COMMENT : the comment string
107 107
108 108 INPUT: the (DECORATOR, INPUT_LINE, REST) where
109 109 DECORATOR: the input decorator (or None)
110 110 INPUT_LINE: the input as string (possibly multi-line)
111 111 REST : any stdout generated by the input line (not OUTPUT)
112 112
113 113
114 114 OUTPUT: the output string, possibly multi-line
115 115 """
116 116
117 117 block = []
118 118 lines = part.split('\n')
119 119 N = len(lines)
120 120 i = 0
121 121 decorator = None
122 122 while 1:
123 123
124 124 if i==N:
125 125 # nothing left to parse -- the last line
126 126 break
127 127
128 128 line = lines[i]
129 129 i += 1
130 130 line_stripped = line.strip()
131 131 if line_stripped.startswith('#'):
132 132 block.append((COMMENT, line))
133 133 continue
134 134
135 135 if line_stripped.startswith('@'):
136 136 # we're assuming at most one decorator -- may need to
137 137 # rethink
138 138 decorator = line_stripped
139 139 continue
140 140
141 141 # does this look like an input line?
142 142 matchin = rgxin.match(line)
143 143 if matchin:
144 144 lineno, inputline = int(matchin.group(1)), matchin.group(2)
145 145
146 146 # the ....: continuation string
147 147 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
148 148 Nc = len(continuation)
149 149 # input lines can continue on for more than one line, if
150 150 # we have a '\' line continuation char or a function call
151 151 # echo line 'print'. The input line can only be
152 152 # terminated by the end of the block or an output line, so
153 153 # we parse out the rest of the input line if it is
154 154 # multiline as well as any echo text
155 155
156 156 rest = []
157 157 while i<N:
158 158
159 159 # look ahead; if the next line is blank, or a comment, or
160 160 # an output line, we're done
161 161
162 162 nextline = lines[i]
163 163 matchout = rgxout.match(nextline)
164 164 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
165 165 if matchout or nextline.startswith('#'):
166 166 break
167 167 elif nextline.startswith(continuation):
168 168 inputline += '\n' + nextline[Nc:]
169 169 else:
170 170 rest.append(nextline)
171 171 i+= 1
172 172
173 173 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
174 174 continue
175 175
176 176 # if it looks like an output line grab all the text to the end
177 177 # of the block
178 178 matchout = rgxout.match(line)
179 179 if matchout:
180 180 lineno, output = int(matchout.group(1)), matchout.group(2)
181 181 if i<N-1:
182 182 output = '\n'.join([output] + lines[i:])
183 183
184 184 block.append((OUTPUT, output))
185 185 break
186 186
187 187 return block
188 188
189 189 class EmbeddedSphinxShell(object):
190 190 """An embedded IPython instance to run inside Sphinx"""
191 191
192 192 def __init__(self):
193 193
194 194 self.cout = cStringIO.StringIO()
195 195
196 196
197 197 # Create config object for IPython
198 198 config = Config()
199 199 config.Global.display_banner = False
200 200 config.Global.exec_lines = ['import numpy as np',
201 201 'from pylab import *'
202 202 ]
203 203 config.InteractiveShell.autocall = False
204 204 config.InteractiveShell.autoindent = False
205 205 config.InteractiveShell.colors = 'NoColor'
206 206
207 207 # create a profile so instance history isn't saved
208 208 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
209 209 profname = 'auto_profile_sphinx_build'
210 210 pdir = os.path.join(tmp_profile_dir,profname)
211 211 profile = ProfileDir.create_profile_dir(pdir)
212 212
213 213 # Create and initialize ipython, but don't start its mainloop
214 214 IP = InteractiveShell.instance(config=config, profile_dir=profile)
215 215 # io.stdout redirect must be done *after* instantiating InteractiveShell
216 216 io.stdout = self.cout
217 217 io.stderr = self.cout
218 218
219 219 # For debugging, so we can see normal output, use this:
220 220 #from IPython.utils.io import Tee
221 221 #io.stdout = Tee(self.cout, channel='stdout') # dbg
222 222 #io.stderr = Tee(self.cout, channel='stderr') # dbg
223 223
224 224 # Store a few parts of IPython we'll need.
225 225 self.IP = IP
226 226 self.user_ns = self.IP.user_ns
227 227 self.user_global_ns = self.IP.user_global_ns
228 228
229 229 self.input = ''
230 230 self.output = ''
231 231
232 232 self.is_verbatim = False
233 233 self.is_doctest = False
234 234 self.is_suppress = False
235 235
236 236 # on the first call to the savefig decorator, we'll import
237 237 # pyplot as plt so we can make a call to the plt.gcf().savefig
238 238 self._pyplot_imported = False
239 239
240 240 def clear_cout(self):
241 241 self.cout.seek(0)
242 242 self.cout.truncate(0)
243 243
244 244 def process_input_line(self, line, store_history=True):
245 245 """process the input, capturing stdout"""
246 246 #print "input='%s'"%self.input
247 247 stdout = sys.stdout
248 248 splitter = self.IP.input_splitter
249 249 try:
250 250 sys.stdout = self.cout
251 251 splitter.push(line)
252 252 more = splitter.push_accepts_more()
253 253 if not more:
254 254 source_raw = splitter.source_raw_reset()[1]
255 255 self.IP.run_cell(source_raw, store_history=store_history)
256 256 finally:
257 257 sys.stdout = stdout
258 258
259 259 def process_image(self, decorator):
260 260 """
261 261 # build out an image directive like
262 262 # .. image:: somefile.png
263 263 # :width 4in
264 264 #
265 265 # from an input like
266 266 # savefig somefile.png width=4in
267 267 """
268 268 savefig_dir = self.savefig_dir
269 269 source_dir = self.source_dir
270 270 saveargs = decorator.split(' ')
271 271 filename = saveargs[1]
272 272 # insert relative path to image file in source
273 273 outfile = os.path.relpath(os.path.join(savefig_dir,filename),
274 274 source_dir)
275 275
276 276 imagerows = ['.. image:: %s'%outfile]
277 277
278 278 for kwarg in saveargs[2:]:
279 279 arg, val = kwarg.split('=')
280 280 arg = arg.strip()
281 281 val = val.strip()
282 282 imagerows.append(' :%s: %s'%(arg, val))
283 283
284 284 image_file = os.path.basename(outfile) # only return file name
285 285 image_directive = '\n'.join(imagerows)
286 286 return image_file, image_directive
287 287
288 288
289 289 # Callbacks for each type of token
290 290 def process_input(self, data, input_prompt, lineno):
291 291 """Process data block for INPUT token."""
292 292 decorator, input, rest = data
293 293 image_file = None
294 294 image_directive = None
295 295 #print 'INPUT:', data # dbg
296 296 is_verbatim = decorator=='@verbatim' or self.is_verbatim
297 297 is_doctest = decorator=='@doctest' or self.is_doctest
298 298 is_suppress = decorator=='@suppress' or self.is_suppress
299 299 is_savefig = decorator is not None and \
300 300 decorator.startswith('@savefig')
301 301
302 302 input_lines = input.split('\n')
303 303
304 304 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
305 305 Nc = len(continuation)
306 306
307 307 if is_savefig:
308 308 image_file, image_directive = self.process_image(decorator)
309 309
310 310 ret = []
311 311 is_semicolon = False
312 312 store_history = True
313 313
314 314 for i, line in enumerate(input_lines):
315 315 if line.endswith(';'):
316 316 is_semicolon = True
317 317 if is_semicolon or is_suppress:
318 318 store_history = False
319 319
320 320 if i==0:
321 321 # process the first input line
322 322 if is_verbatim:
323 323 self.process_input_line('')
324 324 self.IP.execution_count += 1 # increment it anyway
325 325 else:
326 326 # only submit the line in non-verbatim mode
327 327 self.process_input_line(line, store_history=store_history)
328 328 formatted_line = '%s %s'%(input_prompt, line)
329 329 else:
330 330 # process a continuation line
331 331 if not is_verbatim:
332 332 self.process_input_line(line, store_history=store_history)
333 333
334 334 formatted_line = '%s %s'%(continuation, line)
335 335
336 336 if not is_suppress:
337 337 ret.append(formatted_line)
338 338
339 339 if not is_suppress:
340 340 if len(rest.strip()):
341 341 if is_verbatim:
342 342 # the "rest" is the standard output of the
343 343 # input, which needs to be added in
344 344 # verbatim mode
345 345 ret.append(rest)
346 346
347 347 self.cout.seek(0)
348 348 output = self.cout.read()
349 349 if not is_suppress and not is_semicolon:
350 350 ret.append(output)
351 351
352 352 self.cout.truncate(0)
353 353 return (ret, input_lines, output, is_doctest, image_file,
354 354 image_directive)
355 355 #print 'OUTPUT', output # dbg
356 356
357 357 def process_output(self, data, output_prompt,
358 358 input_lines, output, is_doctest, image_file):
359 359 """Process data block for OUTPUT token."""
360 360 if is_doctest:
361 361 submitted = data.strip()
362 362 found = output
363 363 if found is not None:
364 364 found = found.strip()
365 365
366 366 # XXX - fperez: in 0.11, 'output' never comes with the prompt
367 367 # in it, just the actual output text. So I think all this code
368 368 # can be nuked...
369 369
370 370 # the above comment does not appear to be accurate... (minrk)
371 371
372 372 ind = found.find(output_prompt)
373 373 if ind<0:
374 374 e='output prompt="%s" does not match out line=%s' % \
375 375 (output_prompt, found)
376 376 raise RuntimeError(e)
377 377 found = found[len(output_prompt):].strip()
378 378
379 379 if found!=submitted:
380 380 e = ('doctest failure for input_lines="%s" with '
381 381 'found_output="%s" and submitted output="%s"' %
382 382 (input_lines, found, submitted) )
383 383 raise RuntimeError(e)
384 384 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
385 385
386 386 def process_comment(self, data):
387 387 """Process data fPblock for COMMENT token."""
388 388 if not self.is_suppress:
389 389 return [data]
390 390
391 391 def save_image(self, image_file):
392 392 """
393 393 Saves the image file to disk.
394 394 """
395 395 self.ensure_pyplot()
396 396 command = 'plt.gcf().savefig("%s")'%image_file
397 397 #print 'SAVEFIG', command # dbg
398 398 self.process_input_line('bookmark ipy_thisdir', store_history=False)
399 399 self.process_input_line('cd -b ipy_savedir', store_history=False)
400 400 self.process_input_line(command, store_history=False)
401 401 self.process_input_line('cd -b ipy_thisdir', store_history=False)
402 402 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
403 403 self.clear_cout()
404 404
405 405
406 406 def process_block(self, block):
407 407 """
408 408 process block from the block_parser and return a list of processed lines
409 409 """
410 410 ret = []
411 411 output = None
412 412 input_lines = None
413 413 lineno = self.IP.execution_count
414 414
415 415 input_prompt = self.promptin%lineno
416 416 output_prompt = self.promptout%lineno
417 417 image_file = None
418 418 image_directive = None
419 419
420 420 for token, data in block:
421 421 if token==COMMENT:
422 422 out_data = self.process_comment(data)
423 423 elif token==INPUT:
424 424 (out_data, input_lines, output, is_doctest, image_file,
425 425 image_directive) = \
426 426 self.process_input(data, input_prompt, lineno)
427 427 elif token==OUTPUT:
428 428 out_data = \
429 429 self.process_output(data, output_prompt,
430 430 input_lines, output, is_doctest,
431 431 image_file)
432 432 if out_data:
433 433 ret.extend(out_data)
434 434
435 435 # save the image files
436 436 if image_file is not None:
437 437 self.save_image(image_file)
438 438
439 439 return ret, image_directive
440 440
441 441 def ensure_pyplot(self):
442 442 if self._pyplot_imported:
443 443 return
444 444 self.process_input_line('import matplotlib.pyplot as plt',
445 445 store_history=False)
446 446
447 447 def process_pure_python(self, content):
448 448 """
449 449 content is a list of strings. it is unedited directive conent
450 450
451 451 This runs it line by line in the InteractiveShell, prepends
452 452 prompts as needed capturing stderr and stdout, then returns
453 453 the content as a list as if it were ipython code
454 454 """
455 455 output = []
456 456 savefig = False # keep up with this to clear figure
457 457 multiline = False # to handle line continuation
458 multiline_start = None
458 459 fmtin = self.promptin
459 460
461 ct = 0
462
460 463 for lineno, line in enumerate(content):
461 464
462 465 line_stripped = line.strip()
463
464 466 if not len(line):
465 output.append(line) # preserve empty lines in output
467 output.append(line)
466 468 continue
467 469
468 470 # handle decorators
469 471 if line_stripped.startswith('@'):
470 472 output.extend([line])
471 473 if 'savefig' in line:
472 474 savefig = True # and need to clear figure
473 475 continue
474 476
475 477 # handle comments
476 478 if line_stripped.startswith('#'):
477 479 output.extend([line])
478 480 continue
479 481
480 # deal with multilines
481 if not multiline: # not currently on a multiline
482
483 if line_stripped.endswith('\\'): # now we are
482 # deal with lines checking for multiline
483 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
484 if not multiline:
485 modified = u"%s %s" % (fmtin % ct, line_stripped)
486 output.append(modified)
487 ct += 1
488 try:
489 ast.parse(line_stripped)
490 output.append(u'')
491 except Exception: # on a multiline
484 492 multiline = True
485 cont_len = len(str(lineno)) + 2
486 line_to_process = line.strip('\\')
487 output.extend([u"%s %s" % (fmtin%lineno,line)])
488 continue
489 else: # no we're still not
490 line_to_process = line.strip('\\')
491 else: # we are currently on a multiline
492 line_to_process += line.strip('\\')
493 if line_stripped.endswith('\\'): # and we still are
494 continuation = '.' * cont_len
495 output.extend([(u' %s: '+line_stripped) % continuation])
496 continue
497 # else go ahead and run this multiline then carry on
498
499 # get output of line
500 self.process_input_line(unicode(line_to_process.strip()),
501 store_history=False)
502 out_line = self.cout.getvalue()
503 self.clear_cout()
504
505 # clear current figure if plotted
506 if savefig:
493 multiline_start = lineno
494 if line_stripped.startswith('def '):
495 is_function = True
496 else: # still on a multiline
497 modified = u'%s %s' % (continuation, line)
498 output.append(modified)
499 try:
500 mod = ast.parse(
501 '\n'.join(content[multiline_start:lineno+1]))
502 if isinstance(mod.body[0], ast.FunctionDef):
503 # check to see if we have the whole function
504 for element in mod.body[0].body:
505 if isinstance(element, ast.Return):
506 multiline = False
507 else:
508 output.append(u'')
509 multiline = False
510 except Exception:
511 pass
512
513 if savefig: # clear figure if plotted
507 514 self.ensure_pyplot()
508 515 self.process_input_line('plt.clf()', store_history=False)
509 516 self.clear_cout()
510 517 savefig = False
511 518
512 # line numbers don't actually matter, they're replaced later
513 if not multiline:
514 in_line = u"%s %s" % (fmtin%lineno,line)
515
516 output.extend([in_line])
517 else:
518 output.extend([(u' %s: '+line_stripped) % continuation])
519 multiline = False
520 if len(out_line):
521 output.extend([out_line])
522 output.extend([u''])
523
524 519 return output
525 520
526 521 class IpythonDirective(Directive):
527 522
528 523 has_content = True
529 524 required_arguments = 0
530 525 optional_arguments = 4 # python, suppress, verbatim, doctest
531 526 final_argumuent_whitespace = True
532 527 option_spec = { 'python': directives.unchanged,
533 528 'suppress' : directives.flag,
534 529 'verbatim' : directives.flag,
535 530 'doctest' : directives.flag,
536 531 }
537 532
538 533 shell = EmbeddedSphinxShell()
539 534
540 535 def get_config_options(self):
541 536 # contains sphinx configuration variables
542 537 config = self.state.document.settings.env.config
543 538
544 539 # get config variables to set figure output directory
545 540 confdir = self.state.document.settings.env.app.confdir
546 541 savefig_dir = config.ipython_savefig_dir
547 542 source_dir = os.path.dirname(self.state.document.current_source)
548 543 if savefig_dir is None:
549 544 savefig_dir = config.html_static_path
550 545 if isinstance(savefig_dir, list):
551 546 savefig_dir = savefig_dir[0] # safe to assume only one path?
552 547 savefig_dir = os.path.join(confdir, savefig_dir)
553 548
554 549 # get regex and prompt stuff
555 550 rgxin = config.ipython_rgxin
556 551 rgxout = config.ipython_rgxout
557 552 promptin = config.ipython_promptin
558 553 promptout = config.ipython_promptout
559 554
560 555 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
561 556
562 557 def setup(self):
563 558 # get config values
564 559 (savefig_dir, source_dir, rgxin,
565 560 rgxout, promptin, promptout) = self.get_config_options()
566 561
567 562 # and attach to shell so we don't have to pass them around
568 563 self.shell.rgxin = rgxin
569 564 self.shell.rgxout = rgxout
570 565 self.shell.promptin = promptin
571 566 self.shell.promptout = promptout
572 567 self.shell.savefig_dir = savefig_dir
573 568 self.shell.source_dir = source_dir
574 569
575 570 # setup bookmark for saving figures directory
576 571
577 572 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
578 573 store_history=False)
579 574 self.shell.clear_cout()
580 575
581 576 return rgxin, rgxout, promptin, promptout
582 577
583 578
584 579 def teardown(self):
585 580 # delete last bookmark
586 581 self.shell.process_input_line('bookmark -d ipy_savedir',
587 582 store_history=False)
588 583 self.shell.clear_cout()
589 584
590 585 def run(self):
591 586 debug = False
592 587
593 588 #TODO, any reason block_parser can't be a method of embeddable shell
594 589 # then we wouldn't have to carry these around
595 590 rgxin, rgxout, promptin, promptout = self.setup()
596 591
597 592 options = self.options
598 593 self.shell.is_suppress = 'suppress' in options
599 594 self.shell.is_doctest = 'doctest' in options
600 595 self.shell.is_verbatim = 'verbatim' in options
601 596
602 597
603 598 # handle pure python code
604 599 if 'python' in self.arguments:
605 600 content = self.content
606 601 self.content = self.shell.process_pure_python(content)
607 602
608 603 parts = '\n'.join(self.content).split('\n\n')
609 604
610 605 lines = ['.. code-block:: ipython','']
611 606 figures = []
612 607
613 608 for part in parts:
614 609
615 610 block = block_parser(part, rgxin, rgxout, promptin, promptout)
616 611
617 612 if len(block):
618 613 rows, figure = self.shell.process_block(block)
619 614 for row in rows:
620 615 lines.extend([' %s'%line for line in row.split('\n')])
621 616
622 617 if figure is not None:
623 618 figures.append(figure)
624 619
625 620 #text = '\n'.join(lines)
626 621 #figs = '\n'.join(figures)
627 622
628 623 for figure in figures:
629 624 lines.append('')
630 625 lines.extend(figure.split('\n'))
631 626 lines.append('')
632 627
633 628 #print lines
634 629 if len(lines)>2:
635 630 if debug:
636 631 print '\n'.join(lines)
637 632 else: #NOTE: this raises some errors, what's it for?
638 633 #print 'INSERTING %d lines'%len(lines)
639 634 self.state_machine.insert_input(
640 635 lines, self.state_machine.input_lines.source(0))
641 636
642 637 text = '\n'.join(lines)
643 638 txtnode = nodes.literal_block(text, text)
644 639 txtnode['language'] = 'ipython'
645 640 #imgnode = nodes.image(figs)
646 641
647 642 # cleanup
648 643 self.teardown()
649 644
650 645 return []#, imgnode]
651 646
652 647 # Enable as a proper Sphinx directive
653 648 def setup(app):
654 649 setup.app = app
655 650
656 651 app.add_directive('ipython', IpythonDirective)
657 652 app.add_config_value('ipython_savefig_dir', None, True)
658 653 app.add_config_value('ipython_rgxin',
659 654 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
660 655 app.add_config_value('ipython_rgxout',
661 656 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
662 657 app.add_config_value('ipython_promptin', 'In [%d]:', True)
663 658 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
664 659
665 660
666 661 # Simple smoke test, needs to be converted to a proper automatic test.
667 662 def test():
668 663
669 664 examples = [
670 665 r"""
671 666 In [9]: pwd
672 667 Out[9]: '/home/jdhunter/py4science/book'
673 668
674 669 In [10]: cd bookdata/
675 670 /home/jdhunter/py4science/book/bookdata
676 671
677 672 In [2]: from pylab import *
678 673
679 674 In [2]: ion()
680 675
681 676 In [3]: im = imread('stinkbug.png')
682 677
683 678 @savefig mystinkbug.png width=4in
684 679 In [4]: imshow(im)
685 680 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
686 681
687 682 """,
688 683 r"""
689 684
690 685 In [1]: x = 'hello world'
691 686
692 687 # string methods can be
693 688 # used to alter the string
694 689 @doctest
695 690 In [2]: x.upper()
696 691 Out[2]: 'HELLO WORLD'
697 692
698 693 @verbatim
699 694 In [3]: x.st<TAB>
700 695 x.startswith x.strip
701 696 """,
702 697 r"""
703 698
704 699 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
705 700 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
706 701
707 702 In [131]: print url.split('&')
708 703 ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv']
709 704
710 705 In [60]: import urllib
711 706
712 707 """,
713 708 r"""\
714 709
715 710 In [133]: import numpy.random
716 711
717 712 @suppress
718 713 In [134]: numpy.random.seed(2358)
719 714
720 715 @doctest
721 716 In [135]: numpy.random.rand(10,2)
722 717 Out[135]:
723 718 array([[ 0.64524308, 0.59943846],
724 719 [ 0.47102322, 0.8715456 ],
725 720 [ 0.29370834, 0.74776844],
726 721 [ 0.99539577, 0.1313423 ],
727 722 [ 0.16250302, 0.21103583],
728 723 [ 0.81626524, 0.1312433 ],
729 724 [ 0.67338089, 0.72302393],
730 725 [ 0.7566368 , 0.07033696],
731 726 [ 0.22591016, 0.77731835],
732 727 [ 0.0072729 , 0.34273127]])
733 728
734 729 """,
735 730
736 731 r"""
737 732 In [106]: print x
738 733 jdh
739 734
740 735 In [109]: for i in range(10):
741 736 .....: print i
742 737 .....:
743 738 .....:
744 739 0
745 740 1
746 741 2
747 742 3
748 743 4
749 744 5
750 745 6
751 746 7
752 747 8
753 748 9
754 749 """,
755 750
756 751 r"""
757 752
758 753 In [144]: from pylab import *
759 754
760 755 In [145]: ion()
761 756
762 757 # use a semicolon to suppress the output
763 758 @savefig test_hist.png width=4in
764 759 In [151]: hist(np.random.randn(10000), 100);
765 760
766 761
767 762 @savefig test_plot.png width=4in
768 763 In [151]: plot(np.random.randn(10000), 'o');
769 764 """,
770 765
771 766 r"""
772 767 # use a semicolon to suppress the output
773 768 In [151]: plt.clf()
774 769
775 770 @savefig plot_simple.png width=4in
776 771 In [151]: plot([1,2,3])
777 772
778 773 @savefig hist_simple.png width=4in
779 774 In [151]: hist(np.random.randn(10000), 100);
780 775
781 776 """,
782 777 r"""
783 778 # update the current fig
784 779 In [151]: ylabel('number')
785 780
786 781 In [152]: title('normal distribution')
787 782
788 783
789 784 @savefig hist_with_text.png
790 785 In [153]: grid(True)
791 786
792 787 """,
793 788 ]
794 789 # skip local-file depending first example:
795 790 examples = examples[1:]
796 791
797 792 #ipython_directive.DEBUG = True # dbg
798 793 #options = dict(suppress=True) # dbg
799 794 options = dict()
800 795 for example in examples:
801 796 content = example.split('\n')
802 797 ipython_directive('debug', arguments=None, options=options,
803 798 content=content, lineno=0,
804 799 content_offset=None, block_text=None,
805 800 state=None, state_machine=None,
806 801 )
807 802
808 803 # Run test suite as a script
809 804 if __name__=='__main__':
810 805 if not os.path.isdir('_static'):
811 806 os.mkdir('_static')
812 807 test()
813 808 print 'All OK? Check figures in _static/'
General Comments 0
You need to be logged in to leave comments. Login now