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