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