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