##// END OF EJS Templates
Fixed a problem with evaluating indented lines in pure python code....
Ahmet Bakan -
Show More
@@ -1,822 +1,828 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
500 # if the next line is indented, it should be part of multiline
501 if len(content) > lineno + 1:
502 nextline = content[lineno + 1]
503 if len(nextline) - len(nextline.lstrip()) > 3:
504 continue
499 try:
505 try:
500 mod = ast.parse(
506 mod = ast.parse(
501 '\n'.join(content[multiline_start:lineno+1]))
507 '\n'.join(content[multiline_start:lineno+1]))
502 if isinstance(mod.body[0], ast.FunctionDef):
508 if isinstance(mod.body[0], ast.FunctionDef):
503 # check to see if we have the whole function
509 # check to see if we have the whole function
504 for element in mod.body[0].body:
510 for element in mod.body[0].body:
505 if isinstance(element, ast.Return):
511 if isinstance(element, ast.Return):
506 multiline = False
512 multiline = False
507 else:
513 else:
508 output.append(u'')
514 output.append(u'')
509 multiline = False
515 multiline = False
510 except Exception:
516 except Exception:
511 pass
517 pass
512
518
513 if savefig: # clear figure if plotted
519 if savefig: # clear figure if plotted
514 self.ensure_pyplot()
520 self.ensure_pyplot()
515 self.process_input_line('plt.clf()', store_history=False)
521 self.process_input_line('plt.clf()', store_history=False)
516 self.clear_cout()
522 self.clear_cout()
517 savefig = False
523 savefig = False
518
524
519 return output
525 return output
520
526
521 class IpythonDirective(Directive):
527 class IpythonDirective(Directive):
522
528
523 has_content = True
529 has_content = True
524 required_arguments = 0
530 required_arguments = 0
525 optional_arguments = 4 # python, suppress, verbatim, doctest
531 optional_arguments = 4 # python, suppress, verbatim, doctest
526 final_argumuent_whitespace = True
532 final_argumuent_whitespace = True
527 option_spec = { 'python': directives.unchanged,
533 option_spec = { 'python': directives.unchanged,
528 'suppress' : directives.flag,
534 'suppress' : directives.flag,
529 'verbatim' : directives.flag,
535 'verbatim' : directives.flag,
530 'doctest' : directives.flag,
536 'doctest' : directives.flag,
531 }
537 }
532
538
533 shell = EmbeddedSphinxShell()
539 shell = EmbeddedSphinxShell()
534
540
535 seen_docs = set()
541 seen_docs = set()
536
542
537 def get_config_options(self):
543 def get_config_options(self):
538 # contains sphinx configuration variables
544 # contains sphinx configuration variables
539 config = self.state.document.settings.env.config
545 config = self.state.document.settings.env.config
540
546
541 # get config variables to set figure output directory
547 # get config variables to set figure output directory
542 confdir = self.state.document.settings.env.app.confdir
548 confdir = self.state.document.settings.env.app.confdir
543 savefig_dir = config.ipython_savefig_dir
549 savefig_dir = config.ipython_savefig_dir
544 source_dir = os.path.dirname(self.state.document.current_source)
550 source_dir = os.path.dirname(self.state.document.current_source)
545 if savefig_dir is None:
551 if savefig_dir is None:
546 savefig_dir = config.html_static_path
552 savefig_dir = config.html_static_path
547 if isinstance(savefig_dir, list):
553 if isinstance(savefig_dir, list):
548 savefig_dir = savefig_dir[0] # safe to assume only one path?
554 savefig_dir = savefig_dir[0] # safe to assume only one path?
549 savefig_dir = os.path.join(confdir, savefig_dir)
555 savefig_dir = os.path.join(confdir, savefig_dir)
550
556
551 # get regex and prompt stuff
557 # get regex and prompt stuff
552 rgxin = config.ipython_rgxin
558 rgxin = config.ipython_rgxin
553 rgxout = config.ipython_rgxout
559 rgxout = config.ipython_rgxout
554 promptin = config.ipython_promptin
560 promptin = config.ipython_promptin
555 promptout = config.ipython_promptout
561 promptout = config.ipython_promptout
556
562
557 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
563 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
558
564
559 def setup(self):
565 def setup(self):
560 # reset the execution count if we haven't processed this doc
566 # reset the execution count if we haven't processed this doc
561 #NOTE: this may be borked if there are multiple seen_doc tmp files
567 #NOTE: this may be borked if there are multiple seen_doc tmp files
562 #check time stamp?
568 #check time stamp?
563
569
564
570
565 if not self.state.document.current_source in self.seen_docs:
571 if not self.state.document.current_source in self.seen_docs:
566 self.shell.IP.history_manager.reset()
572 self.shell.IP.history_manager.reset()
567 self.shell.IP.execution_count = 1
573 self.shell.IP.execution_count = 1
568 self.seen_docs.add(self.state.document.current_source)
574 self.seen_docs.add(self.state.document.current_source)
569
575
570
576
571
577
572 # get config values
578 # get config values
573 (savefig_dir, source_dir, rgxin,
579 (savefig_dir, source_dir, rgxin,
574 rgxout, promptin, promptout) = self.get_config_options()
580 rgxout, promptin, promptout) = self.get_config_options()
575
581
576 # and attach to shell so we don't have to pass them around
582 # and attach to shell so we don't have to pass them around
577 self.shell.rgxin = rgxin
583 self.shell.rgxin = rgxin
578 self.shell.rgxout = rgxout
584 self.shell.rgxout = rgxout
579 self.shell.promptin = promptin
585 self.shell.promptin = promptin
580 self.shell.promptout = promptout
586 self.shell.promptout = promptout
581 self.shell.savefig_dir = savefig_dir
587 self.shell.savefig_dir = savefig_dir
582 self.shell.source_dir = source_dir
588 self.shell.source_dir = source_dir
583
589
584 # setup bookmark for saving figures directory
590 # setup bookmark for saving figures directory
585
591
586 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
592 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
587 store_history=False)
593 store_history=False)
588 self.shell.clear_cout()
594 self.shell.clear_cout()
589
595
590 return rgxin, rgxout, promptin, promptout
596 return rgxin, rgxout, promptin, promptout
591
597
592
598
593 def teardown(self):
599 def teardown(self):
594 # delete last bookmark
600 # delete last bookmark
595 self.shell.process_input_line('bookmark -d ipy_savedir',
601 self.shell.process_input_line('bookmark -d ipy_savedir',
596 store_history=False)
602 store_history=False)
597 self.shell.clear_cout()
603 self.shell.clear_cout()
598
604
599 def run(self):
605 def run(self):
600 debug = False
606 debug = False
601
607
602 #TODO, any reason block_parser can't be a method of embeddable shell
608 #TODO, any reason block_parser can't be a method of embeddable shell
603 # then we wouldn't have to carry these around
609 # then we wouldn't have to carry these around
604 rgxin, rgxout, promptin, promptout = self.setup()
610 rgxin, rgxout, promptin, promptout = self.setup()
605
611
606 options = self.options
612 options = self.options
607 self.shell.is_suppress = 'suppress' in options
613 self.shell.is_suppress = 'suppress' in options
608 self.shell.is_doctest = 'doctest' in options
614 self.shell.is_doctest = 'doctest' in options
609 self.shell.is_verbatim = 'verbatim' in options
615 self.shell.is_verbatim = 'verbatim' in options
610
616
611
617
612 # handle pure python code
618 # handle pure python code
613 if 'python' in self.arguments:
619 if 'python' in self.arguments:
614 content = self.content
620 content = self.content
615 self.content = self.shell.process_pure_python(content)
621 self.content = self.shell.process_pure_python(content)
616
622
617 parts = '\n'.join(self.content).split('\n\n')
623 parts = '\n'.join(self.content).split('\n\n')
618
624
619 lines = ['.. code-block:: ipython','']
625 lines = ['.. code-block:: ipython','']
620 figures = []
626 figures = []
621
627
622 for part in parts:
628 for part in parts:
623
629
624 block = block_parser(part, rgxin, rgxout, promptin, promptout)
630 block = block_parser(part, rgxin, rgxout, promptin, promptout)
625
631
626 if len(block):
632 if len(block):
627 rows, figure = self.shell.process_block(block)
633 rows, figure = self.shell.process_block(block)
628 for row in rows:
634 for row in rows:
629 lines.extend([' %s'%line for line in row.split('\n')])
635 lines.extend([' %s'%line for line in row.split('\n')])
630
636
631 if figure is not None:
637 if figure is not None:
632 figures.append(figure)
638 figures.append(figure)
633
639
634 #text = '\n'.join(lines)
640 #text = '\n'.join(lines)
635 #figs = '\n'.join(figures)
641 #figs = '\n'.join(figures)
636
642
637 for figure in figures:
643 for figure in figures:
638 lines.append('')
644 lines.append('')
639 lines.extend(figure.split('\n'))
645 lines.extend(figure.split('\n'))
640 lines.append('')
646 lines.append('')
641
647
642 #print lines
648 #print lines
643 if len(lines)>2:
649 if len(lines)>2:
644 if debug:
650 if debug:
645 print '\n'.join(lines)
651 print '\n'.join(lines)
646 else: #NOTE: this raises some errors, what's it for?
652 else: #NOTE: this raises some errors, what's it for?
647 #print 'INSERTING %d lines'%len(lines)
653 #print 'INSERTING %d lines'%len(lines)
648 self.state_machine.insert_input(
654 self.state_machine.insert_input(
649 lines, self.state_machine.input_lines.source(0))
655 lines, self.state_machine.input_lines.source(0))
650
656
651 text = '\n'.join(lines)
657 text = '\n'.join(lines)
652 txtnode = nodes.literal_block(text, text)
658 txtnode = nodes.literal_block(text, text)
653 txtnode['language'] = 'ipython'
659 txtnode['language'] = 'ipython'
654 #imgnode = nodes.image(figs)
660 #imgnode = nodes.image(figs)
655
661
656 # cleanup
662 # cleanup
657 self.teardown()
663 self.teardown()
658
664
659 return []#, imgnode]
665 return []#, imgnode]
660
666
661 # Enable as a proper Sphinx directive
667 # Enable as a proper Sphinx directive
662 def setup(app):
668 def setup(app):
663 setup.app = app
669 setup.app = app
664
670
665 app.add_directive('ipython', IpythonDirective)
671 app.add_directive('ipython', IpythonDirective)
666 app.add_config_value('ipython_savefig_dir', None, True)
672 app.add_config_value('ipython_savefig_dir', None, True)
667 app.add_config_value('ipython_rgxin',
673 app.add_config_value('ipython_rgxin',
668 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
674 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
669 app.add_config_value('ipython_rgxout',
675 app.add_config_value('ipython_rgxout',
670 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
676 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
671 app.add_config_value('ipython_promptin', 'In [%d]:', True)
677 app.add_config_value('ipython_promptin', 'In [%d]:', True)
672 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
678 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
673
679
674
680
675 # Simple smoke test, needs to be converted to a proper automatic test.
681 # Simple smoke test, needs to be converted to a proper automatic test.
676 def test():
682 def test():
677
683
678 examples = [
684 examples = [
679 r"""
685 r"""
680 In [9]: pwd
686 In [9]: pwd
681 Out[9]: '/home/jdhunter/py4science/book'
687 Out[9]: '/home/jdhunter/py4science/book'
682
688
683 In [10]: cd bookdata/
689 In [10]: cd bookdata/
684 /home/jdhunter/py4science/book/bookdata
690 /home/jdhunter/py4science/book/bookdata
685
691
686 In [2]: from pylab import *
692 In [2]: from pylab import *
687
693
688 In [2]: ion()
694 In [2]: ion()
689
695
690 In [3]: im = imread('stinkbug.png')
696 In [3]: im = imread('stinkbug.png')
691
697
692 @savefig mystinkbug.png width=4in
698 @savefig mystinkbug.png width=4in
693 In [4]: imshow(im)
699 In [4]: imshow(im)
694 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
700 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
695
701
696 """,
702 """,
697 r"""
703 r"""
698
704
699 In [1]: x = 'hello world'
705 In [1]: x = 'hello world'
700
706
701 # string methods can be
707 # string methods can be
702 # used to alter the string
708 # used to alter the string
703 @doctest
709 @doctest
704 In [2]: x.upper()
710 In [2]: x.upper()
705 Out[2]: 'HELLO WORLD'
711 Out[2]: 'HELLO WORLD'
706
712
707 @verbatim
713 @verbatim
708 In [3]: x.st<TAB>
714 In [3]: x.st<TAB>
709 x.startswith x.strip
715 x.startswith x.strip
710 """,
716 """,
711 r"""
717 r"""
712
718
713 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
719 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
714 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
720 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
715
721
716 In [131]: print url.split('&')
722 In [131]: print url.split('&')
717 ['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']
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']
718
724
719 In [60]: import urllib
725 In [60]: import urllib
720
726
721 """,
727 """,
722 r"""\
728 r"""\
723
729
724 In [133]: import numpy.random
730 In [133]: import numpy.random
725
731
726 @suppress
732 @suppress
727 In [134]: numpy.random.seed(2358)
733 In [134]: numpy.random.seed(2358)
728
734
729 @doctest
735 @doctest
730 In [135]: numpy.random.rand(10,2)
736 In [135]: numpy.random.rand(10,2)
731 Out[135]:
737 Out[135]:
732 array([[ 0.64524308, 0.59943846],
738 array([[ 0.64524308, 0.59943846],
733 [ 0.47102322, 0.8715456 ],
739 [ 0.47102322, 0.8715456 ],
734 [ 0.29370834, 0.74776844],
740 [ 0.29370834, 0.74776844],
735 [ 0.99539577, 0.1313423 ],
741 [ 0.99539577, 0.1313423 ],
736 [ 0.16250302, 0.21103583],
742 [ 0.16250302, 0.21103583],
737 [ 0.81626524, 0.1312433 ],
743 [ 0.81626524, 0.1312433 ],
738 [ 0.67338089, 0.72302393],
744 [ 0.67338089, 0.72302393],
739 [ 0.7566368 , 0.07033696],
745 [ 0.7566368 , 0.07033696],
740 [ 0.22591016, 0.77731835],
746 [ 0.22591016, 0.77731835],
741 [ 0.0072729 , 0.34273127]])
747 [ 0.0072729 , 0.34273127]])
742
748
743 """,
749 """,
744
750
745 r"""
751 r"""
746 In [106]: print x
752 In [106]: print x
747 jdh
753 jdh
748
754
749 In [109]: for i in range(10):
755 In [109]: for i in range(10):
750 .....: print i
756 .....: print i
751 .....:
757 .....:
752 .....:
758 .....:
753 0
759 0
754 1
760 1
755 2
761 2
756 3
762 3
757 4
763 4
758 5
764 5
759 6
765 6
760 7
766 7
761 8
767 8
762 9
768 9
763 """,
769 """,
764
770
765 r"""
771 r"""
766
772
767 In [144]: from pylab import *
773 In [144]: from pylab import *
768
774
769 In [145]: ion()
775 In [145]: ion()
770
776
771 # use a semicolon to suppress the output
777 # use a semicolon to suppress the output
772 @savefig test_hist.png width=4in
778 @savefig test_hist.png width=4in
773 In [151]: hist(np.random.randn(10000), 100);
779 In [151]: hist(np.random.randn(10000), 100);
774
780
775
781
776 @savefig test_plot.png width=4in
782 @savefig test_plot.png width=4in
777 In [151]: plot(np.random.randn(10000), 'o');
783 In [151]: plot(np.random.randn(10000), 'o');
778 """,
784 """,
779
785
780 r"""
786 r"""
781 # use a semicolon to suppress the output
787 # use a semicolon to suppress the output
782 In [151]: plt.clf()
788 In [151]: plt.clf()
783
789
784 @savefig plot_simple.png width=4in
790 @savefig plot_simple.png width=4in
785 In [151]: plot([1,2,3])
791 In [151]: plot([1,2,3])
786
792
787 @savefig hist_simple.png width=4in
793 @savefig hist_simple.png width=4in
788 In [151]: hist(np.random.randn(10000), 100);
794 In [151]: hist(np.random.randn(10000), 100);
789
795
790 """,
796 """,
791 r"""
797 r"""
792 # update the current fig
798 # update the current fig
793 In [151]: ylabel('number')
799 In [151]: ylabel('number')
794
800
795 In [152]: title('normal distribution')
801 In [152]: title('normal distribution')
796
802
797
803
798 @savefig hist_with_text.png
804 @savefig hist_with_text.png
799 In [153]: grid(True)
805 In [153]: grid(True)
800
806
801 """,
807 """,
802 ]
808 ]
803 # skip local-file depending first example:
809 # skip local-file depending first example:
804 examples = examples[1:]
810 examples = examples[1:]
805
811
806 #ipython_directive.DEBUG = True # dbg
812 #ipython_directive.DEBUG = True # dbg
807 #options = dict(suppress=True) # dbg
813 #options = dict(suppress=True) # dbg
808 options = dict()
814 options = dict()
809 for example in examples:
815 for example in examples:
810 content = example.split('\n')
816 content = example.split('\n')
811 ipython_directive('debug', arguments=None, options=options,
817 ipython_directive('debug', arguments=None, options=options,
812 content=content, lineno=0,
818 content=content, lineno=0,
813 content_offset=None, block_text=None,
819 content_offset=None, block_text=None,
814 state=None, state_machine=None,
820 state=None, state_machine=None,
815 )
821 )
816
822
817 # Run test suite as a script
823 # Run test suite as a script
818 if __name__=='__main__':
824 if __name__=='__main__':
819 if not os.path.isdir('_static'):
825 if not os.path.isdir('_static'):
820 os.mkdir('_static')
826 os.mkdir('_static')
821 test()
827 test()
822 print 'All OK? Check figures in _static/'
828 print 'All OK? Check figures in _static/'
General Comments 0
You need to be logged in to leave comments. Login now