##// END OF EJS Templates
Merge pull request #3298 from abakan/master...
Min RK -
r10952:51078a2f merge
parent child Browse files
Show More
@@ -1,830 +1,828
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
541 seen_docs = set()
542
535 def get_config_options(self):
543 def get_config_options(self):
536 # contains sphinx configuration variables
544 # contains sphinx configuration variables
537 config = self.state.document.settings.env.config
545 config = self.state.document.settings.env.config
538
546
539 # get config variables to set figure output directory
547 # get config variables to set figure output directory
540 confdir = self.state.document.settings.env.app.confdir
548 confdir = self.state.document.settings.env.app.confdir
541 savefig_dir = config.ipython_savefig_dir
549 savefig_dir = config.ipython_savefig_dir
542 source_dir = os.path.dirname(self.state.document.current_source)
550 source_dir = os.path.dirname(self.state.document.current_source)
543 if savefig_dir is None:
551 if savefig_dir is None:
544 savefig_dir = config.html_static_path
552 savefig_dir = config.html_static_path
545 if isinstance(savefig_dir, list):
553 if isinstance(savefig_dir, list):
546 savefig_dir = savefig_dir[0] # safe to assume only one path?
554 savefig_dir = savefig_dir[0] # safe to assume only one path?
547 savefig_dir = os.path.join(confdir, savefig_dir)
555 savefig_dir = os.path.join(confdir, savefig_dir)
548
556
549 # get regex and prompt stuff
557 # get regex and prompt stuff
550 rgxin = config.ipython_rgxin
558 rgxin = config.ipython_rgxin
551 rgxout = config.ipython_rgxout
559 rgxout = config.ipython_rgxout
552 promptin = config.ipython_promptin
560 promptin = config.ipython_promptin
553 promptout = config.ipython_promptout
561 promptout = config.ipython_promptout
554
562
555 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
563 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
556
564
557 def setup(self):
565 def setup(self):
558 # reset the execution count if we haven't processed this doc
566 # reset the execution count if we haven't processed this doc
559 #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
560 #check time stamp?
568 #check time stamp?
561 seen_docs = [i for i in os.listdir(tempfile.tempdir)
569
562 if i.startswith('seen_doc')]
570
563 if seen_docs:
571 if not self.state.document.current_source in self.seen_docs:
564 fname = os.path.join(tempfile.tempdir, seen_docs[0])
565 docs = open(fname).read().split('\n')
566 if not self.state.document.current_source in docs:
567 self.shell.IP.history_manager.reset()
572 self.shell.IP.history_manager.reset()
568 self.shell.IP.execution_count = 1
573 self.shell.IP.execution_count = 1
569 else: # haven't processed any docs yet
574 self.seen_docs.add(self.state.document.current_source)
570 docs = []
575
571
576
572
577
573 # get config values
578 # get config values
574 (savefig_dir, source_dir, rgxin,
579 (savefig_dir, source_dir, rgxin,
575 rgxout, promptin, promptout) = self.get_config_options()
580 rgxout, promptin, promptout) = self.get_config_options()
576
581
577 # 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
578 self.shell.rgxin = rgxin
583 self.shell.rgxin = rgxin
579 self.shell.rgxout = rgxout
584 self.shell.rgxout = rgxout
580 self.shell.promptin = promptin
585 self.shell.promptin = promptin
581 self.shell.promptout = promptout
586 self.shell.promptout = promptout
582 self.shell.savefig_dir = savefig_dir
587 self.shell.savefig_dir = savefig_dir
583 self.shell.source_dir = source_dir
588 self.shell.source_dir = source_dir
584
589
585 # setup bookmark for saving figures directory
590 # setup bookmark for saving figures directory
586
591
587 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
592 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
588 store_history=False)
593 store_history=False)
589 self.shell.clear_cout()
594 self.shell.clear_cout()
590
595
591 # write the filename to a tempfile because it's been "seen" now
592 if not self.state.document.current_source in docs:
593 fd, fname = tempfile.mkstemp(prefix="seen_doc", text=True)
594 fout = open(fname, 'a')
595 fout.write(self.state.document.current_source+'\n')
596 fout.close()
597
598 return rgxin, rgxout, promptin, promptout
596 return rgxin, rgxout, promptin, promptout
599
597
600
598
601 def teardown(self):
599 def teardown(self):
602 # delete last bookmark
600 # delete last bookmark
603 self.shell.process_input_line('bookmark -d ipy_savedir',
601 self.shell.process_input_line('bookmark -d ipy_savedir',
604 store_history=False)
602 store_history=False)
605 self.shell.clear_cout()
603 self.shell.clear_cout()
606
604
607 def run(self):
605 def run(self):
608 debug = False
606 debug = False
609
607
610 #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
611 # then we wouldn't have to carry these around
609 # then we wouldn't have to carry these around
612 rgxin, rgxout, promptin, promptout = self.setup()
610 rgxin, rgxout, promptin, promptout = self.setup()
613
611
614 options = self.options
612 options = self.options
615 self.shell.is_suppress = 'suppress' in options
613 self.shell.is_suppress = 'suppress' in options
616 self.shell.is_doctest = 'doctest' in options
614 self.shell.is_doctest = 'doctest' in options
617 self.shell.is_verbatim = 'verbatim' in options
615 self.shell.is_verbatim = 'verbatim' in options
618
616
619
617
620 # handle pure python code
618 # handle pure python code
621 if 'python' in self.arguments:
619 if 'python' in self.arguments:
622 content = self.content
620 content = self.content
623 self.content = self.shell.process_pure_python(content)
621 self.content = self.shell.process_pure_python(content)
624
622
625 parts = '\n'.join(self.content).split('\n\n')
623 parts = '\n'.join(self.content).split('\n\n')
626
624
627 lines = ['.. code-block:: ipython','']
625 lines = ['.. code-block:: ipython','']
628 figures = []
626 figures = []
629
627
630 for part in parts:
628 for part in parts:
631
629
632 block = block_parser(part, rgxin, rgxout, promptin, promptout)
630 block = block_parser(part, rgxin, rgxout, promptin, promptout)
633
631
634 if len(block):
632 if len(block):
635 rows, figure = self.shell.process_block(block)
633 rows, figure = self.shell.process_block(block)
636 for row in rows:
634 for row in rows:
637 lines.extend([' %s'%line for line in row.split('\n')])
635 lines.extend([' %s'%line for line in row.split('\n')])
638
636
639 if figure is not None:
637 if figure is not None:
640 figures.append(figure)
638 figures.append(figure)
641
639
642 #text = '\n'.join(lines)
640 #text = '\n'.join(lines)
643 #figs = '\n'.join(figures)
641 #figs = '\n'.join(figures)
644
642
645 for figure in figures:
643 for figure in figures:
646 lines.append('')
644 lines.append('')
647 lines.extend(figure.split('\n'))
645 lines.extend(figure.split('\n'))
648 lines.append('')
646 lines.append('')
649
647
650 #print lines
648 #print lines
651 if len(lines)>2:
649 if len(lines)>2:
652 if debug:
650 if debug:
653 print '\n'.join(lines)
651 print '\n'.join(lines)
654 else: #NOTE: this raises some errors, what's it for?
652 else: #NOTE: this raises some errors, what's it for?
655 #print 'INSERTING %d lines'%len(lines)
653 #print 'INSERTING %d lines'%len(lines)
656 self.state_machine.insert_input(
654 self.state_machine.insert_input(
657 lines, self.state_machine.input_lines.source(0))
655 lines, self.state_machine.input_lines.source(0))
658
656
659 text = '\n'.join(lines)
657 text = '\n'.join(lines)
660 txtnode = nodes.literal_block(text, text)
658 txtnode = nodes.literal_block(text, text)
661 txtnode['language'] = 'ipython'
659 txtnode['language'] = 'ipython'
662 #imgnode = nodes.image(figs)
660 #imgnode = nodes.image(figs)
663
661
664 # cleanup
662 # cleanup
665 self.teardown()
663 self.teardown()
666
664
667 return []#, imgnode]
665 return []#, imgnode]
668
666
669 # Enable as a proper Sphinx directive
667 # Enable as a proper Sphinx directive
670 def setup(app):
668 def setup(app):
671 setup.app = app
669 setup.app = app
672
670
673 app.add_directive('ipython', IpythonDirective)
671 app.add_directive('ipython', IpythonDirective)
674 app.add_config_value('ipython_savefig_dir', None, True)
672 app.add_config_value('ipython_savefig_dir', None, True)
675 app.add_config_value('ipython_rgxin',
673 app.add_config_value('ipython_rgxin',
676 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
674 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
677 app.add_config_value('ipython_rgxout',
675 app.add_config_value('ipython_rgxout',
678 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
676 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
679 app.add_config_value('ipython_promptin', 'In [%d]:', True)
677 app.add_config_value('ipython_promptin', 'In [%d]:', True)
680 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
678 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
681
679
682
680
683 # 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.
684 def test():
682 def test():
685
683
686 examples = [
684 examples = [
687 r"""
685 r"""
688 In [9]: pwd
686 In [9]: pwd
689 Out[9]: '/home/jdhunter/py4science/book'
687 Out[9]: '/home/jdhunter/py4science/book'
690
688
691 In [10]: cd bookdata/
689 In [10]: cd bookdata/
692 /home/jdhunter/py4science/book/bookdata
690 /home/jdhunter/py4science/book/bookdata
693
691
694 In [2]: from pylab import *
692 In [2]: from pylab import *
695
693
696 In [2]: ion()
694 In [2]: ion()
697
695
698 In [3]: im = imread('stinkbug.png')
696 In [3]: im = imread('stinkbug.png')
699
697
700 @savefig mystinkbug.png width=4in
698 @savefig mystinkbug.png width=4in
701 In [4]: imshow(im)
699 In [4]: imshow(im)
702 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
700 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
703
701
704 """,
702 """,
705 r"""
703 r"""
706
704
707 In [1]: x = 'hello world'
705 In [1]: x = 'hello world'
708
706
709 # string methods can be
707 # string methods can be
710 # used to alter the string
708 # used to alter the string
711 @doctest
709 @doctest
712 In [2]: x.upper()
710 In [2]: x.upper()
713 Out[2]: 'HELLO WORLD'
711 Out[2]: 'HELLO WORLD'
714
712
715 @verbatim
713 @verbatim
716 In [3]: x.st<TAB>
714 In [3]: x.st<TAB>
717 x.startswith x.strip
715 x.startswith x.strip
718 """,
716 """,
719 r"""
717 r"""
720
718
721 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\
722 .....: &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'
723
721
724 In [131]: print url.split('&')
722 In [131]: print url.split('&')
725 ['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']
726
724
727 In [60]: import urllib
725 In [60]: import urllib
728
726
729 """,
727 """,
730 r"""\
728 r"""\
731
729
732 In [133]: import numpy.random
730 In [133]: import numpy.random
733
731
734 @suppress
732 @suppress
735 In [134]: numpy.random.seed(2358)
733 In [134]: numpy.random.seed(2358)
736
734
737 @doctest
735 @doctest
738 In [135]: numpy.random.rand(10,2)
736 In [135]: numpy.random.rand(10,2)
739 Out[135]:
737 Out[135]:
740 array([[ 0.64524308, 0.59943846],
738 array([[ 0.64524308, 0.59943846],
741 [ 0.47102322, 0.8715456 ],
739 [ 0.47102322, 0.8715456 ],
742 [ 0.29370834, 0.74776844],
740 [ 0.29370834, 0.74776844],
743 [ 0.99539577, 0.1313423 ],
741 [ 0.99539577, 0.1313423 ],
744 [ 0.16250302, 0.21103583],
742 [ 0.16250302, 0.21103583],
745 [ 0.81626524, 0.1312433 ],
743 [ 0.81626524, 0.1312433 ],
746 [ 0.67338089, 0.72302393],
744 [ 0.67338089, 0.72302393],
747 [ 0.7566368 , 0.07033696],
745 [ 0.7566368 , 0.07033696],
748 [ 0.22591016, 0.77731835],
746 [ 0.22591016, 0.77731835],
749 [ 0.0072729 , 0.34273127]])
747 [ 0.0072729 , 0.34273127]])
750
748
751 """,
749 """,
752
750
753 r"""
751 r"""
754 In [106]: print x
752 In [106]: print x
755 jdh
753 jdh
756
754
757 In [109]: for i in range(10):
755 In [109]: for i in range(10):
758 .....: print i
756 .....: print i
759 .....:
757 .....:
760 .....:
758 .....:
761 0
759 0
762 1
760 1
763 2
761 2
764 3
762 3
765 4
763 4
766 5
764 5
767 6
765 6
768 7
766 7
769 8
767 8
770 9
768 9
771 """,
769 """,
772
770
773 r"""
771 r"""
774
772
775 In [144]: from pylab import *
773 In [144]: from pylab import *
776
774
777 In [145]: ion()
775 In [145]: ion()
778
776
779 # use a semicolon to suppress the output
777 # use a semicolon to suppress the output
780 @savefig test_hist.png width=4in
778 @savefig test_hist.png width=4in
781 In [151]: hist(np.random.randn(10000), 100);
779 In [151]: hist(np.random.randn(10000), 100);
782
780
783
781
784 @savefig test_plot.png width=4in
782 @savefig test_plot.png width=4in
785 In [151]: plot(np.random.randn(10000), 'o');
783 In [151]: plot(np.random.randn(10000), 'o');
786 """,
784 """,
787
785
788 r"""
786 r"""
789 # use a semicolon to suppress the output
787 # use a semicolon to suppress the output
790 In [151]: plt.clf()
788 In [151]: plt.clf()
791
789
792 @savefig plot_simple.png width=4in
790 @savefig plot_simple.png width=4in
793 In [151]: plot([1,2,3])
791 In [151]: plot([1,2,3])
794
792
795 @savefig hist_simple.png width=4in
793 @savefig hist_simple.png width=4in
796 In [151]: hist(np.random.randn(10000), 100);
794 In [151]: hist(np.random.randn(10000), 100);
797
795
798 """,
796 """,
799 r"""
797 r"""
800 # update the current fig
798 # update the current fig
801 In [151]: ylabel('number')
799 In [151]: ylabel('number')
802
800
803 In [152]: title('normal distribution')
801 In [152]: title('normal distribution')
804
802
805
803
806 @savefig hist_with_text.png
804 @savefig hist_with_text.png
807 In [153]: grid(True)
805 In [153]: grid(True)
808
806
809 """,
807 """,
810 ]
808 ]
811 # skip local-file depending first example:
809 # skip local-file depending first example:
812 examples = examples[1:]
810 examples = examples[1:]
813
811
814 #ipython_directive.DEBUG = True # dbg
812 #ipython_directive.DEBUG = True # dbg
815 #options = dict(suppress=True) # dbg
813 #options = dict(suppress=True) # dbg
816 options = dict()
814 options = dict()
817 for example in examples:
815 for example in examples:
818 content = example.split('\n')
816 content = example.split('\n')
819 ipython_directive('debug', arguments=None, options=options,
817 ipython_directive('debug', arguments=None, options=options,
820 content=content, lineno=0,
818 content=content, lineno=0,
821 content_offset=None, block_text=None,
819 content_offset=None, block_text=None,
822 state=None, state_machine=None,
820 state=None, state_machine=None,
823 )
821 )
824
822
825 # Run test suite as a script
823 # Run test suite as a script
826 if __name__=='__main__':
824 if __name__=='__main__':
827 if not os.path.isdir('_static'):
825 if not os.path.isdir('_static'):
828 os.mkdir('_static')
826 os.mkdir('_static')
829 test()
827 test()
830 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