##// END OF EJS Templates
Guard against self.state is None
y-p -
Show More
@@ -1,1042 +1,1045 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 249 def __init__(self, exec_lines=None, state=None):
250 250
251 251 self.cout = StringIO()
252 252
253 253 if exec_lines is None:
254 254 exec_lines = []
255 255
256 256 self.state = state
257 257
258 258 # Create config object for IPython
259 259 config = Config()
260 260 config.InteractiveShell.autocall = False
261 261 config.InteractiveShell.autoindent = False
262 262 config.InteractiveShell.colors = 'NoColor'
263 263
264 264 # create a profile so instance history isn't saved
265 265 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
266 266 profname = 'auto_profile_sphinx_build'
267 267 pdir = os.path.join(tmp_profile_dir,profname)
268 268 profile = ProfileDir.create_profile_dir(pdir)
269 269
270 270 # Create and initialize global ipython, but don't start its mainloop.
271 271 # This will persist across different EmbededSphinxShell instances.
272 272 IP = InteractiveShell.instance(config=config, profile_dir=profile)
273 273
274 274 # io.stdout redirect must be done after instantiating InteractiveShell
275 275 io.stdout = self.cout
276 276 io.stderr = self.cout
277 277
278 278 # For debugging, so we can see normal output, use this:
279 279 #from IPython.utils.io import Tee
280 280 #io.stdout = Tee(self.cout, channel='stdout') # dbg
281 281 #io.stderr = Tee(self.cout, channel='stderr') # dbg
282 282
283 283 # Store a few parts of IPython we'll need.
284 284 self.IP = IP
285 285 self.user_ns = self.IP.user_ns
286 286 self.user_global_ns = self.IP.user_global_ns
287 287
288 288 self.input = ''
289 289 self.output = ''
290 290
291 291 self.is_verbatim = False
292 292 self.is_doctest = False
293 293 self.is_suppress = False
294 294
295 295 # Optionally, provide more detailed information to shell.
296 296 self.directive = None
297 297
298 298 # on the first call to the savefig decorator, we'll import
299 299 # pyplot as plt so we can make a call to the plt.gcf().savefig
300 300 self._pyplot_imported = False
301 301
302 302 # Prepopulate the namespace.
303 303 for line in exec_lines:
304 304 self.process_input_line(line, store_history=False)
305 305
306 306 def clear_cout(self):
307 307 self.cout.seek(0)
308 308 self.cout.truncate(0)
309 309
310 310 def process_input_line(self, line, store_history=True):
311 311 """process the input, capturing stdout"""
312 312
313 313 stdout = sys.stdout
314 314 splitter = self.IP.input_splitter
315 315 try:
316 316 sys.stdout = self.cout
317 317 splitter.push(line)
318 318 more = splitter.push_accepts_more()
319 319 if not more:
320 320 source_raw = splitter.source_raw_reset()[1]
321 321 self.IP.run_cell(source_raw, store_history=store_history)
322 322 finally:
323 323 sys.stdout = stdout
324 324
325 325 def process_image(self, decorator):
326 326 """
327 327 # build out an image directive like
328 328 # .. image:: somefile.png
329 329 # :width 4in
330 330 #
331 331 # from an input like
332 332 # savefig somefile.png width=4in
333 333 """
334 334 savefig_dir = self.savefig_dir
335 335 source_dir = self.source_dir
336 336 saveargs = decorator.split(' ')
337 337 filename = saveargs[1]
338 338 # insert relative path to image file in source
339 339 outfile = os.path.relpath(os.path.join(savefig_dir,filename),
340 340 source_dir)
341 341
342 342 imagerows = ['.. image:: %s'%outfile]
343 343
344 344 for kwarg in saveargs[2:]:
345 345 arg, val = kwarg.split('=')
346 346 arg = arg.strip()
347 347 val = val.strip()
348 348 imagerows.append(' :%s: %s'%(arg, val))
349 349
350 350 image_file = os.path.basename(outfile) # only return file name
351 351 image_directive = '\n'.join(imagerows)
352 352 return image_file, image_directive
353 353
354 354 # Callbacks for each type of token
355 355 def process_input(self, data, input_prompt, lineno):
356 356 """
357 357 Process data block for INPUT token.
358 358
359 359 """
360 360 decorator, input, rest = data
361 361 image_file = None
362 362 image_directive = None
363 363
364 364 is_verbatim = decorator=='@verbatim' or self.is_verbatim
365 365 is_doctest = (decorator is not None and \
366 366 decorator.startswith('@doctest')) or self.is_doctest
367 367 is_suppress = decorator=='@suppress' or self.is_suppress
368 368 is_okexcept = decorator=='@okexcept' or self.is_okexcept
369 369 is_okwarning = decorator=='@okwarning' or self.is_okwarning
370 370 is_savefig = decorator is not None and \
371 371 decorator.startswith('@savefig')
372 372
373 373 input_lines = input.split('\n')
374 374 if len(input_lines) > 1:
375 375 if input_lines[-1] != "":
376 376 input_lines.append('') # make sure there's a blank line
377 377 # so splitter buffer gets reset
378 378
379 379 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
380 380
381 381 if is_savefig:
382 382 image_file, image_directive = self.process_image(decorator)
383 383
384 384 ret = []
385 385 is_semicolon = False
386 386
387 387 # Hold the execution count, if requested to do so.
388 388 if is_suppress and self.hold_count:
389 389 store_history = False
390 390 else:
391 391 store_history = True
392 392
393 393 # Note: catch_warnings is not thread safe
394 394 with warnings.catch_warnings(record=True) as ws:
395 395 for i, line in enumerate(input_lines):
396 396 if line.endswith(';'):
397 397 is_semicolon = True
398 398
399 399 if i == 0:
400 400 # process the first input line
401 401 if is_verbatim:
402 402 self.process_input_line('')
403 403 self.IP.execution_count += 1 # increment it anyway
404 404 else:
405 405 # only submit the line in non-verbatim mode
406 406 self.process_input_line(line, store_history=store_history)
407 407 formatted_line = '%s %s'%(input_prompt, line)
408 408 else:
409 409 # process a continuation line
410 410 if not is_verbatim:
411 411 self.process_input_line(line, store_history=store_history)
412 412
413 413 formatted_line = '%s %s'%(continuation, line)
414 414
415 415 if not is_suppress:
416 416 ret.append(formatted_line)
417 417
418 418 if not is_suppress and len(rest.strip()) and is_verbatim:
419 419 # the "rest" is the standard output of the
420 420 # input, which needs to be added in
421 421 # verbatim mode
422 422 ret.append(rest)
423 423
424 424 self.cout.seek(0)
425 425 output = self.cout.read()
426 426 if not is_suppress and not is_semicolon:
427 427 ret.append(output)
428 428 elif is_semicolon: # get spacing right
429 429 ret.append('')
430 430
431 431 # context information
432 filename = self.state.document.current_source
433 lineno = self.state.document.current_line
432 filename = "Unknown"
433 lineno = 0
434 if self.state:
435 filename = self.state.document.current_source
436 lineno = self.state.document.current_line
434 437
435 438 # output any exceptions raised during execution to stdout
436 439 # unless :okexcept: has been specified.
437 440 if not is_okexcept and "Traceback" in output:
438 441 s = "\nException in %s at block ending on line %s\n" % (filename, lineno)
439 442 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
440 443 sys.stdout.write('\n\n>>>' + ('-' * 73))
441 444 sys.stdout.write(s)
442 445 sys.stdout.write(output)
443 446 sys.stdout.write('<<<' + ('-' * 73) + '\n\n')
444 447
445 448 # output any warning raised during execution to stdout
446 449 # unless :okwarning: has been specified.
447 450 if not is_okwarning:
448 451 for w in ws:
449 452 s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno)
450 453 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
451 454 sys.stdout.write('\n\n>>>' + ('-' * 73))
452 455 sys.stdout.write(s)
453 456 sys.stdout.write(('-' * 76) + '\n')
454 457 s=warnings.formatwarning(w.message, w.category,
455 458 w.filename, w.lineno, w.line)
456 459 sys.stdout.write(s)
457 460 sys.stdout.write('<<<' + ('-' * 73) + '\n')
458 461
459 462 self.cout.truncate(0)
460 463 return (ret, input_lines, output, is_doctest, decorator, image_file,
461 464 image_directive)
462 465
463 466
464 467 def process_output(self, data, output_prompt,
465 468 input_lines, output, is_doctest, decorator, image_file):
466 469 """
467 470 Process data block for OUTPUT token.
468 471
469 472 """
470 473 TAB = ' ' * 4
471 474
472 475 if is_doctest and output is not None:
473 476
474 477 found = output
475 478 found = found.strip()
476 479 submitted = data.strip()
477 480
478 481 if self.directive is None:
479 482 source = 'Unavailable'
480 483 content = 'Unavailable'
481 484 else:
482 485 source = self.directive.state.document.current_source
483 486 content = self.directive.content
484 487 # Add tabs and join into a single string.
485 488 content = '\n'.join([TAB + line for line in content])
486 489
487 490 # Make sure the output contains the output prompt.
488 491 ind = found.find(output_prompt)
489 492 if ind < 0:
490 493 e = ('output does not contain output prompt\n\n'
491 494 'Document source: {0}\n\n'
492 495 'Raw content: \n{1}\n\n'
493 496 'Input line(s):\n{TAB}{2}\n\n'
494 497 'Output line(s):\n{TAB}{3}\n\n')
495 498 e = e.format(source, content, '\n'.join(input_lines),
496 499 repr(found), TAB=TAB)
497 500 raise RuntimeError(e)
498 501 found = found[len(output_prompt):].strip()
499 502
500 503 # Handle the actual doctest comparison.
501 504 if decorator.strip() == '@doctest':
502 505 # Standard doctest
503 506 if found != submitted:
504 507 e = ('doctest failure\n\n'
505 508 'Document source: {0}\n\n'
506 509 'Raw content: \n{1}\n\n'
507 510 'On input line(s):\n{TAB}{2}\n\n'
508 511 'we found output:\n{TAB}{3}\n\n'
509 512 'instead of the expected:\n{TAB}{4}\n\n')
510 513 e = e.format(source, content, '\n'.join(input_lines),
511 514 repr(found), repr(submitted), TAB=TAB)
512 515 raise RuntimeError(e)
513 516 else:
514 517 self.custom_doctest(decorator, input_lines, found, submitted)
515 518
516 519 def process_comment(self, data):
517 520 """Process data fPblock for COMMENT token."""
518 521 if not self.is_suppress:
519 522 return [data]
520 523
521 524 def save_image(self, image_file):
522 525 """
523 526 Saves the image file to disk.
524 527 """
525 528 self.ensure_pyplot()
526 529 command = 'plt.gcf().savefig("%s")'%image_file
527 530 #print 'SAVEFIG', command # dbg
528 531 self.process_input_line('bookmark ipy_thisdir', store_history=False)
529 532 self.process_input_line('cd -b ipy_savedir', store_history=False)
530 533 self.process_input_line(command, store_history=False)
531 534 self.process_input_line('cd -b ipy_thisdir', store_history=False)
532 535 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
533 536 self.clear_cout()
534 537
535 538 def process_block(self, block):
536 539 """
537 540 process block from the block_parser and return a list of processed lines
538 541 """
539 542 ret = []
540 543 output = None
541 544 input_lines = None
542 545 lineno = self.IP.execution_count
543 546
544 547 input_prompt = self.promptin % lineno
545 548 output_prompt = self.promptout % lineno
546 549 image_file = None
547 550 image_directive = None
548 551
549 552 for token, data in block:
550 553 if token == COMMENT:
551 554 out_data = self.process_comment(data)
552 555 elif token == INPUT:
553 556 (out_data, input_lines, output, is_doctest, decorator,
554 557 image_file, image_directive) = \
555 558 self.process_input(data, input_prompt, lineno)
556 559 elif token == OUTPUT:
557 560 out_data = \
558 561 self.process_output(data, output_prompt,
559 562 input_lines, output, is_doctest,
560 563 decorator, image_file)
561 564 if out_data:
562 565 ret.extend(out_data)
563 566
564 567 # save the image files
565 568 if image_file is not None:
566 569 self.save_image(image_file)
567 570
568 571 return ret, image_directive
569 572
570 573 def ensure_pyplot(self):
571 574 """
572 575 Ensures that pyplot has been imported into the embedded IPython shell.
573 576
574 577 Also, makes sure to set the backend appropriately if not set already.
575 578
576 579 """
577 580 # We are here if the @figure pseudo decorator was used. Thus, it's
578 581 # possible that we could be here even if python_mplbackend were set to
579 582 # `None`. That's also strange and perhaps worthy of raising an
580 583 # exception, but for now, we just set the backend to 'agg'.
581 584
582 585 if not self._pyplot_imported:
583 586 if 'matplotlib.backends' not in sys.modules:
584 587 # Then ipython_matplotlib was set to None but there was a
585 588 # call to the @figure decorator (and ipython_execlines did
586 589 # not set a backend).
587 590 #raise Exception("No backend was set, but @figure was used!")
588 591 import matplotlib
589 592 matplotlib.use('agg')
590 593
591 594 # Always import pyplot into embedded shell.
592 595 self.process_input_line('import matplotlib.pyplot as plt',
593 596 store_history=False)
594 597 self._pyplot_imported = True
595 598
596 599 def process_pure_python(self, content):
597 600 """
598 601 content is a list of strings. it is unedited directive content
599 602
600 603 This runs it line by line in the InteractiveShell, prepends
601 604 prompts as needed capturing stderr and stdout, then returns
602 605 the content as a list as if it were ipython code
603 606 """
604 607 output = []
605 608 savefig = False # keep up with this to clear figure
606 609 multiline = False # to handle line continuation
607 610 multiline_start = None
608 611 fmtin = self.promptin
609 612
610 613 ct = 0
611 614
612 615 for lineno, line in enumerate(content):
613 616
614 617 line_stripped = line.strip()
615 618 if not len(line):
616 619 output.append(line)
617 620 continue
618 621
619 622 # handle decorators
620 623 if line_stripped.startswith('@'):
621 624 output.extend([line])
622 625 if 'savefig' in line:
623 626 savefig = True # and need to clear figure
624 627 continue
625 628
626 629 # handle comments
627 630 if line_stripped.startswith('#'):
628 631 output.extend([line])
629 632 continue
630 633
631 634 # deal with lines checking for multiline
632 635 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
633 636 if not multiline:
634 637 modified = u"%s %s" % (fmtin % ct, line_stripped)
635 638 output.append(modified)
636 639 ct += 1
637 640 try:
638 641 ast.parse(line_stripped)
639 642 output.append(u'')
640 643 except Exception: # on a multiline
641 644 multiline = True
642 645 multiline_start = lineno
643 646 else: # still on a multiline
644 647 modified = u'%s %s' % (continuation, line)
645 648 output.append(modified)
646 649
647 650 # if the next line is indented, it should be part of multiline
648 651 if len(content) > lineno + 1:
649 652 nextline = content[lineno + 1]
650 653 if len(nextline) - len(nextline.lstrip()) > 3:
651 654 continue
652 655 try:
653 656 mod = ast.parse(
654 657 '\n'.join(content[multiline_start:lineno+1]))
655 658 if isinstance(mod.body[0], ast.FunctionDef):
656 659 # check to see if we have the whole function
657 660 for element in mod.body[0].body:
658 661 if isinstance(element, ast.Return):
659 662 multiline = False
660 663 else:
661 664 output.append(u'')
662 665 multiline = False
663 666 except Exception:
664 667 pass
665 668
666 669 if savefig: # clear figure if plotted
667 670 self.ensure_pyplot()
668 671 self.process_input_line('plt.clf()', store_history=False)
669 672 self.clear_cout()
670 673 savefig = False
671 674
672 675 return output
673 676
674 677 def custom_doctest(self, decorator, input_lines, found, submitted):
675 678 """
676 679 Perform a specialized doctest.
677 680
678 681 """
679 682 from .custom_doctests import doctests
680 683
681 684 args = decorator.split()
682 685 doctest_type = args[1]
683 686 if doctest_type in doctests:
684 687 doctests[doctest_type](self, args, input_lines, found, submitted)
685 688 else:
686 689 e = "Invalid option to @doctest: {0}".format(doctest_type)
687 690 raise Exception(e)
688 691
689 692
690 693 class IPythonDirective(Directive):
691 694
692 695 has_content = True
693 696 required_arguments = 0
694 697 optional_arguments = 4 # python, suppress, verbatim, doctest
695 698 final_argumuent_whitespace = True
696 699 option_spec = { 'python': directives.unchanged,
697 700 'suppress' : directives.flag,
698 701 'verbatim' : directives.flag,
699 702 'doctest' : directives.flag,
700 703 'okexcept': directives.flag,
701 704 'okwarning': directives.flag
702 705 }
703 706
704 707 shell = None
705 708
706 709 seen_docs = set()
707 710
708 711 def get_config_options(self):
709 712 # contains sphinx configuration variables
710 713 config = self.state.document.settings.env.config
711 714
712 715 # get config variables to set figure output directory
713 716 confdir = self.state.document.settings.env.app.confdir
714 717 savefig_dir = config.ipython_savefig_dir
715 718 source_dir = os.path.dirname(self.state.document.current_source)
716 719 if savefig_dir is None:
717 720 savefig_dir = config.html_static_path
718 721 if isinstance(savefig_dir, list):
719 722 savefig_dir = savefig_dir[0] # safe to assume only one path?
720 723 savefig_dir = os.path.join(confdir, savefig_dir)
721 724
722 725 # get regex and prompt stuff
723 726 rgxin = config.ipython_rgxin
724 727 rgxout = config.ipython_rgxout
725 728 promptin = config.ipython_promptin
726 729 promptout = config.ipython_promptout
727 730 mplbackend = config.ipython_mplbackend
728 731 exec_lines = config.ipython_execlines
729 732 hold_count = config.ipython_holdcount
730 733
731 734 return (savefig_dir, source_dir, rgxin, rgxout,
732 735 promptin, promptout, mplbackend, exec_lines, hold_count)
733 736
734 737 def setup(self):
735 738 # Get configuration values.
736 739 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
737 740 mplbackend, exec_lines, hold_count) = self.get_config_options()
738 741
739 742 if self.shell is None:
740 743 # We will be here many times. However, when the
741 744 # EmbeddedSphinxShell is created, its interactive shell member
742 745 # is the same for each instance.
743 746
744 747 if mplbackend:
745 748 import matplotlib
746 749 # Repeated calls to use() will not hurt us since `mplbackend`
747 750 # is the same each time.
748 751 matplotlib.use(mplbackend)
749 752
750 753 # Must be called after (potentially) importing matplotlib and
751 754 # setting its backend since exec_lines might import pylab.
752 755 self.shell = EmbeddedSphinxShell(exec_lines, self.state)
753 756
754 757 # Store IPython directive to enable better error messages
755 758 self.shell.directive = self
756 759
757 760 # reset the execution count if we haven't processed this doc
758 761 #NOTE: this may be borked if there are multiple seen_doc tmp files
759 762 #check time stamp?
760 763 if not self.state.document.current_source in self.seen_docs:
761 764 self.shell.IP.history_manager.reset()
762 765 self.shell.IP.execution_count = 1
763 766 self.shell.IP.prompt_manager.width = 0
764 767 self.seen_docs.add(self.state.document.current_source)
765 768
766 769 # and attach to shell so we don't have to pass them around
767 770 self.shell.rgxin = rgxin
768 771 self.shell.rgxout = rgxout
769 772 self.shell.promptin = promptin
770 773 self.shell.promptout = promptout
771 774 self.shell.savefig_dir = savefig_dir
772 775 self.shell.source_dir = source_dir
773 776 self.shell.hold_count = hold_count
774 777
775 778 # setup bookmark for saving figures directory
776 779 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
777 780 store_history=False)
778 781 self.shell.clear_cout()
779 782
780 783 return rgxin, rgxout, promptin, promptout
781 784
782 785 def teardown(self):
783 786 # delete last bookmark
784 787 self.shell.process_input_line('bookmark -d ipy_savedir',
785 788 store_history=False)
786 789 self.shell.clear_cout()
787 790
788 791 def run(self):
789 792 debug = False
790 793
791 794 #TODO, any reason block_parser can't be a method of embeddable shell
792 795 # then we wouldn't have to carry these around
793 796 rgxin, rgxout, promptin, promptout = self.setup()
794 797
795 798 options = self.options
796 799 self.shell.is_suppress = 'suppress' in options
797 800 self.shell.is_doctest = 'doctest' in options
798 801 self.shell.is_verbatim = 'verbatim' in options
799 802 self.shell.is_okexcept = 'okexcept' in options
800 803 self.shell.is_okwarning = 'okwarning' in options
801 804
802 805 # handle pure python code
803 806 if 'python' in self.arguments:
804 807 content = self.content
805 808 self.content = self.shell.process_pure_python(content)
806 809
807 810 parts = '\n'.join(self.content).split('\n\n')
808 811
809 812 lines = ['.. code-block:: ipython', '']
810 813 figures = []
811 814
812 815 for part in parts:
813 816 block = block_parser(part, rgxin, rgxout, promptin, promptout)
814 817 if len(block):
815 818 rows, figure = self.shell.process_block(block)
816 819 for row in rows:
817 820 lines.extend([' %s'%line for line in row.split('\n')])
818 821
819 822 if figure is not None:
820 823 figures.append(figure)
821 824
822 825 for figure in figures:
823 826 lines.append('')
824 827 lines.extend(figure.split('\n'))
825 828 lines.append('')
826 829
827 830 if len(lines)>2:
828 831 if debug:
829 832 print('\n'.join(lines))
830 833 else:
831 834 # This has to do with input, not output. But if we comment
832 835 # these lines out, then no IPython code will appear in the
833 836 # final output.
834 837 self.state_machine.insert_input(
835 838 lines, self.state_machine.input_lines.source(0))
836 839
837 840 # cleanup
838 841 self.teardown()
839 842
840 843 return []
841 844
842 845 # Enable as a proper Sphinx directive
843 846 def setup(app):
844 847 setup.app = app
845 848
846 849 app.add_directive('ipython', IPythonDirective)
847 850 app.add_config_value('ipython_savefig_dir', None, 'env')
848 851 app.add_config_value('ipython_rgxin',
849 852 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
850 853 app.add_config_value('ipython_rgxout',
851 854 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
852 855 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
853 856 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
854 857
855 858 # We could just let matplotlib pick whatever is specified as the default
856 859 # backend in the matplotlibrc file, but this would cause issues if the
857 860 # backend didn't work in headless environments. For this reason, 'agg'
858 861 # is a good default backend choice.
859 862 app.add_config_value('ipython_mplbackend', 'agg', 'env')
860 863
861 864 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
862 865 # __init__ method will treat it as [].
863 866 execlines = ['import numpy as np', 'import matplotlib.pyplot as plt']
864 867 app.add_config_value('ipython_execlines', execlines, 'env')
865 868
866 869 app.add_config_value('ipython_holdcount', True, 'env')
867 870
868 871 # Simple smoke test, needs to be converted to a proper automatic test.
869 872 def test():
870 873
871 874 examples = [
872 875 r"""
873 876 In [9]: pwd
874 877 Out[9]: '/home/jdhunter/py4science/book'
875 878
876 879 In [10]: cd bookdata/
877 880 /home/jdhunter/py4science/book/bookdata
878 881
879 882 In [2]: from pylab import *
880 883
881 884 In [2]: ion()
882 885
883 886 In [3]: im = imread('stinkbug.png')
884 887
885 888 @savefig mystinkbug.png width=4in
886 889 In [4]: imshow(im)
887 890 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
888 891
889 892 """,
890 893 r"""
891 894
892 895 In [1]: x = 'hello world'
893 896
894 897 # string methods can be
895 898 # used to alter the string
896 899 @doctest
897 900 In [2]: x.upper()
898 901 Out[2]: 'HELLO WORLD'
899 902
900 903 @verbatim
901 904 In [3]: x.st<TAB>
902 905 x.startswith x.strip
903 906 """,
904 907 r"""
905 908
906 909 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
907 910 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
908 911
909 912 In [131]: print url.split('&')
910 913 ['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']
911 914
912 915 In [60]: import urllib
913 916
914 917 """,
915 918 r"""\
916 919
917 920 In [133]: import numpy.random
918 921
919 922 @suppress
920 923 In [134]: numpy.random.seed(2358)
921 924
922 925 @doctest
923 926 In [135]: numpy.random.rand(10,2)
924 927 Out[135]:
925 928 array([[ 0.64524308, 0.59943846],
926 929 [ 0.47102322, 0.8715456 ],
927 930 [ 0.29370834, 0.74776844],
928 931 [ 0.99539577, 0.1313423 ],
929 932 [ 0.16250302, 0.21103583],
930 933 [ 0.81626524, 0.1312433 ],
931 934 [ 0.67338089, 0.72302393],
932 935 [ 0.7566368 , 0.07033696],
933 936 [ 0.22591016, 0.77731835],
934 937 [ 0.0072729 , 0.34273127]])
935 938
936 939 """,
937 940
938 941 r"""
939 942 In [106]: print x
940 943 jdh
941 944
942 945 In [109]: for i in range(10):
943 946 .....: print i
944 947 .....:
945 948 .....:
946 949 0
947 950 1
948 951 2
949 952 3
950 953 4
951 954 5
952 955 6
953 956 7
954 957 8
955 958 9
956 959 """,
957 960
958 961 r"""
959 962
960 963 In [144]: from pylab import *
961 964
962 965 In [145]: ion()
963 966
964 967 # use a semicolon to suppress the output
965 968 @savefig test_hist.png width=4in
966 969 In [151]: hist(np.random.randn(10000), 100);
967 970
968 971
969 972 @savefig test_plot.png width=4in
970 973 In [151]: plot(np.random.randn(10000), 'o');
971 974 """,
972 975
973 976 r"""
974 977 # use a semicolon to suppress the output
975 978 In [151]: plt.clf()
976 979
977 980 @savefig plot_simple.png width=4in
978 981 In [151]: plot([1,2,3])
979 982
980 983 @savefig hist_simple.png width=4in
981 984 In [151]: hist(np.random.randn(10000), 100);
982 985
983 986 """,
984 987 r"""
985 988 # update the current fig
986 989 In [151]: ylabel('number')
987 990
988 991 In [152]: title('normal distribution')
989 992
990 993
991 994 @savefig hist_with_text.png
992 995 In [153]: grid(True)
993 996
994 997 @doctest float
995 998 In [154]: 0.1 + 0.2
996 999 Out[154]: 0.3
997 1000
998 1001 @doctest float
999 1002 In [155]: np.arange(16).reshape(4,4)
1000 1003 Out[155]:
1001 1004 array([[ 0, 1, 2, 3],
1002 1005 [ 4, 5, 6, 7],
1003 1006 [ 8, 9, 10, 11],
1004 1007 [12, 13, 14, 15]])
1005 1008
1006 1009 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1007 1010
1008 1011 In [2]: x[0,0] = np.inf
1009 1012
1010 1013 In [3]: x[0,1] = np.nan
1011 1014
1012 1015 @doctest float
1013 1016 In [4]: x
1014 1017 Out[4]:
1015 1018 array([[ inf, nan, 2., 3.],
1016 1019 [ 4., 5., 6., 7.],
1017 1020 [ 8., 9., 10., 11.],
1018 1021 [ 12., 13., 14., 15.]])
1019 1022
1020 1023
1021 1024 """,
1022 1025 ]
1023 1026 # skip local-file depending first example:
1024 1027 examples = examples[1:]
1025 1028
1026 1029 #ipython_directive.DEBUG = True # dbg
1027 1030 #options = dict(suppress=True) # dbg
1028 1031 options = dict()
1029 1032 for example in examples:
1030 1033 content = example.split('\n')
1031 1034 IPythonDirective('debug', arguments=None, options=options,
1032 1035 content=content, lineno=0,
1033 1036 content_offset=None, block_text=None,
1034 1037 state=None, state_machine=None,
1035 1038 )
1036 1039
1037 1040 # Run test suite as a script
1038 1041 if __name__=='__main__':
1039 1042 if not os.path.isdir('_static'):
1040 1043 os.mkdir('_static')
1041 1044 test()
1042 1045 print('All OK? Check figures in _static/')
General Comments 0
You need to be logged in to leave comments. Login now