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