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