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