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