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