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