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