##// END OF EJS Templates
Add output to IPython directive when in verbatim mode....
chebee7i -
Show More
@@ -1,1071 +1,1081 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 In addition to the Pseudo-Decorators/options described at the above link,
90 90 several enhancements have been made.
91 91 The directive will emit a message to the console at build-time if
92 92 code-execution resulted in an exception or warning. You can suppress these on
93 93 a per-block basis by specifying the :okexcept: or :okwarning: options:
94 94
95 95 .. code-block:: rst
96 96
97 97 .. ipython::
98 98 :okexcept:
99 99 :okwarning:
100 100
101 101 In [1]: 1/0
102 102 In [2]: # raise warning.
103 103
104 104 ToDo
105 105 ----
106 106
107 107 - Turn the ad-hoc test() function into a real test suite.
108 108 - Break up ipython-specific functionality from matplotlib stuff into better
109 109 separated code.
110 110
111 111 Authors
112 112 -------
113 113
114 114 - John D Hunter: orignal author.
115 115 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
116 116 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
117 117 - Skipper Seabold, refactoring, cleanups, pure python addition
118 118 """
119 119 from __future__ import print_function
120 120
121 121 #-----------------------------------------------------------------------------
122 122 # Imports
123 123 #-----------------------------------------------------------------------------
124 124
125 125 # Stdlib
126 126 import os
127 127 import re
128 128 import sys
129 129 import tempfile
130 130 import ast
131 131 import warnings
132 132
133 133 # To keep compatibility with various python versions
134 134 try:
135 135 from hashlib import md5
136 136 except ImportError:
137 137 from md5 import md5
138 138
139 139 # Third-party
140 140 import sphinx
141 141 from docutils.parsers.rst import directives
142 142 from docutils import nodes
143 143 from sphinx.util.compat import Directive
144 144
145 145 # Our own
146 146 from IPython import Config, 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 ouput, 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 # we're assuming at most one decorator -- may need to
207 207 # rethink
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.InteractiveShell.autocall = False
283 283 config.InteractiveShell.autoindent = False
284 284 config.InteractiveShell.colors = 'NoColor'
285 285
286 286 # create a profile so instance history isn't saved
287 287 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
288 288 profname = 'auto_profile_sphinx_build'
289 289 pdir = os.path.join(tmp_profile_dir,profname)
290 290 profile = ProfileDir.create_profile_dir(pdir)
291 291
292 292 # Create and initialize global ipython, but don't start its mainloop.
293 293 # This will persist across different EmbededSphinxShell instances.
294 294 IP = InteractiveShell.instance(config=config, profile_dir=profile)
295 295
296 296 # io.stdout redirect must be done after instantiating InteractiveShell
297 297 io.stdout = self.cout
298 298 io.stderr = self.cout
299 299
300 300 # For debugging, so we can see normal output, use this:
301 301 #from IPython.utils.io import Tee
302 302 #io.stdout = Tee(self.cout, channel='stdout') # dbg
303 303 #io.stderr = Tee(self.cout, channel='stderr') # dbg
304 304
305 305 # Store a few parts of IPython we'll need.
306 306 self.IP = IP
307 307 self.user_ns = self.IP.user_ns
308 308 self.user_global_ns = self.IP.user_global_ns
309 309
310 310 self.input = ''
311 311 self.output = ''
312 312
313 313 self.is_verbatim = False
314 314 self.is_doctest = False
315 315 self.is_suppress = False
316 316
317 317 # Optionally, provide more detailed information to shell.
318 318 # this is assigned by the SetUp method of IPythonDirective
319 319 # to point at itself.
320 320 #
321 321 # So, you can access handy things at self.directive.state
322 322 self.directive = None
323 323
324 324 # on the first call to the savefig decorator, we'll import
325 325 # pyplot as plt so we can make a call to the plt.gcf().savefig
326 326 self._pyplot_imported = False
327 327
328 328 # Prepopulate the namespace.
329 329 for line in exec_lines:
330 330 self.process_input_line(line, store_history=False)
331 331
332 332 def clear_cout(self):
333 333 self.cout.seek(0)
334 334 self.cout.truncate(0)
335 335
336 336 def process_input_line(self, line, store_history=True):
337 337 """process the input, capturing stdout"""
338 338
339 339 stdout = sys.stdout
340 340 splitter = self.IP.input_splitter
341 341 try:
342 342 sys.stdout = self.cout
343 343 splitter.push(line)
344 344 more = splitter.push_accepts_more()
345 345 if not more:
346 346 source_raw = splitter.raw_reset()
347 347 self.IP.run_cell(source_raw, store_history=store_history)
348 348 finally:
349 349 sys.stdout = stdout
350 350
351 351 def process_image(self, decorator):
352 352 """
353 353 # build out an image directive like
354 354 # .. image:: somefile.png
355 355 # :width 4in
356 356 #
357 357 # from an input like
358 358 # savefig somefile.png width=4in
359 359 """
360 360 savefig_dir = self.savefig_dir
361 361 source_dir = self.source_dir
362 362 saveargs = decorator.split(' ')
363 363 filename = saveargs[1]
364 364 # insert relative path to image file in source
365 365 outfile = os.path.relpath(os.path.join(savefig_dir,filename),
366 366 source_dir)
367 367
368 368 imagerows = ['.. image:: %s'%outfile]
369 369
370 370 for kwarg in saveargs[2:]:
371 371 arg, val = kwarg.split('=')
372 372 arg = arg.strip()
373 373 val = val.strip()
374 374 imagerows.append(' :%s: %s'%(arg, val))
375 375
376 376 image_file = os.path.basename(outfile) # only return file name
377 377 image_directive = '\n'.join(imagerows)
378 378 return image_file, image_directive
379 379
380 380 # Callbacks for each type of token
381 381 def process_input(self, data, input_prompt, lineno):
382 382 """
383 383 Process data block for INPUT token.
384 384
385 385 """
386 386 decorator, input, rest = data
387 387 image_file = None
388 388 image_directive = None
389 389
390 390 is_verbatim = decorator=='@verbatim' or self.is_verbatim
391 391 is_doctest = (decorator is not None and \
392 392 decorator.startswith('@doctest')) or self.is_doctest
393 393 is_suppress = decorator=='@suppress' or self.is_suppress
394 394 is_okexcept = decorator=='@okexcept' or self.is_okexcept
395 395 is_okwarning = decorator=='@okwarning' or self.is_okwarning
396 396 is_savefig = decorator is not None and \
397 397 decorator.startswith('@savefig')
398 398
399 399 input_lines = input.split('\n')
400 400 if len(input_lines) > 1:
401 401 if input_lines[-1] != "":
402 402 input_lines.append('') # make sure there's a blank line
403 403 # so splitter buffer gets reset
404 404
405 405 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
406 406
407 407 if is_savefig:
408 408 image_file, image_directive = self.process_image(decorator)
409 409
410 410 ret = []
411 411 is_semicolon = False
412 412
413 413 # Hold the execution count, if requested to do so.
414 414 if is_suppress and self.hold_count:
415 415 store_history = False
416 416 else:
417 417 store_history = True
418 418
419 419 # Note: catch_warnings is not thread safe
420 420 with warnings.catch_warnings(record=True) as ws:
421 421 for i, line in enumerate(input_lines):
422 422 if line.endswith(';'):
423 423 is_semicolon = True
424 424
425 425 if i == 0:
426 426 # process the first input line
427 427 if is_verbatim:
428 428 self.process_input_line('')
429 429 self.IP.execution_count += 1 # increment it anyway
430 430 else:
431 431 # only submit the line in non-verbatim mode
432 432 self.process_input_line(line, store_history=store_history)
433 433 formatted_line = '%s %s'%(input_prompt, line)
434 434 else:
435 435 # process a continuation line
436 436 if not is_verbatim:
437 437 self.process_input_line(line, store_history=store_history)
438 438
439 439 formatted_line = '%s %s'%(continuation, line)
440 440
441 441 if not is_suppress:
442 442 ret.append(formatted_line)
443 443
444 444 if not is_suppress and len(rest.strip()) and is_verbatim:
445 445 # the "rest" is the standard output of the
446 446 # input, which needs to be added in
447 447 # verbatim mode
448 448 ret.append(rest)
449 449
450 450 self.cout.seek(0)
451 451 output = self.cout.read()
452 452 if not is_suppress and not is_semicolon:
453 453 ret.append(output)
454 454 elif is_semicolon: # get spacing right
455 455 ret.append('')
456 456
457 457 # context information
458 458 filename = "Unknown"
459 459 lineno = 0
460 460 if self.directive.state:
461 461 filename = self.directive.state.document.current_source
462 462 lineno = self.directive.state.document.current_line
463 463
464 464 # output any exceptions raised during execution to stdout
465 465 # unless :okexcept: has been specified.
466 466 if not is_okexcept and "Traceback" in output:
467 467 s = "\nException in %s at block ending on line %s\n" % (filename, lineno)
468 468 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
469 469 sys.stdout.write('\n\n>>>' + ('-' * 73))
470 470 sys.stdout.write(s)
471 471 sys.stdout.write(output)
472 472 sys.stdout.write('<<<' + ('-' * 73) + '\n\n')
473 473
474 474 # output any warning raised during execution to stdout
475 475 # unless :okwarning: has been specified.
476 476 if not is_okwarning:
477 477 for w in ws:
478 478 s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno)
479 479 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
480 480 sys.stdout.write('\n\n>>>' + ('-' * 73))
481 481 sys.stdout.write(s)
482 482 sys.stdout.write(('-' * 76) + '\n')
483 483 s=warnings.formatwarning(w.message, w.category,
484 484 w.filename, w.lineno, w.line)
485 485 sys.stdout.write(s)
486 486 sys.stdout.write('<<<' + ('-' * 73) + '\n')
487 487
488 488 self.cout.truncate(0)
489 489 return (ret, input_lines, output, is_doctest, decorator, image_file,
490 490 image_directive)
491 491
492 492
493 493 def process_output(self, data, output_prompt,
494 494 input_lines, output, is_doctest, decorator, image_file):
495 495 """
496 496 Process data block for OUTPUT token.
497 497
498 498 """
499 499 TAB = ' ' * 4
500 500
501 501 if is_doctest and output is not None:
502 502
503 503 found = output
504 504 found = found.strip()
505 505 submitted = data.strip()
506 506
507 507 if self.directive is None:
508 508 source = 'Unavailable'
509 509 content = 'Unavailable'
510 510 else:
511 511 source = self.directive.state.document.current_source
512 512 content = self.directive.content
513 513 # Add tabs and join into a single string.
514 514 content = '\n'.join([TAB + line for line in content])
515 515
516 516 # Make sure the output contains the output prompt.
517 517 ind = found.find(output_prompt)
518 518 if ind < 0:
519 519 e = ('output does not contain output prompt\n\n'
520 520 'Document source: {0}\n\n'
521 521 'Raw content: \n{1}\n\n'
522 522 'Input line(s):\n{TAB}{2}\n\n'
523 523 'Output line(s):\n{TAB}{3}\n\n')
524 524 e = e.format(source, content, '\n'.join(input_lines),
525 525 repr(found), TAB=TAB)
526 526 raise RuntimeError(e)
527 527 found = found[len(output_prompt):].strip()
528 528
529 529 # Handle the actual doctest comparison.
530 530 if decorator.strip() == '@doctest':
531 531 # Standard doctest
532 532 if found != submitted:
533 533 e = ('doctest failure\n\n'
534 534 'Document source: {0}\n\n'
535 535 'Raw content: \n{1}\n\n'
536 536 'On input line(s):\n{TAB}{2}\n\n'
537 537 'we found output:\n{TAB}{3}\n\n'
538 538 'instead of the expected:\n{TAB}{4}\n\n')
539 539 e = e.format(source, content, '\n'.join(input_lines),
540 540 repr(found), repr(submitted), TAB=TAB)
541 541 raise RuntimeError(e)
542 542 else:
543 543 self.custom_doctest(decorator, input_lines, found, submitted)
544 544
545 # Make sure output is processed when in verbatim mode.
546 # https://github.com/ipython/ipython/issues/5776
547 is_verbatim = decorator=='@verbatim' or self.is_verbatim
548 if is_verbatim:
549 out_data = data
550 else:
551 out_data = None
552
553 return out_data
554
545 555 def process_comment(self, data):
546 556 """Process data fPblock for COMMENT token."""
547 557 if not self.is_suppress:
548 558 return [data]
549 559
550 560 def save_image(self, image_file):
551 561 """
552 562 Saves the image file to disk.
553 563 """
554 564 self.ensure_pyplot()
555 565 command = 'plt.gcf().savefig("%s")'%image_file
556 566 #print 'SAVEFIG', command # dbg
557 567 self.process_input_line('bookmark ipy_thisdir', store_history=False)
558 568 self.process_input_line('cd -b ipy_savedir', store_history=False)
559 569 self.process_input_line(command, store_history=False)
560 570 self.process_input_line('cd -b ipy_thisdir', store_history=False)
561 571 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
562 572 self.clear_cout()
563 573
564 574 def process_block(self, block):
565 575 """
566 576 process block from the block_parser and return a list of processed lines
567 577 """
568 578 ret = []
569 579 output = None
570 580 input_lines = None
571 581 lineno = self.IP.execution_count
572 582
573 583 input_prompt = self.promptin % lineno
574 584 output_prompt = self.promptout % lineno
575 585 image_file = None
576 586 image_directive = None
577 587
578 588 for token, data in block:
579 589 if token == COMMENT:
580 590 out_data = self.process_comment(data)
581 591 elif token == INPUT:
582 592 (out_data, input_lines, output, is_doctest, decorator,
583 593 image_file, image_directive) = \
584 594 self.process_input(data, input_prompt, lineno)
585 595 elif token == OUTPUT:
586 596 out_data = \
587 597 self.process_output(data, output_prompt,
588 598 input_lines, output, is_doctest,
589 599 decorator, image_file)
590 600 if out_data:
591 601 ret.extend(out_data)
592 602
593 603 # save the image files
594 604 if image_file is not None:
595 605 self.save_image(image_file)
596 606
597 607 return ret, image_directive
598 608
599 609 def ensure_pyplot(self):
600 610 """
601 611 Ensures that pyplot has been imported into the embedded IPython shell.
602 612
603 613 Also, makes sure to set the backend appropriately if not set already.
604 614
605 615 """
606 616 # We are here if the @figure pseudo decorator was used. Thus, it's
607 617 # possible that we could be here even if python_mplbackend were set to
608 618 # `None`. That's also strange and perhaps worthy of raising an
609 619 # exception, but for now, we just set the backend to 'agg'.
610 620
611 621 if not self._pyplot_imported:
612 622 if 'matplotlib.backends' not in sys.modules:
613 623 # Then ipython_matplotlib was set to None but there was a
614 624 # call to the @figure decorator (and ipython_execlines did
615 625 # not set a backend).
616 626 #raise Exception("No backend was set, but @figure was used!")
617 627 import matplotlib
618 628 matplotlib.use('agg')
619 629
620 630 # Always import pyplot into embedded shell.
621 631 self.process_input_line('import matplotlib.pyplot as plt',
622 632 store_history=False)
623 633 self._pyplot_imported = True
624 634
625 635 def process_pure_python(self, content):
626 636 """
627 637 content is a list of strings. it is unedited directive content
628 638
629 639 This runs it line by line in the InteractiveShell, prepends
630 640 prompts as needed capturing stderr and stdout, then returns
631 641 the content as a list as if it were ipython code
632 642 """
633 643 output = []
634 644 savefig = False # keep up with this to clear figure
635 645 multiline = False # to handle line continuation
636 646 multiline_start = None
637 647 fmtin = self.promptin
638 648
639 649 ct = 0
640 650
641 651 for lineno, line in enumerate(content):
642 652
643 653 line_stripped = line.strip()
644 654 if not len(line):
645 655 output.append(line)
646 656 continue
647 657
648 658 # handle decorators
649 659 if line_stripped.startswith('@'):
650 660 output.extend([line])
651 661 if 'savefig' in line:
652 662 savefig = True # and need to clear figure
653 663 continue
654 664
655 665 # handle comments
656 666 if line_stripped.startswith('#'):
657 667 output.extend([line])
658 668 continue
659 669
660 670 # deal with lines checking for multiline
661 671 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
662 672 if not multiline:
663 673 modified = u"%s %s" % (fmtin % ct, line_stripped)
664 674 output.append(modified)
665 675 ct += 1
666 676 try:
667 677 ast.parse(line_stripped)
668 678 output.append(u'')
669 679 except Exception: # on a multiline
670 680 multiline = True
671 681 multiline_start = lineno
672 682 else: # still on a multiline
673 683 modified = u'%s %s' % (continuation, line)
674 684 output.append(modified)
675 685
676 686 # if the next line is indented, it should be part of multiline
677 687 if len(content) > lineno + 1:
678 688 nextline = content[lineno + 1]
679 689 if len(nextline) - len(nextline.lstrip()) > 3:
680 690 continue
681 691 try:
682 692 mod = ast.parse(
683 693 '\n'.join(content[multiline_start:lineno+1]))
684 694 if isinstance(mod.body[0], ast.FunctionDef):
685 695 # check to see if we have the whole function
686 696 for element in mod.body[0].body:
687 697 if isinstance(element, ast.Return):
688 698 multiline = False
689 699 else:
690 700 output.append(u'')
691 701 multiline = False
692 702 except Exception:
693 703 pass
694 704
695 705 if savefig: # clear figure if plotted
696 706 self.ensure_pyplot()
697 707 self.process_input_line('plt.clf()', store_history=False)
698 708 self.clear_cout()
699 709 savefig = False
700 710
701 711 return output
702 712
703 713 def custom_doctest(self, decorator, input_lines, found, submitted):
704 714 """
705 715 Perform a specialized doctest.
706 716
707 717 """
708 718 from .custom_doctests import doctests
709 719
710 720 args = decorator.split()
711 721 doctest_type = args[1]
712 722 if doctest_type in doctests:
713 723 doctests[doctest_type](self, args, input_lines, found, submitted)
714 724 else:
715 725 e = "Invalid option to @doctest: {0}".format(doctest_type)
716 726 raise Exception(e)
717 727
718 728
719 729 class IPythonDirective(Directive):
720 730
721 731 has_content = True
722 732 required_arguments = 0
723 733 optional_arguments = 4 # python, suppress, verbatim, doctest
724 734 final_argumuent_whitespace = True
725 735 option_spec = { 'python': directives.unchanged,
726 736 'suppress' : directives.flag,
727 737 'verbatim' : directives.flag,
728 738 'doctest' : directives.flag,
729 739 'okexcept': directives.flag,
730 740 'okwarning': directives.flag
731 741 }
732 742
733 743 shell = None
734 744
735 745 seen_docs = set()
736 746
737 747 def get_config_options(self):
738 748 # contains sphinx configuration variables
739 749 config = self.state.document.settings.env.config
740 750
741 751 # get config variables to set figure output directory
742 752 confdir = self.state.document.settings.env.app.confdir
743 753 savefig_dir = config.ipython_savefig_dir
744 754 source_dir = os.path.dirname(self.state.document.current_source)
745 755 if savefig_dir is None:
746 756 savefig_dir = config.html_static_path
747 757 if isinstance(savefig_dir, list):
748 758 savefig_dir = savefig_dir[0] # safe to assume only one path?
749 759 savefig_dir = os.path.join(confdir, savefig_dir)
750 760
751 761 # get regex and prompt stuff
752 762 rgxin = config.ipython_rgxin
753 763 rgxout = config.ipython_rgxout
754 764 promptin = config.ipython_promptin
755 765 promptout = config.ipython_promptout
756 766 mplbackend = config.ipython_mplbackend
757 767 exec_lines = config.ipython_execlines
758 768 hold_count = config.ipython_holdcount
759 769
760 770 return (savefig_dir, source_dir, rgxin, rgxout,
761 771 promptin, promptout, mplbackend, exec_lines, hold_count)
762 772
763 773 def setup(self):
764 774 # Get configuration values.
765 775 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
766 776 mplbackend, exec_lines, hold_count) = self.get_config_options()
767 777
768 778 if self.shell is None:
769 779 # We will be here many times. However, when the
770 780 # EmbeddedSphinxShell is created, its interactive shell member
771 781 # is the same for each instance.
772 782
773 783 if mplbackend:
774 784 import matplotlib
775 785 # Repeated calls to use() will not hurt us since `mplbackend`
776 786 # is the same each time.
777 787 matplotlib.use(mplbackend)
778 788
779 789 # Must be called after (potentially) importing matplotlib and
780 790 # setting its backend since exec_lines might import pylab.
781 791 self.shell = EmbeddedSphinxShell(exec_lines)
782 792
783 793 # Store IPython directive to enable better error messages
784 794 self.shell.directive = self
785 795
786 796 # reset the execution count if we haven't processed this doc
787 797 #NOTE: this may be borked if there are multiple seen_doc tmp files
788 798 #check time stamp?
789 799 if not self.state.document.current_source in self.seen_docs:
790 800 self.shell.IP.history_manager.reset()
791 801 self.shell.IP.execution_count = 1
792 802 self.shell.IP.prompt_manager.width = 0
793 803 self.seen_docs.add(self.state.document.current_source)
794 804
795 805 # and attach to shell so we don't have to pass them around
796 806 self.shell.rgxin = rgxin
797 807 self.shell.rgxout = rgxout
798 808 self.shell.promptin = promptin
799 809 self.shell.promptout = promptout
800 810 self.shell.savefig_dir = savefig_dir
801 811 self.shell.source_dir = source_dir
802 812 self.shell.hold_count = hold_count
803 813
804 814 # setup bookmark for saving figures directory
805 815 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
806 816 store_history=False)
807 817 self.shell.clear_cout()
808 818
809 819 return rgxin, rgxout, promptin, promptout
810 820
811 821 def teardown(self):
812 822 # delete last bookmark
813 823 self.shell.process_input_line('bookmark -d ipy_savedir',
814 824 store_history=False)
815 825 self.shell.clear_cout()
816 826
817 827 def run(self):
818 828 debug = False
819 829
820 830 #TODO, any reason block_parser can't be a method of embeddable shell
821 831 # then we wouldn't have to carry these around
822 832 rgxin, rgxout, promptin, promptout = self.setup()
823 833
824 834 options = self.options
825 835 self.shell.is_suppress = 'suppress' in options
826 836 self.shell.is_doctest = 'doctest' in options
827 837 self.shell.is_verbatim = 'verbatim' in options
828 838 self.shell.is_okexcept = 'okexcept' in options
829 839 self.shell.is_okwarning = 'okwarning' in options
830 840
831 841 # handle pure python code
832 842 if 'python' in self.arguments:
833 843 content = self.content
834 844 self.content = self.shell.process_pure_python(content)
835 845
836 846 parts = '\n'.join(self.content).split('\n\n')
837 847
838 848 lines = ['.. code-block:: ipython', '']
839 849 figures = []
840 850
841 851 for part in parts:
842 852 block = block_parser(part, rgxin, rgxout, promptin, promptout)
843 853 if len(block):
844 854 rows, figure = self.shell.process_block(block)
845 855 for row in rows:
846 856 lines.extend([' %s'%line for line in row.split('\n')])
847 857
848 858 if figure is not None:
849 859 figures.append(figure)
850 860
851 861 for figure in figures:
852 862 lines.append('')
853 863 lines.extend(figure.split('\n'))
854 864 lines.append('')
855 865
856 866 if len(lines)>2:
857 867 if debug:
858 868 print('\n'.join(lines))
859 869 else:
860 870 # This has to do with input, not output. But if we comment
861 871 # these lines out, then no IPython code will appear in the
862 872 # final output.
863 873 self.state_machine.insert_input(
864 874 lines, self.state_machine.input_lines.source(0))
865 875
866 876 # cleanup
867 877 self.teardown()
868 878
869 879 return []
870 880
871 881 # Enable as a proper Sphinx directive
872 882 def setup(app):
873 883 setup.app = app
874 884
875 885 app.add_directive('ipython', IPythonDirective)
876 886 app.add_config_value('ipython_savefig_dir', None, 'env')
877 887 app.add_config_value('ipython_rgxin',
878 888 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
879 889 app.add_config_value('ipython_rgxout',
880 890 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
881 891 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
882 892 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
883 893
884 894 # We could just let matplotlib pick whatever is specified as the default
885 895 # backend in the matplotlibrc file, but this would cause issues if the
886 896 # backend didn't work in headless environments. For this reason, 'agg'
887 897 # is a good default backend choice.
888 898 app.add_config_value('ipython_mplbackend', 'agg', 'env')
889 899
890 900 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
891 901 # __init__ method will treat it as [].
892 902 execlines = ['import numpy as np', 'import matplotlib.pyplot as plt']
893 903 app.add_config_value('ipython_execlines', execlines, 'env')
894 904
895 905 app.add_config_value('ipython_holdcount', True, 'env')
896 906
897 907 # Simple smoke test, needs to be converted to a proper automatic test.
898 908 def test():
899 909
900 910 examples = [
901 911 r"""
902 912 In [9]: pwd
903 913 Out[9]: '/home/jdhunter/py4science/book'
904 914
905 915 In [10]: cd bookdata/
906 916 /home/jdhunter/py4science/book/bookdata
907 917
908 918 In [2]: from pylab import *
909 919
910 920 In [2]: ion()
911 921
912 922 In [3]: im = imread('stinkbug.png')
913 923
914 924 @savefig mystinkbug.png width=4in
915 925 In [4]: imshow(im)
916 926 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
917 927
918 928 """,
919 929 r"""
920 930
921 931 In [1]: x = 'hello world'
922 932
923 933 # string methods can be
924 934 # used to alter the string
925 935 @doctest
926 936 In [2]: x.upper()
927 937 Out[2]: 'HELLO WORLD'
928 938
929 939 @verbatim
930 940 In [3]: x.st<TAB>
931 941 x.startswith x.strip
932 942 """,
933 943 r"""
934 944
935 945 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
936 946 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
937 947
938 948 In [131]: print url.split('&')
939 949 ['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']
940 950
941 951 In [60]: import urllib
942 952
943 953 """,
944 954 r"""\
945 955
946 956 In [133]: import numpy.random
947 957
948 958 @suppress
949 959 In [134]: numpy.random.seed(2358)
950 960
951 961 @doctest
952 962 In [135]: numpy.random.rand(10,2)
953 963 Out[135]:
954 964 array([[ 0.64524308, 0.59943846],
955 965 [ 0.47102322, 0.8715456 ],
956 966 [ 0.29370834, 0.74776844],
957 967 [ 0.99539577, 0.1313423 ],
958 968 [ 0.16250302, 0.21103583],
959 969 [ 0.81626524, 0.1312433 ],
960 970 [ 0.67338089, 0.72302393],
961 971 [ 0.7566368 , 0.07033696],
962 972 [ 0.22591016, 0.77731835],
963 973 [ 0.0072729 , 0.34273127]])
964 974
965 975 """,
966 976
967 977 r"""
968 978 In [106]: print x
969 979 jdh
970 980
971 981 In [109]: for i in range(10):
972 982 .....: print i
973 983 .....:
974 984 .....:
975 985 0
976 986 1
977 987 2
978 988 3
979 989 4
980 990 5
981 991 6
982 992 7
983 993 8
984 994 9
985 995 """,
986 996
987 997 r"""
988 998
989 999 In [144]: from pylab import *
990 1000
991 1001 In [145]: ion()
992 1002
993 1003 # use a semicolon to suppress the output
994 1004 @savefig test_hist.png width=4in
995 1005 In [151]: hist(np.random.randn(10000), 100);
996 1006
997 1007
998 1008 @savefig test_plot.png width=4in
999 1009 In [151]: plot(np.random.randn(10000), 'o');
1000 1010 """,
1001 1011
1002 1012 r"""
1003 1013 # use a semicolon to suppress the output
1004 1014 In [151]: plt.clf()
1005 1015
1006 1016 @savefig plot_simple.png width=4in
1007 1017 In [151]: plot([1,2,3])
1008 1018
1009 1019 @savefig hist_simple.png width=4in
1010 1020 In [151]: hist(np.random.randn(10000), 100);
1011 1021
1012 1022 """,
1013 1023 r"""
1014 1024 # update the current fig
1015 1025 In [151]: ylabel('number')
1016 1026
1017 1027 In [152]: title('normal distribution')
1018 1028
1019 1029
1020 1030 @savefig hist_with_text.png
1021 1031 In [153]: grid(True)
1022 1032
1023 1033 @doctest float
1024 1034 In [154]: 0.1 + 0.2
1025 1035 Out[154]: 0.3
1026 1036
1027 1037 @doctest float
1028 1038 In [155]: np.arange(16).reshape(4,4)
1029 1039 Out[155]:
1030 1040 array([[ 0, 1, 2, 3],
1031 1041 [ 4, 5, 6, 7],
1032 1042 [ 8, 9, 10, 11],
1033 1043 [12, 13, 14, 15]])
1034 1044
1035 1045 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1036 1046
1037 1047 In [2]: x[0,0] = np.inf
1038 1048
1039 1049 In [3]: x[0,1] = np.nan
1040 1050
1041 1051 @doctest float
1042 1052 In [4]: x
1043 1053 Out[4]:
1044 1054 array([[ inf, nan, 2., 3.],
1045 1055 [ 4., 5., 6., 7.],
1046 1056 [ 8., 9., 10., 11.],
1047 1057 [ 12., 13., 14., 15.]])
1048 1058
1049 1059
1050 1060 """,
1051 1061 ]
1052 1062 # skip local-file depending first example:
1053 1063 examples = examples[1:]
1054 1064
1055 1065 #ipython_directive.DEBUG = True # dbg
1056 1066 #options = dict(suppress=True) # dbg
1057 1067 options = dict()
1058 1068 for example in examples:
1059 1069 content = example.split('\n')
1060 1070 IPythonDirective('debug', arguments=None, options=options,
1061 1071 content=content, lineno=0,
1062 1072 content_offset=None, block_text=None,
1063 1073 state=None, state_machine=None,
1064 1074 )
1065 1075
1066 1076 # Run test suite as a script
1067 1077 if __name__=='__main__':
1068 1078 if not os.path.isdir('_static'):
1069 1079 os.mkdir('_static')
1070 1080 test()
1071 1081 print('All OK? Check figures in _static/')
General Comments 0
You need to be logged in to leave comments. Login now