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