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