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