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