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