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