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