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