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