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