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