##// END OF EJS Templates
Keep track of seen documents to reset line counts across pages
Skipper Seabold -
Show More
@@ -1,813 +1,835 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 store_history = True
317 store_history = True
318
318
319 for i, line in enumerate(input_lines):
319 for i, line in enumerate(input_lines):
320 if line.endswith(';'):
320 if line.endswith(';'):
321 is_semicolon = True
321 is_semicolon = True
322 if is_suppress:
322 if is_suppress:
323 store_history = False
323 store_history = False
324
324
325 if i==0:
325 if i==0:
326 # process the first input line
326 # process the first input line
327 if is_verbatim:
327 if is_verbatim:
328 self.process_input_line('')
328 self.process_input_line('')
329 self.IP.execution_count += 1 # increment it anyway
329 self.IP.execution_count += 1 # increment it anyway
330 else:
330 else:
331 # only submit the line in non-verbatim mode
331 # only submit the line in non-verbatim mode
332 self.process_input_line(line, store_history=store_history)
332 self.process_input_line(line, store_history=store_history)
333 formatted_line = '%s %s'%(input_prompt, line)
333 formatted_line = '%s %s'%(input_prompt, line)
334 else:
334 else:
335 # process a continuation line
335 # process a continuation line
336 if not is_verbatim:
336 if not is_verbatim:
337 self.process_input_line(line, store_history=store_history)
337 self.process_input_line(line, store_history=store_history)
338
338
339 formatted_line = '%s %s'%(continuation, line)
339 formatted_line = '%s %s'%(continuation, line)
340
340
341 if not is_suppress:
341 if not is_suppress:
342 ret.append(formatted_line)
342 ret.append(formatted_line)
343
343
344 if not is_suppress and len(rest.strip()) and is_verbatim:
344 if not is_suppress and len(rest.strip()) and is_verbatim:
345 # the "rest" is the standard output of the
345 # the "rest" is the standard output of the
346 # input, which needs to be added in
346 # input, which needs to be added in
347 # verbatim mode
347 # verbatim mode
348 ret.append(rest)
348 ret.append(rest)
349
349
350 self.cout.seek(0)
350 self.cout.seek(0)
351 output = self.cout.read()
351 output = self.cout.read()
352 if not is_suppress and not is_semicolon:
352 if not is_suppress and not is_semicolon:
353 ret.append(output)
353 ret.append(output)
354 elif is_semicolon: # get spacing right
354 elif is_semicolon: # get spacing right
355 ret.append('')
355 ret.append('')
356
356
357 self.cout.truncate(0)
357 self.cout.truncate(0)
358 return (ret, input_lines, output, is_doctest, image_file,
358 return (ret, input_lines, output, is_doctest, image_file,
359 image_directive)
359 image_directive)
360 #print 'OUTPUT', output # dbg
360 #print 'OUTPUT', output # dbg
361
361
362 def process_output(self, data, output_prompt,
362 def process_output(self, data, output_prompt,
363 input_lines, output, is_doctest, image_file):
363 input_lines, output, is_doctest, image_file):
364 """Process data block for OUTPUT token."""
364 """Process data block for OUTPUT token."""
365 if is_doctest:
365 if is_doctest:
366 submitted = data.strip()
366 submitted = data.strip()
367 found = output
367 found = output
368 if found is not None:
368 if found is not None:
369 found = found.strip()
369 found = found.strip()
370
370
371 # XXX - fperez: in 0.11, 'output' never comes with the prompt
371 # XXX - fperez: in 0.11, 'output' never comes with the prompt
372 # in it, just the actual output text. So I think all this code
372 # in it, just the actual output text. So I think all this code
373 # can be nuked...
373 # can be nuked...
374
374
375 # the above comment does not appear to be accurate... (minrk)
375 # the above comment does not appear to be accurate... (minrk)
376
376
377 ind = found.find(output_prompt)
377 ind = found.find(output_prompt)
378 if ind<0:
378 if ind<0:
379 e='output prompt="%s" does not match out line=%s' % \
379 e='output prompt="%s" does not match out line=%s' % \
380 (output_prompt, found)
380 (output_prompt, found)
381 raise RuntimeError(e)
381 raise RuntimeError(e)
382 found = found[len(output_prompt):].strip()
382 found = found[len(output_prompt):].strip()
383
383
384 if found!=submitted:
384 if found!=submitted:
385 e = ('doctest failure for input_lines="%s" with '
385 e = ('doctest failure for input_lines="%s" with '
386 'found_output="%s" and submitted output="%s"' %
386 'found_output="%s" and submitted output="%s"' %
387 (input_lines, found, submitted) )
387 (input_lines, found, submitted) )
388 raise RuntimeError(e)
388 raise RuntimeError(e)
389 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
389 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
390
390
391 def process_comment(self, data):
391 def process_comment(self, data):
392 """Process data fPblock for COMMENT token."""
392 """Process data fPblock for COMMENT token."""
393 if not self.is_suppress:
393 if not self.is_suppress:
394 return [data]
394 return [data]
395
395
396 def save_image(self, image_file):
396 def save_image(self, image_file):
397 """
397 """
398 Saves the image file to disk.
398 Saves the image file to disk.
399 """
399 """
400 self.ensure_pyplot()
400 self.ensure_pyplot()
401 command = 'plt.gcf().savefig("%s")'%image_file
401 command = 'plt.gcf().savefig("%s")'%image_file
402 #print 'SAVEFIG', command # dbg
402 #print 'SAVEFIG', command # dbg
403 self.process_input_line('bookmark ipy_thisdir', store_history=False)
403 self.process_input_line('bookmark ipy_thisdir', store_history=False)
404 self.process_input_line('cd -b ipy_savedir', store_history=False)
404 self.process_input_line('cd -b ipy_savedir', store_history=False)
405 self.process_input_line(command, store_history=False)
405 self.process_input_line(command, store_history=False)
406 self.process_input_line('cd -b ipy_thisdir', store_history=False)
406 self.process_input_line('cd -b ipy_thisdir', store_history=False)
407 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
407 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
408 self.clear_cout()
408 self.clear_cout()
409
409
410
410
411 def process_block(self, block):
411 def process_block(self, block):
412 """
412 """
413 process block from the block_parser and return a list of processed lines
413 process block from the block_parser and return a list of processed lines
414 """
414 """
415 ret = []
415 ret = []
416 output = None
416 output = None
417 input_lines = None
417 input_lines = None
418 lineno = self.IP.execution_count
418 lineno = self.IP.execution_count
419
419
420 input_prompt = self.promptin%lineno
420 input_prompt = self.promptin%lineno
421 output_prompt = self.promptout%lineno
421 output_prompt = self.promptout%lineno
422 image_file = None
422 image_file = None
423 image_directive = None
423 image_directive = None
424
424
425 for token, data in block:
425 for token, data in block:
426 if token==COMMENT:
426 if token==COMMENT:
427 out_data = self.process_comment(data)
427 out_data = self.process_comment(data)
428 elif token==INPUT:
428 elif token==INPUT:
429 (out_data, input_lines, output, is_doctest, image_file,
429 (out_data, input_lines, output, is_doctest, image_file,
430 image_directive) = \
430 image_directive) = \
431 self.process_input(data, input_prompt, lineno)
431 self.process_input(data, input_prompt, lineno)
432 elif token==OUTPUT:
432 elif token==OUTPUT:
433 out_data = \
433 out_data = \
434 self.process_output(data, output_prompt,
434 self.process_output(data, output_prompt,
435 input_lines, output, is_doctest,
435 input_lines, output, is_doctest,
436 image_file)
436 image_file)
437 if out_data:
437 if out_data:
438 ret.extend(out_data)
438 ret.extend(out_data)
439
439
440 # save the image files
440 # save the image files
441 if image_file is not None:
441 if image_file is not None:
442 self.save_image(image_file)
442 self.save_image(image_file)
443
443
444 return ret, image_directive
444 return ret, image_directive
445
445
446 def ensure_pyplot(self):
446 def ensure_pyplot(self):
447 if self._pyplot_imported:
447 if self._pyplot_imported:
448 return
448 return
449 self.process_input_line('import matplotlib.pyplot as plt',
449 self.process_input_line('import matplotlib.pyplot as plt',
450 store_history=False)
450 store_history=False)
451
451
452 def process_pure_python(self, content):
452 def process_pure_python(self, content):
453 """
453 """
454 content is a list of strings. it is unedited directive conent
454 content is a list of strings. it is unedited directive conent
455
455
456 This runs it line by line in the InteractiveShell, prepends
456 This runs it line by line in the InteractiveShell, prepends
457 prompts as needed capturing stderr and stdout, then returns
457 prompts as needed capturing stderr and stdout, then returns
458 the content as a list as if it were ipython code
458 the content as a list as if it were ipython code
459 """
459 """
460 output = []
460 output = []
461 savefig = False # keep up with this to clear figure
461 savefig = False # keep up with this to clear figure
462 multiline = False # to handle line continuation
462 multiline = False # to handle line continuation
463 multiline_start = None
463 multiline_start = None
464 fmtin = self.promptin
464 fmtin = self.promptin
465
465
466 ct = 0
466 ct = 0
467
467
468 for lineno, line in enumerate(content):
468 for lineno, line in enumerate(content):
469
469
470 line_stripped = line.strip()
470 line_stripped = line.strip()
471 if not len(line):
471 if not len(line):
472 output.append(line)
472 output.append(line)
473 continue
473 continue
474
474
475 # handle decorators
475 # handle decorators
476 if line_stripped.startswith('@'):
476 if line_stripped.startswith('@'):
477 output.extend([line])
477 output.extend([line])
478 if 'savefig' in line:
478 if 'savefig' in line:
479 savefig = True # and need to clear figure
479 savefig = True # and need to clear figure
480 continue
480 continue
481
481
482 # handle comments
482 # handle comments
483 if line_stripped.startswith('#'):
483 if line_stripped.startswith('#'):
484 output.extend([line])
484 output.extend([line])
485 continue
485 continue
486
486
487 # deal with lines checking for multiline
487 # deal with lines checking for multiline
488 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
488 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
489 if not multiline:
489 if not multiline:
490 modified = u"%s %s" % (fmtin % ct, line_stripped)
490 modified = u"%s %s" % (fmtin % ct, line_stripped)
491 output.append(modified)
491 output.append(modified)
492 ct += 1
492 ct += 1
493 try:
493 try:
494 ast.parse(line_stripped)
494 ast.parse(line_stripped)
495 output.append(u'')
495 output.append(u'')
496 except Exception: # on a multiline
496 except Exception: # on a multiline
497 multiline = True
497 multiline = True
498 multiline_start = lineno
498 multiline_start = lineno
499 if line_stripped.startswith('def '):
499 if line_stripped.startswith('def '):
500 is_function = True
500 is_function = True
501 else: # still on a multiline
501 else: # still on a multiline
502 modified = u'%s %s' % (continuation, line)
502 modified = u'%s %s' % (continuation, line)
503 output.append(modified)
503 output.append(modified)
504 try:
504 try:
505 mod = ast.parse(
505 mod = ast.parse(
506 '\n'.join(content[multiline_start:lineno+1]))
506 '\n'.join(content[multiline_start:lineno+1]))
507 if isinstance(mod.body[0], ast.FunctionDef):
507 if isinstance(mod.body[0], ast.FunctionDef):
508 # check to see if we have the whole function
508 # check to see if we have the whole function
509 for element in mod.body[0].body:
509 for element in mod.body[0].body:
510 if isinstance(element, ast.Return):
510 if isinstance(element, ast.Return):
511 multiline = False
511 multiline = False
512 else:
512 else:
513 output.append(u'')
513 output.append(u'')
514 multiline = False
514 multiline = False
515 except Exception:
515 except Exception:
516 pass
516 pass
517
517
518 if savefig: # clear figure if plotted
518 if savefig: # clear figure if plotted
519 self.ensure_pyplot()
519 self.ensure_pyplot()
520 self.process_input_line('plt.clf()', store_history=False)
520 self.process_input_line('plt.clf()', store_history=False)
521 self.clear_cout()
521 self.clear_cout()
522 savefig = False
522 savefig = False
523
523
524 return output
524 return output
525
525
526 class IpythonDirective(Directive):
526 class IpythonDirective(Directive):
527
527
528 has_content = True
528 has_content = True
529 required_arguments = 0
529 required_arguments = 0
530 optional_arguments = 4 # python, suppress, verbatim, doctest
530 optional_arguments = 4 # python, suppress, verbatim, doctest
531 final_argumuent_whitespace = True
531 final_argumuent_whitespace = True
532 option_spec = { 'python': directives.unchanged,
532 option_spec = { 'python': directives.unchanged,
533 'suppress' : directives.flag,
533 'suppress' : directives.flag,
534 'verbatim' : directives.flag,
534 'verbatim' : directives.flag,
535 'doctest' : directives.flag,
535 'doctest' : directives.flag,
536 }
536 }
537
537
538 shell = EmbeddedSphinxShell()
538 shell = EmbeddedSphinxShell()
539
539
540 def get_config_options(self):
540 def get_config_options(self):
541 # contains sphinx configuration variables
541 # contains sphinx configuration variables
542 config = self.state.document.settings.env.config
542 config = self.state.document.settings.env.config
543
543
544 # get config variables to set figure output directory
544 # get config variables to set figure output directory
545 confdir = self.state.document.settings.env.app.confdir
545 confdir = self.state.document.settings.env.app.confdir
546 savefig_dir = config.ipython_savefig_dir
546 savefig_dir = config.ipython_savefig_dir
547 source_dir = os.path.dirname(self.state.document.current_source)
547 source_dir = os.path.dirname(self.state.document.current_source)
548 if savefig_dir is None:
548 if savefig_dir is None:
549 savefig_dir = config.html_static_path
549 savefig_dir = config.html_static_path
550 if isinstance(savefig_dir, list):
550 if isinstance(savefig_dir, list):
551 savefig_dir = savefig_dir[0] # safe to assume only one path?
551 savefig_dir = savefig_dir[0] # safe to assume only one path?
552 savefig_dir = os.path.join(confdir, savefig_dir)
552 savefig_dir = os.path.join(confdir, savefig_dir)
553
553
554 # get regex and prompt stuff
554 # get regex and prompt stuff
555 rgxin = config.ipython_rgxin
555 rgxin = config.ipython_rgxin
556 rgxout = config.ipython_rgxout
556 rgxout = config.ipython_rgxout
557 promptin = config.ipython_promptin
557 promptin = config.ipython_promptin
558 promptout = config.ipython_promptout
558 promptout = config.ipython_promptout
559
559
560 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
560 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
561
561
562 def setup(self):
562 def setup(self):
563 # reset the execution count if we haven't processed this doc
564 #NOTE: this may be borked if there are multiple seen_doc tmp files
565 #check time stamp?
566 seen_docs = [i for i in os.listdir(tempfile.tempdir)
567 if i.startswith('seen_doc')]
568 if seen_docs:
569 fname = os.path.join(tempfile.tempdir, seen_docs[0])
570 docs = open(fname).read().split('\n')
571 if not self.state.document.current_source in docs:
572 self.shell.IP.history_manager.reset()
573 self.shell.IP.execution_count = 1
574 else: # haven't processed any docs yet
575 docs = []
576
577
563 # get config values
578 # get config values
564 (savefig_dir, source_dir, rgxin,
579 (savefig_dir, source_dir, rgxin,
565 rgxout, promptin, promptout) = self.get_config_options()
580 rgxout, promptin, promptout) = self.get_config_options()
566
581
567 # 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
568 self.shell.rgxin = rgxin
583 self.shell.rgxin = rgxin
569 self.shell.rgxout = rgxout
584 self.shell.rgxout = rgxout
570 self.shell.promptin = promptin
585 self.shell.promptin = promptin
571 self.shell.promptout = promptout
586 self.shell.promptout = promptout
572 self.shell.savefig_dir = savefig_dir
587 self.shell.savefig_dir = savefig_dir
573 self.shell.source_dir = source_dir
588 self.shell.source_dir = source_dir
574
589
575 # setup bookmark for saving figures directory
590 # setup bookmark for saving figures directory
576
591
577 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
592 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
578 store_history=False)
593 store_history=False)
579 self.shell.clear_cout()
594 self.shell.clear_cout()
580
595
596 # write the filename to a tempfile because it's been "seen" now
597 if not self.state.document.current_source in docs:
598 fd, fname = tempfile.mkstemp(prefix="seen_doc", text=True)
599 fout = open(fname, 'a')
600 fout.write(self.state.document.current_source+'\n')
601 fout.close()
602
581 return rgxin, rgxout, promptin, promptout
603 return rgxin, rgxout, promptin, promptout
582
604
583
605
584 def teardown(self):
606 def teardown(self):
585 # delete last bookmark
607 # delete last bookmark
586 self.shell.process_input_line('bookmark -d ipy_savedir',
608 self.shell.process_input_line('bookmark -d ipy_savedir',
587 store_history=False)
609 store_history=False)
588 self.shell.clear_cout()
610 self.shell.clear_cout()
589
611
590 def run(self):
612 def run(self):
591 debug = False
613 debug = False
592
614
593 #TODO, any reason block_parser can't be a method of embeddable shell
615 #TODO, any reason block_parser can't be a method of embeddable shell
594 # then we wouldn't have to carry these around
616 # then we wouldn't have to carry these around
595 rgxin, rgxout, promptin, promptout = self.setup()
617 rgxin, rgxout, promptin, promptout = self.setup()
596
618
597 options = self.options
619 options = self.options
598 self.shell.is_suppress = 'suppress' in options
620 self.shell.is_suppress = 'suppress' in options
599 self.shell.is_doctest = 'doctest' in options
621 self.shell.is_doctest = 'doctest' in options
600 self.shell.is_verbatim = 'verbatim' in options
622 self.shell.is_verbatim = 'verbatim' in options
601
623
602
624
603 # handle pure python code
625 # handle pure python code
604 if 'python' in self.arguments:
626 if 'python' in self.arguments:
605 content = self.content
627 content = self.content
606 self.content = self.shell.process_pure_python(content)
628 self.content = self.shell.process_pure_python(content)
607
629
608 parts = '\n'.join(self.content).split('\n\n')
630 parts = '\n'.join(self.content).split('\n\n')
609
631
610 lines = ['.. code-block:: ipython','']
632 lines = ['.. code-block:: ipython','']
611 figures = []
633 figures = []
612
634
613 for part in parts:
635 for part in parts:
614
636
615 block = block_parser(part, rgxin, rgxout, promptin, promptout)
637 block = block_parser(part, rgxin, rgxout, promptin, promptout)
616
638
617 if len(block):
639 if len(block):
618 rows, figure = self.shell.process_block(block)
640 rows, figure = self.shell.process_block(block)
619 for row in rows:
641 for row in rows:
620 lines.extend([' %s'%line for line in row.split('\n')])
642 lines.extend([' %s'%line for line in row.split('\n')])
621
643
622 if figure is not None:
644 if figure is not None:
623 figures.append(figure)
645 figures.append(figure)
624
646
625 #text = '\n'.join(lines)
647 #text = '\n'.join(lines)
626 #figs = '\n'.join(figures)
648 #figs = '\n'.join(figures)
627
649
628 for figure in figures:
650 for figure in figures:
629 lines.append('')
651 lines.append('')
630 lines.extend(figure.split('\n'))
652 lines.extend(figure.split('\n'))
631 lines.append('')
653 lines.append('')
632
654
633 #print lines
655 #print lines
634 if len(lines)>2:
656 if len(lines)>2:
635 if debug:
657 if debug:
636 print '\n'.join(lines)
658 print '\n'.join(lines)
637 else: #NOTE: this raises some errors, what's it for?
659 else: #NOTE: this raises some errors, what's it for?
638 #print 'INSERTING %d lines'%len(lines)
660 #print 'INSERTING %d lines'%len(lines)
639 self.state_machine.insert_input(
661 self.state_machine.insert_input(
640 lines, self.state_machine.input_lines.source(0))
662 lines, self.state_machine.input_lines.source(0))
641
663
642 text = '\n'.join(lines)
664 text = '\n'.join(lines)
643 txtnode = nodes.literal_block(text, text)
665 txtnode = nodes.literal_block(text, text)
644 txtnode['language'] = 'ipython'
666 txtnode['language'] = 'ipython'
645 #imgnode = nodes.image(figs)
667 #imgnode = nodes.image(figs)
646
668
647 # cleanup
669 # cleanup
648 self.teardown()
670 self.teardown()
649
671
650 return []#, imgnode]
672 return []#, imgnode]
651
673
652 # Enable as a proper Sphinx directive
674 # Enable as a proper Sphinx directive
653 def setup(app):
675 def setup(app):
654 setup.app = app
676 setup.app = app
655
677
656 app.add_directive('ipython', IpythonDirective)
678 app.add_directive('ipython', IpythonDirective)
657 app.add_config_value('ipython_savefig_dir', None, True)
679 app.add_config_value('ipython_savefig_dir', None, True)
658 app.add_config_value('ipython_rgxin',
680 app.add_config_value('ipython_rgxin',
659 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
681 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
660 app.add_config_value('ipython_rgxout',
682 app.add_config_value('ipython_rgxout',
661 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
683 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
662 app.add_config_value('ipython_promptin', 'In [%d]:', True)
684 app.add_config_value('ipython_promptin', 'In [%d]:', True)
663 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
685 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
664
686
665
687
666 # Simple smoke test, needs to be converted to a proper automatic test.
688 # Simple smoke test, needs to be converted to a proper automatic test.
667 def test():
689 def test():
668
690
669 examples = [
691 examples = [
670 r"""
692 r"""
671 In [9]: pwd
693 In [9]: pwd
672 Out[9]: '/home/jdhunter/py4science/book'
694 Out[9]: '/home/jdhunter/py4science/book'
673
695
674 In [10]: cd bookdata/
696 In [10]: cd bookdata/
675 /home/jdhunter/py4science/book/bookdata
697 /home/jdhunter/py4science/book/bookdata
676
698
677 In [2]: from pylab import *
699 In [2]: from pylab import *
678
700
679 In [2]: ion()
701 In [2]: ion()
680
702
681 In [3]: im = imread('stinkbug.png')
703 In [3]: im = imread('stinkbug.png')
682
704
683 @savefig mystinkbug.png width=4in
705 @savefig mystinkbug.png width=4in
684 In [4]: imshow(im)
706 In [4]: imshow(im)
685 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
707 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
686
708
687 """,
709 """,
688 r"""
710 r"""
689
711
690 In [1]: x = 'hello world'
712 In [1]: x = 'hello world'
691
713
692 # string methods can be
714 # string methods can be
693 # used to alter the string
715 # used to alter the string
694 @doctest
716 @doctest
695 In [2]: x.upper()
717 In [2]: x.upper()
696 Out[2]: 'HELLO WORLD'
718 Out[2]: 'HELLO WORLD'
697
719
698 @verbatim
720 @verbatim
699 In [3]: x.st<TAB>
721 In [3]: x.st<TAB>
700 x.startswith x.strip
722 x.startswith x.strip
701 """,
723 """,
702 r"""
724 r"""
703
725
704 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
726 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
705 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
727 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
706
728
707 In [131]: print url.split('&')
729 In [131]: print url.split('&')
708 ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv']
730 ['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']
709
731
710 In [60]: import urllib
732 In [60]: import urllib
711
733
712 """,
734 """,
713 r"""\
735 r"""\
714
736
715 In [133]: import numpy.random
737 In [133]: import numpy.random
716
738
717 @suppress
739 @suppress
718 In [134]: numpy.random.seed(2358)
740 In [134]: numpy.random.seed(2358)
719
741
720 @doctest
742 @doctest
721 In [135]: numpy.random.rand(10,2)
743 In [135]: numpy.random.rand(10,2)
722 Out[135]:
744 Out[135]:
723 array([[ 0.64524308, 0.59943846],
745 array([[ 0.64524308, 0.59943846],
724 [ 0.47102322, 0.8715456 ],
746 [ 0.47102322, 0.8715456 ],
725 [ 0.29370834, 0.74776844],
747 [ 0.29370834, 0.74776844],
726 [ 0.99539577, 0.1313423 ],
748 [ 0.99539577, 0.1313423 ],
727 [ 0.16250302, 0.21103583],
749 [ 0.16250302, 0.21103583],
728 [ 0.81626524, 0.1312433 ],
750 [ 0.81626524, 0.1312433 ],
729 [ 0.67338089, 0.72302393],
751 [ 0.67338089, 0.72302393],
730 [ 0.7566368 , 0.07033696],
752 [ 0.7566368 , 0.07033696],
731 [ 0.22591016, 0.77731835],
753 [ 0.22591016, 0.77731835],
732 [ 0.0072729 , 0.34273127]])
754 [ 0.0072729 , 0.34273127]])
733
755
734 """,
756 """,
735
757
736 r"""
758 r"""
737 In [106]: print x
759 In [106]: print x
738 jdh
760 jdh
739
761
740 In [109]: for i in range(10):
762 In [109]: for i in range(10):
741 .....: print i
763 .....: print i
742 .....:
764 .....:
743 .....:
765 .....:
744 0
766 0
745 1
767 1
746 2
768 2
747 3
769 3
748 4
770 4
749 5
771 5
750 6
772 6
751 7
773 7
752 8
774 8
753 9
775 9
754 """,
776 """,
755
777
756 r"""
778 r"""
757
779
758 In [144]: from pylab import *
780 In [144]: from pylab import *
759
781
760 In [145]: ion()
782 In [145]: ion()
761
783
762 # use a semicolon to suppress the output
784 # use a semicolon to suppress the output
763 @savefig test_hist.png width=4in
785 @savefig test_hist.png width=4in
764 In [151]: hist(np.random.randn(10000), 100);
786 In [151]: hist(np.random.randn(10000), 100);
765
787
766
788
767 @savefig test_plot.png width=4in
789 @savefig test_plot.png width=4in
768 In [151]: plot(np.random.randn(10000), 'o');
790 In [151]: plot(np.random.randn(10000), 'o');
769 """,
791 """,
770
792
771 r"""
793 r"""
772 # use a semicolon to suppress the output
794 # use a semicolon to suppress the output
773 In [151]: plt.clf()
795 In [151]: plt.clf()
774
796
775 @savefig plot_simple.png width=4in
797 @savefig plot_simple.png width=4in
776 In [151]: plot([1,2,3])
798 In [151]: plot([1,2,3])
777
799
778 @savefig hist_simple.png width=4in
800 @savefig hist_simple.png width=4in
779 In [151]: hist(np.random.randn(10000), 100);
801 In [151]: hist(np.random.randn(10000), 100);
780
802
781 """,
803 """,
782 r"""
804 r"""
783 # update the current fig
805 # update the current fig
784 In [151]: ylabel('number')
806 In [151]: ylabel('number')
785
807
786 In [152]: title('normal distribution')
808 In [152]: title('normal distribution')
787
809
788
810
789 @savefig hist_with_text.png
811 @savefig hist_with_text.png
790 In [153]: grid(True)
812 In [153]: grid(True)
791
813
792 """,
814 """,
793 ]
815 ]
794 # skip local-file depending first example:
816 # skip local-file depending first example:
795 examples = examples[1:]
817 examples = examples[1:]
796
818
797 #ipython_directive.DEBUG = True # dbg
819 #ipython_directive.DEBUG = True # dbg
798 #options = dict(suppress=True) # dbg
820 #options = dict(suppress=True) # dbg
799 options = dict()
821 options = dict()
800 for example in examples:
822 for example in examples:
801 content = example.split('\n')
823 content = example.split('\n')
802 ipython_directive('debug', arguments=None, options=options,
824 ipython_directive('debug', arguments=None, options=options,
803 content=content, lineno=0,
825 content=content, lineno=0,
804 content_offset=None, block_text=None,
826 content_offset=None, block_text=None,
805 state=None, state_machine=None,
827 state=None, state_machine=None,
806 )
828 )
807
829
808 # Run test suite as a script
830 # Run test suite as a script
809 if __name__=='__main__':
831 if __name__=='__main__':
810 if not os.path.isdir('_static'):
832 if not os.path.isdir('_static'):
811 os.mkdir('_static')
833 os.mkdir('_static')
812 test()
834 test()
813 print 'All OK? Check figures in _static/'
835 print 'All OK? Check figures in _static/'
General Comments 0
You need to be logged in to leave comments. Login now