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