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