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