##// END OF EJS Templates
Merge pull request #4570 from chebee7i/ipydirective...
Thomas Kluyver -
r13909:95829b6e merge
parent child Browse files
Show More
@@ -0,0 +1,155 b''
1 """
2 Handlers for IPythonDirective's @doctest pseudo-decorator.
3
4 The Sphinx extension that provides support for embedded IPython code provides
5 a pseudo-decorator @doctest, which treats the input/output block as a
6 doctest, raising a RuntimeError during doc generation if the actual output
7 (after running the input) does not match the expected output.
8
9 An example usage is:
10
11 .. code-block:: rst
12
13 .. ipython::
14
15 In [1]: x = 1
16
17 @doctest
18 In [2]: x + 2
19 Out[3]: 3
20
21 One can also provide arguments to the decorator. The first argument should be
22 the name of a custom handler. The specification of any other arguments is
23 determined by the handler. For example,
24
25 .. code-block:: rst
26
27 .. ipython::
28
29 @doctest float
30 In [154]: 0.1 + 0.2
31 Out[154]: 0.3
32
33 allows the actual output ``0.30000000000000004`` to match the expected output
34 due to a comparison with `np.allclose`.
35
36 This module contains handlers for the @doctest pseudo-decorator. Handlers
37 should have the following function signature::
38
39 handler(sphinx_shell, args, input_lines, found, submitted)
40
41 where `sphinx_shell` is the embedded Sphinx shell, `args` contains the list
42 of arguments that follow: '@doctest handler_name', `input_lines` contains
43 a list of the lines relevant to the current doctest, `found` is a string
44 containing the output from the IPython shell, and `submitted` is a string
45 containing the expected output from the IPython shell.
46
47 Handlers must be registered in the `doctests` dict at the end of this module.
48
49 """
50
51 def str_to_array(s):
52 """
53 Simplistic converter of strings from repr to float NumPy arrays.
54
55 If the repr representation has ellipsis in it, then this will fail.
56
57 Parameters
58 ----------
59 s : str
60 The repr version of a NumPy array.
61
62 Examples
63 --------
64 >>> s = "array([ 0.3, inf, nan])"
65 >>> a = str_to_array(s)
66
67 """
68 import numpy as np
69
70 # Need to make sure eval() knows about inf and nan.
71 # This also assumes default printoptions for NumPy.
72 from numpy import inf, nan
73
74 if s.startswith(u'array'):
75 # Remove array( and )
76 s = s[6:-1]
77
78 if s.startswith(u'['):
79 a = np.array(eval(s), dtype=float)
80 else:
81 # Assume its a regular float. Force 1D so we can index into it.
82 a = np.atleast_1d(float(s))
83 return a
84
85 def float_doctest(sphinx_shell, args, input_lines, found, submitted):
86 """
87 Doctest which allow the submitted output to vary slightly from the input.
88
89 Here is how it might appear in an rst file:
90
91 .. code-block:: rst
92
93 .. ipython::
94
95 @doctest float
96 In [1]: 0.1 + 0.2
97 Out[1]: 0.3
98
99 """
100 import numpy as np
101
102 if len(args) == 2:
103 rtol = 1e-05
104 atol = 1e-08
105 else:
106 # Both must be specified if any are specified.
107 try:
108 rtol = float(args[2])
109 atol = float(args[3])
110 except IndexError:
111 e = ("Both `rtol` and `atol` must be specified "
112 "if either are specified: {0}".format(args))
113 raise IndexError(e)
114
115 try:
116 submitted = str_to_array(submitted)
117 found = str_to_array(found)
118 except:
119 # For example, if the array is huge and there are ellipsis in it.
120 error = True
121 else:
122 found_isnan = np.isnan(found)
123 submitted_isnan = np.isnan(submitted)
124 error = not np.allclose(found_isnan, submitted_isnan)
125 error |= not np.allclose(found[~found_isnan],
126 submitted[~submitted_isnan],
127 rtol=rtol, atol=atol)
128
129 TAB = ' ' * 4
130 directive = sphinx_shell.directive
131 if directive is None:
132 source = 'Unavailable'
133 content = 'Unavailable'
134 else:
135 source = directive.state.document.current_source
136 # Add tabs and make into a single string.
137 content = '\n'.join([TAB + line for line in directive.content])
138
139 if error:
140
141 e = ('doctest float comparison failure\n\n'
142 'Document source: {0}\n\n'
143 'Raw content: \n{1}\n\n'
144 'On input line(s):\n{TAB}{2}\n\n'
145 'we found output:\n{TAB}{3}\n\n'
146 'instead of the expected:\n{TAB}{4}\n\n')
147 e = e.format(source, content, '\n'.join(input_lines), repr(found),
148 repr(submitted), TAB=TAB)
149 raise RuntimeError(e)
150
151 # dict of allowable doctest handlers. The key represents the first argument
152 # that must be given to @doctest in order to activate the handler.
153 doctests = {
154 'float': float_doctest,
155 }
@@ -1,5 +1,6 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Sphinx directive to support embedded IPython code.
2 """
3 Sphinx directive to support embedded IPython code.
3
4
4 This directive allows pasting of entire interactive IPython sessions, prompts
5 This directive allows pasting of entire interactive IPython sessions, prompts
5 and all, and their code will actually get re-executed at doc build time, with
6 and all, and their code will actually get re-executed at doc build time, with
@@ -9,11 +10,17 b' like an interactive ipython section.'
9
10
10 To enable this directive, simply list it in your Sphinx ``conf.py`` file
11 To enable this directive, simply list it in your Sphinx ``conf.py`` file
11 (making sure the directory where you placed it is visible to sphinx, as is
12 (making sure the directory where you placed it is visible to sphinx, as is
12 needed for all Sphinx directives).
13 needed for all Sphinx directives). For example, to enable syntax highlighting
14 and the IPython directive::
15
16 extensions = ['IPython.sphinxext.ipython_console_highlighting',
17 'IPython.sphinxext.ipython_directive']
13
18
14 By default this directive assumes that your prompts are unchanged IPython ones,
19 The IPython directive outputs code-blocks with the language 'ipython'. So
15 but this can be customized. The configurable options that can be placed in
20 if you do not have the syntax highlighting extension enabled as well, then
16 conf.py are
21 all rendered code-blocks will be uncolored. By default this directive assumes
22 that your prompts are unchanged IPython ones, but this can be customized.
23 The configurable options that can be placed in conf.py are:
17
24
18 ipython_savefig_dir:
25 ipython_savefig_dir:
19 The directory in which to save the figures. This is relative to the
26 The directory in which to save the figures. This is relative to the
@@ -34,6 +41,47 b' ipython_promptout:'
34 The string to represent the IPython prompt in the generated ReST. The
41 The string to represent the IPython prompt in the generated ReST. The
35 default is 'Out [%d]:'. This expects that the line numbers are used
42 default is 'Out [%d]:'. This expects that the line numbers are used
36 in the prompt.
43 in the prompt.
44 ipython_mplbackend:
45 The string which specifies if the embedded Sphinx shell should import
46 Matplotlib and set the backend. The value specifies a backend that is
47 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
48 executed. If not specified in conf.py, then the default value of 'agg' is
49 used. To use the IPython directive without matplotlib as a dependency, set
50 the value to `None`. It may end up that matplotlib is still imported
51 if the user specifies so in `ipython_execlines` or makes use of the
52 @savefig pseudo decorator.
53 ipython_execlines:
54 A list of strings to be exec'd in the embedded Sphinx shell. Typical
55 usage is to make certain packages always available. Set this to an empty
56 list if you wish to have no imports always available. If specified in
57 conf.py as `None`, then it has the effect of making no imports available.
58 If omitted from conf.py altogether, then the default value of
59 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
60 ipython_holdcount
61 When the @suppress pseudo-decorator is used, the execution count can be
62 incremented or not. The default behavior is to hold the execution count,
63 corresponding to a value of `True`. Set this to `False` to increment
64 the execution count after each suppressed command.
65
66 As an example, to use the IPython directive when `matplotlib` is not available,
67 one sets the backend to `None`::
68
69 ipython_mplbackend = None
70
71 An example usage of the directive is:
72
73 .. code-block:: rst
74
75 .. ipython::
76
77 In [1]: x = 1
78
79 In [2]: y = x**2
80
81 In [3]: print(y)
82
83 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
84 documentation.
37
85
38 ToDo
86 ToDo
39 ----
87 ----
@@ -70,14 +118,11 b' except ImportError:'
70 from md5 import md5
118 from md5 import md5
71
119
72 # Third-party
120 # Third-party
73 import matplotlib
74 import sphinx
121 import sphinx
75 from docutils.parsers.rst import directives
122 from docutils.parsers.rst import directives
76 from docutils import nodes
123 from docutils import nodes
77 from sphinx.util.compat import Directive
124 from sphinx.util.compat import Directive
78
125
79 matplotlib.use('Agg')
80
81 # Our own
126 # Our own
82 from IPython import Config, InteractiveShell
127 from IPython import Config, InteractiveShell
83 from IPython.core.profiledir import ProfileDir
128 from IPython.core.profiledir import ProfileDir
@@ -98,6 +143,7 b' COMMENT, INPUT, OUTPUT = range(3)'
98 #-----------------------------------------------------------------------------
143 #-----------------------------------------------------------------------------
99 # Functions and class declarations
144 # Functions and class declarations
100 #-----------------------------------------------------------------------------
145 #-----------------------------------------------------------------------------
146
101 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
147 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
102 """
148 """
103 part is a string of ipython text, comprised of at most one
149 part is a string of ipython text, comprised of at most one
@@ -116,10 +162,9 b' def block_parser(part, rgxin, rgxout, fmtin, fmtout):'
116 INPUT_LINE: the input as string (possibly multi-line)
162 INPUT_LINE: the input as string (possibly multi-line)
117 REST : any stdout generated by the input line (not OUTPUT)
163 REST : any stdout generated by the input line (not OUTPUT)
118
164
119
120 OUTPUT: the output string, possibly multi-line
165 OUTPUT: the output string, possibly multi-line
121 """
122
166
167 """
123 block = []
168 block = []
124 lines = part.split('\n')
169 lines = part.split('\n')
125 N = len(lines)
170 N = len(lines)
@@ -192,20 +237,19 b' def block_parser(part, rgxin, rgxout, fmtin, fmtout):'
192
237
193 return block
238 return block
194
239
240
195 class EmbeddedSphinxShell(object):
241 class EmbeddedSphinxShell(object):
196 """An embedded IPython instance to run inside Sphinx"""
242 """An embedded IPython instance to run inside Sphinx"""
197
243
198 def __init__(self):
244 def __init__(self, exec_lines=None):
199
245
200 self.cout = StringIO()
246 self.cout = StringIO()
201
247
248 if exec_lines is None:
249 exec_lines = []
202
250
203 # Create config object for IPython
251 # Create config object for IPython
204 config = Config()
252 config = Config()
205 config.Global.display_banner = False
206 config.Global.exec_lines = ['import numpy as np',
207 'from pylab import *'
208 ]
209 config.InteractiveShell.autocall = False
253 config.InteractiveShell.autocall = False
210 config.InteractiveShell.autoindent = False
254 config.InteractiveShell.autoindent = False
211 config.InteractiveShell.colors = 'NoColor'
255 config.InteractiveShell.colors = 'NoColor'
@@ -216,9 +260,11 b' class EmbeddedSphinxShell(object):'
216 pdir = os.path.join(tmp_profile_dir,profname)
260 pdir = os.path.join(tmp_profile_dir,profname)
217 profile = ProfileDir.create_profile_dir(pdir)
261 profile = ProfileDir.create_profile_dir(pdir)
218
262
219 # Create and initialize ipython, but don't start its mainloop
263 # Create and initialize global ipython, but don't start its mainloop.
264 # This will persist across different EmbededSphinxShell instances.
220 IP = InteractiveShell.instance(config=config, profile_dir=profile)
265 IP = InteractiveShell.instance(config=config, profile_dir=profile)
221 # io.stdout redirect must be done *after* instantiating InteractiveShell
266
267 # io.stdout redirect must be done after instantiating InteractiveShell
222 io.stdout = self.cout
268 io.stdout = self.cout
223 io.stderr = self.cout
269 io.stderr = self.cout
224
270
@@ -239,17 +285,24 b' class EmbeddedSphinxShell(object):'
239 self.is_doctest = False
285 self.is_doctest = False
240 self.is_suppress = False
286 self.is_suppress = False
241
287
288 # Optionally, provide more detailed information to shell.
289 self.directive = None
290
242 # on the first call to the savefig decorator, we'll import
291 # on the first call to the savefig decorator, we'll import
243 # pyplot as plt so we can make a call to the plt.gcf().savefig
292 # pyplot as plt so we can make a call to the plt.gcf().savefig
244 self._pyplot_imported = False
293 self._pyplot_imported = False
245
294
295 # Prepopulate the namespace.
296 for line in exec_lines:
297 self.process_input_line(line, store_history=False)
298
246 def clear_cout(self):
299 def clear_cout(self):
247 self.cout.seek(0)
300 self.cout.seek(0)
248 self.cout.truncate(0)
301 self.cout.truncate(0)
249
302
250 def process_input_line(self, line, store_history=True):
303 def process_input_line(self, line, store_history=True):
251 """process the input, capturing stdout"""
304 """process the input, capturing stdout"""
252 #print "input='%s'"%self.input
305
253 stdout = sys.stdout
306 stdout = sys.stdout
254 splitter = self.IP.input_splitter
307 splitter = self.IP.input_splitter
255 try:
308 try:
@@ -291,16 +344,19 b' class EmbeddedSphinxShell(object):'
291 image_directive = '\n'.join(imagerows)
344 image_directive = '\n'.join(imagerows)
292 return image_file, image_directive
345 return image_file, image_directive
293
346
294
295 # Callbacks for each type of token
347 # Callbacks for each type of token
296 def process_input(self, data, input_prompt, lineno):
348 def process_input(self, data, input_prompt, lineno):
297 """Process data block for INPUT token."""
349 """
350 Process data block for INPUT token.
351
352 """
298 decorator, input, rest = data
353 decorator, input, rest = data
299 image_file = None
354 image_file = None
300 image_directive = None
355 image_directive = None
301 #print 'INPUT:', data # dbg
356
302 is_verbatim = decorator=='@verbatim' or self.is_verbatim
357 is_verbatim = decorator=='@verbatim' or self.is_verbatim
303 is_doctest = decorator=='@doctest' or self.is_doctest
358 is_doctest = (decorator is not None and \
359 decorator.startswith('@doctest')) or self.is_doctest
304 is_suppress = decorator=='@suppress' or self.is_suppress
360 is_suppress = decorator=='@suppress' or self.is_suppress
305 is_savefig = decorator is not None and \
361 is_savefig = decorator is not None and \
306 decorator.startswith('@savefig')
362 decorator.startswith('@savefig')
@@ -312,7 +368,6 b' class EmbeddedSphinxShell(object):'
312 # so splitter buffer gets reset
368 # so splitter buffer gets reset
313
369
314 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
370 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
315 Nc = len(continuation)
316
371
317 if is_savefig:
372 if is_savefig:
318 image_file, image_directive = self.process_image(decorator)
373 image_file, image_directive = self.process_image(decorator)
@@ -320,23 +375,29 b' class EmbeddedSphinxShell(object):'
320 ret = []
375 ret = []
321 is_semicolon = False
376 is_semicolon = False
322
377
378 # Hold the execution count, if requested to do so.
379 if is_suppress and self.hold_count:
380 store_history = False
381 else:
382 store_history = True
383
323 for i, line in enumerate(input_lines):
384 for i, line in enumerate(input_lines):
324 if line.endswith(';'):
385 if line.endswith(';'):
325 is_semicolon = True
386 is_semicolon = True
326
387
327 if i==0:
388 if i == 0:
328 # process the first input line
389 # process the first input line
329 if is_verbatim:
390 if is_verbatim:
330 self.process_input_line('')
391 self.process_input_line('')
331 self.IP.execution_count += 1 # increment it anyway
392 self.IP.execution_count += 1 # increment it anyway
332 else:
393 else:
333 # only submit the line in non-verbatim mode
394 # only submit the line in non-verbatim mode
334 self.process_input_line(line, store_history=True)
395 self.process_input_line(line, store_history=store_history)
335 formatted_line = '%s %s'%(input_prompt, line)
396 formatted_line = '%s %s'%(input_prompt, line)
336 else:
397 else:
337 # process a continuation line
398 # process a continuation line
338 if not is_verbatim:
399 if not is_verbatim:
339 self.process_input_line(line, store_history=True)
400 self.process_input_line(line, store_history=store_history)
340
401
341 formatted_line = '%s %s'%(continuation, line)
402 formatted_line = '%s %s'%(continuation, line)
342
403
@@ -357,38 +418,61 b' class EmbeddedSphinxShell(object):'
357 ret.append('')
418 ret.append('')
358
419
359 self.cout.truncate(0)
420 self.cout.truncate(0)
360 return (ret, input_lines, output, is_doctest, image_file,
421 return (ret, input_lines, output, is_doctest, decorator, image_file,
361 image_directive)
422 image_directive)
362 #print 'OUTPUT', output # dbg
423
363
424
364 def process_output(self, data, output_prompt,
425 def process_output(self, data, output_prompt,
365 input_lines, output, is_doctest, image_file):
426 input_lines, output, is_doctest, decorator, image_file):
366 """Process data block for OUTPUT token."""
427 """
367 if is_doctest:
428 Process data block for OUTPUT token.
368 submitted = data.strip()
369 found = output
370 if found is not None:
371 found = found.strip()
372
429
373 # XXX - fperez: in 0.11, 'output' never comes with the prompt
430 """
374 # in it, just the actual output text. So I think all this code
431 TAB = ' ' * 4
375 # can be nuked...
376
432
377 # the above comment does not appear to be accurate... (minrk)
433 if is_doctest and output is not None:
378
434
379 ind = found.find(output_prompt)
435 found = output
380 if ind<0:
436 found = found.strip()
381 e='output prompt="%s" does not match out line=%s' % \
437 submitted = data.strip()
382 (output_prompt, found)
383 raise RuntimeError(e)
384 found = found[len(output_prompt):].strip()
385
438
386 if found!=submitted:
439 if self.directive is None:
387 e = ('doctest failure for input_lines="%s" with '
440 source = 'Unavailable'
388 'found_output="%s" and submitted output="%s"' %
441 content = 'Unavailable'
389 (input_lines, found, submitted) )
442 else:
443 source = self.directive.state.document.current_source
444 content = self.directive.content
445 # Add tabs and join into a single string.
446 content = '\n'.join([TAB + line for line in content])
447
448 # Make sure the output contains the output prompt.
449 ind = found.find(output_prompt)
450 if ind < 0:
451 e = ('output does not contain output prompt\n\n'
452 'Document source: {0}\n\n'
453 'Raw content: \n{1}\n\n'
454 'Input line(s):\n{TAB}{2}\n\n'
455 'Output line(s):\n{TAB}{3}\n\n')
456 e = e.format(source, content, '\n'.join(input_lines),
457 repr(found), TAB=TAB)
458 raise RuntimeError(e)
459 found = found[len(output_prompt):].strip()
460
461 # Handle the actual doctest comparison.
462 if decorator.strip() == '@doctest':
463 # Standard doctest
464 if found != submitted:
465 e = ('doctest failure\n\n'
466 'Document source: {0}\n\n'
467 'Raw content: \n{1}\n\n'
468 'On input line(s):\n{TAB}{2}\n\n'
469 'we found output:\n{TAB}{3}\n\n'
470 'instead of the expected:\n{TAB}{4}\n\n')
471 e = e.format(source, content, '\n'.join(input_lines),
472 repr(found), repr(submitted), TAB=TAB)
390 raise RuntimeError(e)
473 raise RuntimeError(e)
391 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
474 else:
475 self.custom_doctest(decorator, input_lines, found, submitted)
392
476
393 def process_comment(self, data):
477 def process_comment(self, data):
394 """Process data fPblock for COMMENT token."""
478 """Process data fPblock for COMMENT token."""
@@ -409,7 +493,6 b' class EmbeddedSphinxShell(object):'
409 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
493 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
410 self.clear_cout()
494 self.clear_cout()
411
495
412
413 def process_block(self, block):
496 def process_block(self, block):
414 """
497 """
415 process block from the block_parser and return a list of processed lines
498 process block from the block_parser and return a list of processed lines
@@ -419,23 +502,23 b' class EmbeddedSphinxShell(object):'
419 input_lines = None
502 input_lines = None
420 lineno = self.IP.execution_count
503 lineno = self.IP.execution_count
421
504
422 input_prompt = self.promptin%lineno
505 input_prompt = self.promptin % lineno
423 output_prompt = self.promptout%lineno
506 output_prompt = self.promptout % lineno
424 image_file = None
507 image_file = None
425 image_directive = None
508 image_directive = None
426
509
427 for token, data in block:
510 for token, data in block:
428 if token==COMMENT:
511 if token == COMMENT:
429 out_data = self.process_comment(data)
512 out_data = self.process_comment(data)
430 elif token==INPUT:
513 elif token == INPUT:
431 (out_data, input_lines, output, is_doctest, image_file,
514 (out_data, input_lines, output, is_doctest, decorator,
432 image_directive) = \
515 image_file, image_directive) = \
433 self.process_input(data, input_prompt, lineno)
516 self.process_input(data, input_prompt, lineno)
434 elif token==OUTPUT:
517 elif token == OUTPUT:
435 out_data = \
518 out_data = \
436 self.process_output(data, output_prompt,
519 self.process_output(data, output_prompt,
437 input_lines, output, is_doctest,
520 input_lines, output, is_doctest,
438 image_file)
521 decorator, image_file)
439 if out_data:
522 if out_data:
440 ret.extend(out_data)
523 ret.extend(out_data)
441
524
@@ -446,14 +529,34 b' class EmbeddedSphinxShell(object):'
446 return ret, image_directive
529 return ret, image_directive
447
530
448 def ensure_pyplot(self):
531 def ensure_pyplot(self):
449 if self._pyplot_imported:
532 """
450 return
533 Ensures that pyplot has been imported into the embedded IPython shell.
451 self.process_input_line('import matplotlib.pyplot as plt',
534
452 store_history=False)
535 Also, makes sure to set the backend appropriately if not set already.
536
537 """
538 # We are here if the @figure pseudo decorator was used. Thus, it's
539 # possible that we could be here even if python_mplbackend were set to
540 # `None`. That's also strange and perhaps worthy of raising an
541 # exception, but for now, we just set the backend to 'agg'.
542
543 if not self._pyplot_imported:
544 if 'matplotlib.backends' not in sys.modules:
545 # Then ipython_matplotlib was set to None but there was a
546 # call to the @figure decorator (and ipython_execlines did
547 # not set a backend).
548 #raise Exception("No backend was set, but @figure was used!")
549 import matplotlib
550 matplotlib.use('agg')
551
552 # Always import pyplot into embedded shell.
553 self.process_input_line('import matplotlib.pyplot as plt',
554 store_history=False)
555 self._pyplot_imported = True
453
556
454 def process_pure_python(self, content):
557 def process_pure_python(self, content):
455 """
558 """
456 content is a list of strings. it is unedited directive conent
559 content is a list of strings. it is unedited directive content
457
560
458 This runs it line by line in the InteractiveShell, prepends
561 This runs it line by line in the InteractiveShell, prepends
459 prompts as needed capturing stderr and stdout, then returns
562 prompts as needed capturing stderr and stdout, then returns
@@ -529,6 +632,22 b' class EmbeddedSphinxShell(object):'
529
632
530 return output
633 return output
531
634
635 def custom_doctest(self, decorator, input_lines, found, submitted):
636 """
637 Perform a specialized doctest.
638
639 """
640 from .custom_doctests import doctests
641
642 args = decorator.split()
643 doctest_type = args[1]
644 if doctest_type in doctests:
645 doctests[doctest_type](self, args, input_lines, found, submitted)
646 else:
647 e = "Invalid option to @doctest: {0}".format(doctest_type)
648 raise Exception(e)
649
650
532 class IPythonDirective(Directive):
651 class IPythonDirective(Directive):
533
652
534 has_content = True
653 has_content = True
@@ -560,30 +679,48 b' class IPythonDirective(Directive):'
560 savefig_dir = os.path.join(confdir, savefig_dir)
679 savefig_dir = os.path.join(confdir, savefig_dir)
561
680
562 # get regex and prompt stuff
681 # get regex and prompt stuff
563 rgxin = config.ipython_rgxin
682 rgxin = config.ipython_rgxin
564 rgxout = config.ipython_rgxout
683 rgxout = config.ipython_rgxout
565 promptin = config.ipython_promptin
684 promptin = config.ipython_promptin
566 promptout = config.ipython_promptout
685 promptout = config.ipython_promptout
686 mplbackend = config.ipython_mplbackend
687 exec_lines = config.ipython_execlines
688 hold_count = config.ipython_holdcount
567
689
568 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
690 return (savefig_dir, source_dir, rgxin, rgxout,
691 promptin, promptout, mplbackend, exec_lines, hold_count)
569
692
570 def setup(self):
693 def setup(self):
694 # Get configuration values.
695 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
696 mplbackend, exec_lines, hold_count) = self.get_config_options()
697
571 if self.shell is None:
698 if self.shell is None:
572 self.shell = EmbeddedSphinxShell()
699 # We will be here many times. However, when the
573 # reset the execution count if we haven't processed this doc
700 # EmbeddedSphinxShell is created, its interactive shell member
574 #NOTE: this may be borked if there are multiple seen_doc tmp files
701 # is the same for each instance.
575 #check time stamp?
576
702
577 if not self.state.document.current_source in self.seen_docs:
703 if mplbackend:
578 self.shell.IP.history_manager.reset()
704 import matplotlib
579 self.shell.IP.execution_count = 1
705 # Repeated calls to use() will not hurt us since `mplbackend`
580 self.seen_docs.add(self.state.document.current_source)
706 # is the same each time.
707 matplotlib.use(mplbackend)
581
708
709 # Must be called after (potentially) importing matplotlib and
710 # setting its backend since exec_lines might import pylab.
711 self.shell = EmbeddedSphinxShell(exec_lines)
582
712
713 # Store IPython directive to enable better error messages
714 self.shell.directive = self
583
715
584 # get config values
716 # reset the execution count if we haven't processed this doc
585 (savefig_dir, source_dir, rgxin,
717 #NOTE: this may be borked if there are multiple seen_doc tmp files
586 rgxout, promptin, promptout) = self.get_config_options()
718 #check time stamp?
719 if not self.state.document.current_source in self.seen_docs:
720 self.shell.IP.history_manager.reset()
721 self.shell.IP.execution_count = 1
722 self.shell.IP.prompt_manager.width = 0
723 self.seen_docs.add(self.state.document.current_source)
587
724
588 # and attach to shell so we don't have to pass them around
725 # and attach to shell so we don't have to pass them around
589 self.shell.rgxin = rgxin
726 self.shell.rgxin = rgxin
@@ -592,16 +729,15 b' class IPythonDirective(Directive):'
592 self.shell.promptout = promptout
729 self.shell.promptout = promptout
593 self.shell.savefig_dir = savefig_dir
730 self.shell.savefig_dir = savefig_dir
594 self.shell.source_dir = source_dir
731 self.shell.source_dir = source_dir
732 self.shell.hold_count = hold_count
595
733
596 # setup bookmark for saving figures directory
734 # setup bookmark for saving figures directory
597
598 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
735 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
599 store_history=False)
736 store_history=False)
600 self.shell.clear_cout()
737 self.shell.clear_cout()
601
738
602 return rgxin, rgxout, promptin, promptout
739 return rgxin, rgxout, promptin, promptout
603
740
604
605 def teardown(self):
741 def teardown(self):
606 # delete last bookmark
742 # delete last bookmark
607 self.shell.process_input_line('bookmark -d ipy_savedir',
743 self.shell.process_input_line('bookmark -d ipy_savedir',
@@ -620,7 +756,6 b' class IPythonDirective(Directive):'
620 self.shell.is_doctest = 'doctest' in options
756 self.shell.is_doctest = 'doctest' in options
621 self.shell.is_verbatim = 'verbatim' in options
757 self.shell.is_verbatim = 'verbatim' in options
622
758
623
624 # handle pure python code
759 # handle pure python code
625 if 'python' in self.arguments:
760 if 'python' in self.arguments:
626 content = self.content
761 content = self.content
@@ -628,13 +763,11 b' class IPythonDirective(Directive):'
628
763
629 parts = '\n'.join(self.content).split('\n\n')
764 parts = '\n'.join(self.content).split('\n\n')
630
765
631 lines = ['.. code-block:: ipython','']
766 lines = ['.. code-block:: ipython', '']
632 figures = []
767 figures = []
633
768
634 for part in parts:
769 for part in parts:
635
636 block = block_parser(part, rgxin, rgxout, promptin, promptout)
770 block = block_parser(part, rgxin, rgxout, promptin, promptout)
637
638 if len(block):
771 if len(block):
639 rows, figure = self.shell.process_block(block)
772 rows, figure = self.shell.process_block(block)
640 for row in rows:
773 for row in rows:
@@ -643,46 +776,51 b' class IPythonDirective(Directive):'
643 if figure is not None:
776 if figure is not None:
644 figures.append(figure)
777 figures.append(figure)
645
778
646 #text = '\n'.join(lines)
647 #figs = '\n'.join(figures)
648
649 for figure in figures:
779 for figure in figures:
650 lines.append('')
780 lines.append('')
651 lines.extend(figure.split('\n'))
781 lines.extend(figure.split('\n'))
652 lines.append('')
782 lines.append('')
653
783
654 #print lines
655 if len(lines)>2:
784 if len(lines)>2:
656 if debug:
785 if debug:
657 print('\n'.join(lines))
786 print('\n'.join(lines))
658 else: #NOTE: this raises some errors, what's it for?
787 else:
659 #print 'INSERTING %d lines'%len(lines)
788 # This has to do with input, not output. But if we comment
789 # these lines out, then no IPython code will appear in the
790 # final output.
660 self.state_machine.insert_input(
791 self.state_machine.insert_input(
661 lines, self.state_machine.input_lines.source(0))
792 lines, self.state_machine.input_lines.source(0))
662
793
663 text = '\n'.join(lines)
664 txtnode = nodes.literal_block(text, text)
665 txtnode['language'] = 'ipython'
666 #imgnode = nodes.image(figs)
667
668 # cleanup
794 # cleanup
669 self.teardown()
795 self.teardown()
670
796
671 return []#, imgnode]
797 return []
672
798
673 # Enable as a proper Sphinx directive
799 # Enable as a proper Sphinx directive
674 def setup(app):
800 def setup(app):
675 setup.app = app
801 setup.app = app
676
802
677 app.add_directive('ipython', IPythonDirective)
803 app.add_directive('ipython', IPythonDirective)
678 app.add_config_value('ipython_savefig_dir', None, True)
804 app.add_config_value('ipython_savefig_dir', None, 'env')
679 app.add_config_value('ipython_rgxin',
805 app.add_config_value('ipython_rgxin',
680 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
806 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
681 app.add_config_value('ipython_rgxout',
807 app.add_config_value('ipython_rgxout',
682 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
808 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
683 app.add_config_value('ipython_promptin', 'In [%d]:', True)
809 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
684 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
810 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
685
811
812 # We could just let matplotlib pick whatever is specified as the default
813 # backend in the matplotlibrc file, but this would cause issues if the
814 # backend didn't work in headless environments. For this reason, 'agg'
815 # is a good default backend choice.
816 app.add_config_value('ipython_mplbackend', 'agg', 'env')
817
818 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
819 # __init__ method will treat it as [].
820 execlines = ['import numpy as np', 'import matplotlib.pyplot as plt']
821 app.add_config_value('ipython_execlines', execlines, 'env')
822
823 app.add_config_value('ipython_holdcount', True, 'env')
686
824
687 # Simple smoke test, needs to be converted to a proper automatic test.
825 # Simple smoke test, needs to be converted to a proper automatic test.
688 def test():
826 def test():
@@ -810,6 +948,33 b" In [152]: title('normal distribution')"
810 @savefig hist_with_text.png
948 @savefig hist_with_text.png
811 In [153]: grid(True)
949 In [153]: grid(True)
812
950
951 @doctest float
952 In [154]: 0.1 + 0.2
953 Out[154]: 0.3
954
955 @doctest float
956 In [155]: np.arange(16).reshape(4,4)
957 Out[155]:
958 array([[ 0, 1, 2, 3],
959 [ 4, 5, 6, 7],
960 [ 8, 9, 10, 11],
961 [12, 13, 14, 15]])
962
963 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
964
965 In [2]: x[0,0] = np.inf
966
967 In [3]: x[0,1] = np.nan
968
969 @doctest float
970 In [4]: x
971 Out[4]:
972 array([[ inf, nan, 2., 3.],
973 [ 4., 5., 6., 7.],
974 [ 8., 9., 10., 11.],
975 [ 12., 13., 14., 15.]])
976
977
813 """,
978 """,
814 ]
979 ]
815 # skip local-file depending first example:
980 # skip local-file depending first example:
General Comments 0
You need to be logged in to leave comments. Login now