##// END OF EJS Templates
Fixing InteractiveShell creation
Brian Granger -
Show More
@@ -1,41 +1,47 b''
1 """Minimal script to reproduce our nasty reference counting bug.
1 """Minimal script to reproduce our nasty reference counting bug.
2
2
3 The problem is related to https://bugs.launchpad.net/ipython/+bug/269966
3 The problem is related to https://bugs.launchpad.net/ipython/+bug/269966
4
4
5 The original fix for that appeared to work, but John D. Hunter found a
5 The original fix for that appeared to work, but John D. Hunter found a
6 matplotlib example which, when run twice in a row, would break. The problem
6 matplotlib example which, when run twice in a row, would break. The problem
7 were references held by open figures to internals of Tkinter.
7 were references held by open figures to internals of Tkinter.
8
8
9 This code reproduces the problem that John saw, without matplotlib.
9 This code reproduces the problem that John saw, without matplotlib.
10
10
11 This script is meant to be called by other parts of the test suite that call it
11 This script is meant to be called by other parts of the test suite that call it
12 via %run as if it were executed interactively by the user. As of 2009-04-13,
12 via %run as if it were executed interactively by the user. As of 2009-04-13,
13 test_magic.py calls it.
13 test_magic.py calls it.
14 """
14 """
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Module imports
17 # Module imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 import sys
19 import sys
20
20
21 from IPython.core import ipapi
21 from IPython.core import ipapi
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Globals
24 # Globals
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 ip = ipapi.get()
27
26
28 if not '_refbug_cache' in ip.user_ns:
27 # This needs to be here because nose and other test runners will import
29 ip.user_ns['_refbug_cache'] = []
28 # this module. Importing this module has potential side effects that we
29 # want to prevent.
30 if __name__ == '__main__':
30
31
32 ip = ipapi.get()
31
33
32 aglobal = 'Hello'
34 if not '_refbug_cache' in ip.user_ns:
33 def f():
35 ip.user_ns['_refbug_cache'] = []
34 return aglobal
35
36
36 cache = ip.user_ns['_refbug_cache']
37 cache.append(f)
38
37
39 def call_f():
38 aglobal = 'Hello'
40 for func in cache:
39 def f():
41 print 'lowercased:',func().lower()
40 return aglobal
41
42 cache = ip.user_ns['_refbug_cache']
43 cache.append(f)
44
45 def call_f():
46 for func in cache:
47 print 'lowercased:',func().lower()
@@ -1,635 +1,635 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.
6 all prompts renumbered sequentially.
7
7
8 To enable this directive, simply list it in your Sphinx ``conf.py`` file
8 To enable this directive, simply list it in your Sphinx ``conf.py`` file
9 (making sure the directory where you placed it is visible to sphinx, as is
9 (making sure the directory where you placed it is visible to sphinx, as is
10 needed for all Sphinx directives).
10 needed for all Sphinx directives).
11
11
12 By default this directive assumes that your prompts are unchanged IPython ones,
12 By default this directive assumes that your prompts are unchanged IPython ones,
13 but this can be customized. For example, the following code in your Sphinx
13 but this can be customized. For example, the following code in your Sphinx
14 config file will configure this directive for the following input/output
14 config file will configure this directive for the following input/output
15 prompts ``Yade [1]:`` and ``-> [1]:``::
15 prompts ``Yade [1]:`` and ``-> [1]:``::
16
16
17 import ipython_directive as id
17 import ipython_directive as id
18 id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*')
18 id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*')
19 id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*')
19 id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*')
20 id.fmtin ='Yade [%d]:'
20 id.fmtin ='Yade [%d]:'
21 id.fmtout=' -> [%d]:'
21 id.fmtout=' -> [%d]:'
22
22
23 from IPython import Config
23 from IPython import Config
24 id.CONFIG = Config(
24 id.CONFIG = Config(
25 prompt_in1="Yade [\#]:",
25 prompt_in1="Yade [\#]:",
26 prompt_in2=" .\D..",
26 prompt_in2=" .\D..",
27 prompt_out=" -> [\#]:"
27 prompt_out=" -> [\#]:"
28 )
28 )
29 id.reconfig_shell()
29 id.reconfig_shell()
30
30
31 import ipython_console_highlighting as ich
31 import ipython_console_highlighting as ich
32 ich.IPythonConsoleLexer.input_prompt=
32 ich.IPythonConsoleLexer.input_prompt=
33 re.compile("(Yade \[[0-9]+\]: )|( \.\.\.+:)")
33 re.compile("(Yade \[[0-9]+\]: )|( \.\.\.+:)")
34 ich.IPythonConsoleLexer.output_prompt=
34 ich.IPythonConsoleLexer.output_prompt=
35 re.compile("(( -> )|(Out)\[[0-9]+\]: )|( \.\.\.+:)")
35 re.compile("(( -> )|(Out)\[[0-9]+\]: )|( \.\.\.+:)")
36 ich.IPythonConsoleLexer.continue_prompt=re.compile(" \.\.\.+:")
36 ich.IPythonConsoleLexer.continue_prompt=re.compile(" \.\.\.+:")
37
37
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 - Make sure %bookmarks used internally are removed on exit.
45 - Make sure %bookmarks used internally are removed on exit.
46
46
47
47
48 Authors
48 Authors
49 -------
49 -------
50
50
51 - John D Hunter: orignal author.
51 - John D Hunter: orignal author.
52 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
52 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
53 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
53 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
54 """
54 """
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Imports
57 # Imports
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 # Stdlib
60 # Stdlib
61 import cStringIO
61 import cStringIO
62 import os
62 import os
63 import re
63 import re
64 import sys
64 import sys
65
65
66 # To keep compatibility with various python versions
66 # To keep compatibility with various python versions
67 try:
67 try:
68 from hashlib import md5
68 from hashlib import md5
69 except ImportError:
69 except ImportError:
70 from md5 import md5
70 from md5 import md5
71
71
72 # Third-party
72 # Third-party
73 import matplotlib
73 import matplotlib
74 import sphinx
74 import sphinx
75 from docutils.parsers.rst import directives
75 from docutils.parsers.rst import directives
76
76
77 matplotlib.use('Agg')
77 matplotlib.use('Agg')
78
78
79 # Our own
79 # Our own
80 from IPython import Config, InteractiveShell
80 from IPython import Config, InteractiveShell
81 from IPython.utils.io import Term
81 from IPython.utils.io import Term
82
82
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84 # Globals
84 # Globals
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86
86
87 sphinx_version = sphinx.__version__.split(".")
87 sphinx_version = sphinx.__version__.split(".")
88 # The split is necessary for sphinx beta versions where the string is
88 # The split is necessary for sphinx beta versions where the string is
89 # '6b1'
89 # '6b1'
90 sphinx_version = tuple([int(re.split('[a-z]', x)[0])
90 sphinx_version = tuple([int(re.split('[a-z]', x)[0])
91 for x in sphinx_version[:2]])
91 for x in sphinx_version[:2]])
92
92
93 COMMENT, INPUT, OUTPUT = range(3)
93 COMMENT, INPUT, OUTPUT = range(3)
94 CONFIG = Config()
94 CONFIG = Config()
95 rgxin = re.compile('In \[(\d+)\]:\s?(.*)\s*')
95 rgxin = re.compile('In \[(\d+)\]:\s?(.*)\s*')
96 rgxout = re.compile('Out\[(\d+)\]:\s?(.*)\s*')
96 rgxout = re.compile('Out\[(\d+)\]:\s?(.*)\s*')
97 fmtin = 'In [%d]:'
97 fmtin = 'In [%d]:'
98 fmtout = 'Out[%d]:'
98 fmtout = 'Out[%d]:'
99
99
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101 # Functions and class declarations
101 # Functions and class declarations
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103 def block_parser(part):
103 def block_parser(part):
104 """
104 """
105 part is a string of ipython text, comprised of at most one
105 part is a string of ipython text, comprised of at most one
106 input, one ouput, comments, and blank lines. The block parser
106 input, one ouput, comments, and blank lines. The block parser
107 parses the text into a list of::
107 parses the text into a list of::
108
108
109 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
109 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
110
110
111 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
111 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
112 data is, depending on the type of token::
112 data is, depending on the type of token::
113
113
114 COMMENT : the comment string
114 COMMENT : the comment string
115
115
116 INPUT: the (DECORATOR, INPUT_LINE, REST) where
116 INPUT: the (DECORATOR, INPUT_LINE, REST) where
117 DECORATOR: the input decorator (or None)
117 DECORATOR: the input decorator (or None)
118 INPUT_LINE: the input as string (possibly multi-line)
118 INPUT_LINE: the input as string (possibly multi-line)
119 REST : any stdout generated by the input line (not OUTPUT)
119 REST : any stdout generated by the input line (not OUTPUT)
120
120
121
121
122 OUTPUT: the output string, possibly multi-line
122 OUTPUT: the output string, possibly multi-line
123 """
123 """
124
124
125 block = []
125 block = []
126 lines = part.split('\n')
126 lines = part.split('\n')
127 N = len(lines)
127 N = len(lines)
128 i = 0
128 i = 0
129 decorator = None
129 decorator = None
130 while 1:
130 while 1:
131
131
132 if i==N:
132 if i==N:
133 # nothing left to parse -- the last line
133 # nothing left to parse -- the last line
134 break
134 break
135
135
136 line = lines[i]
136 line = lines[i]
137 i += 1
137 i += 1
138 line_stripped = line.strip()
138 line_stripped = line.strip()
139 if line_stripped.startswith('#'):
139 if line_stripped.startswith('#'):
140 block.append((COMMENT, line))
140 block.append((COMMENT, line))
141 continue
141 continue
142
142
143 if line_stripped.startswith('@'):
143 if line_stripped.startswith('@'):
144 # we're assuming at most one decorator -- may need to
144 # we're assuming at most one decorator -- may need to
145 # rethink
145 # rethink
146 decorator = line_stripped
146 decorator = line_stripped
147 continue
147 continue
148
148
149 # does this look like an input line?
149 # does this look like an input line?
150 matchin = rgxin.match(line)
150 matchin = rgxin.match(line)
151 if matchin:
151 if matchin:
152 lineno, inputline = int(matchin.group(1)), matchin.group(2)
152 lineno, inputline = int(matchin.group(1)), matchin.group(2)
153
153
154 # the ....: continuation string
154 # the ....: continuation string
155 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
155 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
156 Nc = len(continuation)
156 Nc = len(continuation)
157 # input lines can continue on for more than one line, if
157 # input lines can continue on for more than one line, if
158 # we have a '\' line continuation char or a function call
158 # we have a '\' line continuation char or a function call
159 # echo line 'print'. The input line can only be
159 # echo line 'print'. The input line can only be
160 # terminated by the end of the block or an output line, so
160 # terminated by the end of the block or an output line, so
161 # we parse out the rest of the input line if it is
161 # we parse out the rest of the input line if it is
162 # multiline as well as any echo text
162 # multiline as well as any echo text
163
163
164 rest = []
164 rest = []
165 while i<N:
165 while i<N:
166
166
167 # look ahead; if the next line is blank, or a comment, or
167 # look ahead; if the next line is blank, or a comment, or
168 # an output line, we're done
168 # an output line, we're done
169
169
170 nextline = lines[i]
170 nextline = lines[i]
171 matchout = rgxout.match(nextline)
171 matchout = rgxout.match(nextline)
172 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
172 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
173 if matchout or nextline.startswith('#'):
173 if matchout or nextline.startswith('#'):
174 break
174 break
175 elif nextline.startswith(continuation):
175 elif nextline.startswith(continuation):
176 inputline += '\n' + nextline[Nc:]
176 inputline += '\n' + nextline[Nc:]
177 else:
177 else:
178 rest.append(nextline)
178 rest.append(nextline)
179 i+= 1
179 i+= 1
180
180
181 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
181 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
182 continue
182 continue
183
183
184 # if it looks like an output line grab all the text to the end
184 # if it looks like an output line grab all the text to the end
185 # of the block
185 # of the block
186 matchout = rgxout.match(line)
186 matchout = rgxout.match(line)
187 if matchout:
187 if matchout:
188 lineno, output = int(matchout.group(1)), matchout.group(2)
188 lineno, output = int(matchout.group(1)), matchout.group(2)
189 if i<N-1:
189 if i<N-1:
190 output = '\n'.join([output] + lines[i:])
190 output = '\n'.join([output] + lines[i:])
191
191
192 block.append((OUTPUT, output))
192 block.append((OUTPUT, output))
193 break
193 break
194
194
195 return block
195 return block
196
196
197
197
198 class EmbeddedSphinxShell(object):
198 class EmbeddedSphinxShell(object):
199 """An embedded IPython instance to run inside Sphinx"""
199 """An embedded IPython instance to run inside Sphinx"""
200
200
201 def __init__(self):
201 def __init__(self):
202
202
203 self.cout = cStringIO.StringIO()
203 self.cout = cStringIO.StringIO()
204 Term.cout = self.cout
204 Term.cout = self.cout
205 Term.cerr = self.cout
205 Term.cerr = self.cout
206
206
207 # For debugging, so we can see normal output, use this:
207 # For debugging, so we can see normal output, use this:
208 # from IPython.utils.io import Tee
208 # from IPython.utils.io import Tee
209 #Term.cout = Tee(self.cout, channel='stdout') # dbg
209 #Term.cout = Tee(self.cout, channel='stdout') # dbg
210 #Term.cerr = Tee(self.cout, channel='stderr') # dbg
210 #Term.cerr = Tee(self.cout, channel='stderr') # dbg
211
211
212 # Create config object for IPython
212 # Create config object for IPython
213 config = Config()
213 config = Config()
214 config.Global.display_banner = False
214 config.Global.display_banner = False
215 config.Global.exec_lines = ['import numpy as np',
215 config.Global.exec_lines = ['import numpy as np',
216 'from pylab import *'
216 'from pylab import *'
217 ]
217 ]
218 config.InteractiveShell.autocall = False
218 config.InteractiveShell.autocall = False
219 config.InteractiveShell.autoindent = False
219 config.InteractiveShell.autoindent = False
220 config.InteractiveShell.colors = 'NoColor'
220 config.InteractiveShell.colors = 'NoColor'
221
221
222 # Create and initialize ipython, but don't start its mainloop
222 # Create and initialize ipython, but don't start its mainloop
223 IP = InteractiveShell(parent=None, config=config)
223 IP = InteractiveShell.instance(config=config)
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 # we need bookmark the current dir first so we can save
241 # we need bookmark the current dir first so we can save
242 # relative to it
242 # relative to it
243 self.process_input_line('bookmark ipy_basedir')
243 self.process_input_line('bookmark ipy_basedir')
244 self.cout.seek(0)
244 self.cout.seek(0)
245 self.cout.truncate(0)
245 self.cout.truncate(0)
246
246
247 def process_input_line(self, line):
247 def process_input_line(self, line):
248 """process the input, capturing stdout"""
248 """process the input, capturing stdout"""
249 #print "input='%s'"%self.input
249 #print "input='%s'"%self.input
250 stdout = sys.stdout
250 stdout = sys.stdout
251 try:
251 try:
252 sys.stdout = self.cout
252 sys.stdout = self.cout
253 self.IP.push_line(line)
253 self.IP.push_line(line)
254 finally:
254 finally:
255 sys.stdout = stdout
255 sys.stdout = stdout
256
256
257 # Callbacks for each type of token
257 # Callbacks for each type of token
258 def process_input(self, data, input_prompt, lineno):
258 def process_input(self, data, input_prompt, lineno):
259 """Process data block for INPUT token."""
259 """Process data block for INPUT token."""
260 decorator, input, rest = data
260 decorator, input, rest = data
261 image_file = None
261 image_file = None
262 #print 'INPUT:', data # dbg
262 #print 'INPUT:', data # dbg
263 is_verbatim = decorator=='@verbatim' or self.is_verbatim
263 is_verbatim = decorator=='@verbatim' or self.is_verbatim
264 is_doctest = decorator=='@doctest' or self.is_doctest
264 is_doctest = decorator=='@doctest' or self.is_doctest
265 is_suppress = decorator=='@suppress' or self.is_suppress
265 is_suppress = decorator=='@suppress' or self.is_suppress
266 is_savefig = decorator is not None and \
266 is_savefig = decorator is not None and \
267 decorator.startswith('@savefig')
267 decorator.startswith('@savefig')
268
268
269 input_lines = input.split('\n')
269 input_lines = input.split('\n')
270
270
271 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
271 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
272 Nc = len(continuation)
272 Nc = len(continuation)
273
273
274 if is_savefig:
274 if is_savefig:
275 saveargs = decorator.split(' ')
275 saveargs = decorator.split(' ')
276 filename = saveargs[1]
276 filename = saveargs[1]
277 outfile = os.path.join('_static/%s'%filename)
277 outfile = os.path.join('_static/%s'%filename)
278 # build out an image directive like
278 # build out an image directive like
279 # .. image:: somefile.png
279 # .. image:: somefile.png
280 # :width 4in
280 # :width 4in
281 #
281 #
282 # from an input like
282 # from an input like
283 # savefig somefile.png width=4in
283 # savefig somefile.png width=4in
284 imagerows = ['.. image:: %s'%outfile]
284 imagerows = ['.. image:: %s'%outfile]
285
285
286 for kwarg in saveargs[2:]:
286 for kwarg in saveargs[2:]:
287 arg, val = kwarg.split('=')
287 arg, val = kwarg.split('=')
288 arg = arg.strip()
288 arg = arg.strip()
289 val = val.strip()
289 val = val.strip()
290 imagerows.append(' :%s: %s'%(arg, val))
290 imagerows.append(' :%s: %s'%(arg, val))
291
291
292 image_file = outfile
292 image_file = outfile
293 image_directive = '\n'.join(imagerows)
293 image_directive = '\n'.join(imagerows)
294
294
295 # TODO: can we get "rest" from ipython
295 # TODO: can we get "rest" from ipython
296 #self.process_input_line('\n'.join(input_lines))
296 #self.process_input_line('\n'.join(input_lines))
297
297
298 ret = []
298 ret = []
299 is_semicolon = False
299 is_semicolon = False
300
300
301 for i, line in enumerate(input_lines):
301 for i, line in enumerate(input_lines):
302 if line.endswith(';'):
302 if line.endswith(';'):
303 is_semicolon = True
303 is_semicolon = True
304
304
305 if i==0:
305 if i==0:
306 # process the first input line
306 # process the first input line
307 if is_verbatim:
307 if is_verbatim:
308 self.process_input_line('')
308 self.process_input_line('')
309 else:
309 else:
310 # only submit the line in non-verbatim mode
310 # only submit the line in non-verbatim mode
311 self.process_input_line(line)
311 self.process_input_line(line)
312 formatted_line = '%s %s'%(input_prompt, line)
312 formatted_line = '%s %s'%(input_prompt, line)
313 else:
313 else:
314 # process a continuation line
314 # process a continuation line
315 if not is_verbatim:
315 if not is_verbatim:
316 self.process_input_line(line)
316 self.process_input_line(line)
317
317
318 formatted_line = '%s %s'%(continuation, line)
318 formatted_line = '%s %s'%(continuation, line)
319
319
320 if not is_suppress:
320 if not is_suppress:
321 ret.append(formatted_line)
321 ret.append(formatted_line)
322
322
323 if not is_suppress:
323 if not is_suppress:
324 if len(rest.strip()):
324 if len(rest.strip()):
325 if is_verbatim:
325 if is_verbatim:
326 # the "rest" is the standard output of the
326 # the "rest" is the standard output of the
327 # input, which needs to be added in
327 # input, which needs to be added in
328 # verbatim mode
328 # verbatim mode
329 ret.append(rest)
329 ret.append(rest)
330
330
331 self.cout.seek(0)
331 self.cout.seek(0)
332 output = self.cout.read()
332 output = self.cout.read()
333 if not is_suppress and not is_semicolon:
333 if not is_suppress and not is_semicolon:
334 ret.append(output)
334 ret.append(output)
335
335
336 self.cout.truncate(0)
336 self.cout.truncate(0)
337 return ret, input_lines, output, is_doctest, image_file
337 return ret, input_lines, output, is_doctest, image_file
338 #print 'OUTPUT', output # dbg
338 #print 'OUTPUT', output # dbg
339
339
340 def process_output(self, data, output_prompt,
340 def process_output(self, data, output_prompt,
341 input_lines, output, is_doctest, image_file):
341 input_lines, output, is_doctest, image_file):
342 """Process data block for OUTPUT token."""
342 """Process data block for OUTPUT token."""
343 if is_doctest:
343 if is_doctest:
344 submitted = data.strip()
344 submitted = data.strip()
345 found = output
345 found = output
346 if found is not None:
346 if found is not None:
347 found = found.strip()
347 found = found.strip()
348
348
349 # XXX - fperez: in 0.11, 'output' never comes with the prompt
349 # XXX - fperez: in 0.11, 'output' never comes with the prompt
350 # in it, just the actual output text. So I think all this code
350 # in it, just the actual output text. So I think all this code
351 # can be nuked...
351 # can be nuked...
352 ## ind = found.find(output_prompt)
352 ## ind = found.find(output_prompt)
353 ## if ind<0:
353 ## if ind<0:
354 ## e='output prompt="%s" does not match out line=%s' % \
354 ## e='output prompt="%s" does not match out line=%s' % \
355 ## (output_prompt, found)
355 ## (output_prompt, found)
356 ## raise RuntimeError(e)
356 ## raise RuntimeError(e)
357 ## found = found[len(output_prompt):].strip()
357 ## found = found[len(output_prompt):].strip()
358
358
359 if found!=submitted:
359 if found!=submitted:
360 e = ('doctest failure for input_lines="%s" with '
360 e = ('doctest failure for input_lines="%s" with '
361 'found_output="%s" and submitted output="%s"' %
361 'found_output="%s" and submitted output="%s"' %
362 (input_lines, found, submitted) )
362 (input_lines, found, submitted) )
363 raise RuntimeError(e)
363 raise RuntimeError(e)
364 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
364 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
365
365
366 def process_comment(self, data):
366 def process_comment(self, data):
367 """Process data block for COMMENT token."""
367 """Process data block for COMMENT token."""
368 if not self.is_suppress:
368 if not self.is_suppress:
369 return [data]
369 return [data]
370
370
371 def process_block(self, block):
371 def process_block(self, block):
372 """
372 """
373 process block from the block_parser and return a list of processed lines
373 process block from the block_parser and return a list of processed lines
374 """
374 """
375
375
376 ret = []
376 ret = []
377 output = None
377 output = None
378 input_lines = None
378 input_lines = None
379
379
380 m = rgxin.match(str(self.IP.outputcache.prompt1).strip())
380 m = rgxin.match(str(self.IP.outputcache.prompt1).strip())
381 lineno = int(m.group(1))
381 lineno = int(m.group(1))
382
382
383 input_prompt = fmtin%lineno
383 input_prompt = fmtin%lineno
384 output_prompt = fmtout%lineno
384 output_prompt = fmtout%lineno
385 image_file = None
385 image_file = None
386 image_directive = None
386 image_directive = None
387 # XXX - This needs a second refactor. There's too much state being
387 # XXX - This needs a second refactor. There's too much state being
388 # held globally, which makes for a very awkward interface and large,
388 # held globally, which makes for a very awkward interface and large,
389 # hard to test functions. I've already broken this up at least into
389 # hard to test functions. I've already broken this up at least into
390 # three separate processors to isolate the logic better, but this only
390 # three separate processors to isolate the logic better, but this only
391 # serves to highlight the coupling. Next we need to clean it up...
391 # serves to highlight the coupling. Next we need to clean it up...
392 for token, data in block:
392 for token, data in block:
393 if token==COMMENT:
393 if token==COMMENT:
394 out_data = self.process_comment(data)
394 out_data = self.process_comment(data)
395 elif token==INPUT:
395 elif token==INPUT:
396 out_data, input_lines, output, is_doctest, image_file= \
396 out_data, input_lines, output, is_doctest, image_file= \
397 self.process_input(data, input_prompt, lineno)
397 self.process_input(data, input_prompt, lineno)
398 elif token==OUTPUT:
398 elif token==OUTPUT:
399 out_data = \
399 out_data = \
400 self.process_output(data, output_prompt,
400 self.process_output(data, output_prompt,
401 input_lines, output, is_doctest,
401 input_lines, output, is_doctest,
402 image_file)
402 image_file)
403 if out_data:
403 if out_data:
404 ret.extend(out_data)
404 ret.extend(out_data)
405
405
406 if image_file is not None:
406 if image_file is not None:
407 self.ensure_pyplot()
407 self.ensure_pyplot()
408 command = 'plt.gcf().savefig("%s")'%image_file
408 command = 'plt.gcf().savefig("%s")'%image_file
409 print 'SAVEFIG', command # dbg
409 print 'SAVEFIG', command # dbg
410 self.process_input_line('bookmark ipy_thisdir')
410 self.process_input_line('bookmark ipy_thisdir')
411 self.process_input_line('cd -b ipy_basedir')
411 self.process_input_line('cd -b ipy_basedir')
412 self.process_input_line(command)
412 self.process_input_line(command)
413 self.process_input_line('cd -b ipy_thisdir')
413 self.process_input_line('cd -b ipy_thisdir')
414 self.cout.seek(0)
414 self.cout.seek(0)
415 self.cout.truncate(0)
415 self.cout.truncate(0)
416 return ret, image_directive
416 return ret, image_directive
417
417
418 def ensure_pyplot(self):
418 def ensure_pyplot(self):
419 if self._pyplot_imported:
419 if self._pyplot_imported:
420 return
420 return
421 self.process_input_line('import matplotlib.pyplot as plt')
421 self.process_input_line('import matplotlib.pyplot as plt')
422
422
423 # A global instance used below. XXX: not sure why this can't be created inside
423 # A global instance used below. XXX: not sure why this can't be created inside
424 # ipython_directive itself.
424 # ipython_directive itself.
425 shell = EmbeddedSphinxShell()
425 shell = EmbeddedSphinxShell()
426
426
427 def reconfig_shell():
427 def reconfig_shell():
428 """Called after setting module-level variables to re-instantiate
428 """Called after setting module-level variables to re-instantiate
429 with the set values (since shell is instantiated first at import-time
429 with the set values (since shell is instantiated first at import-time
430 when module variables have default values)"""
430 when module variables have default values)"""
431 global shell
431 global shell
432 shell = EmbeddedSphinxShell()
432 shell = EmbeddedSphinxShell()
433
433
434
434
435 def ipython_directive(name, arguments, options, content, lineno,
435 def ipython_directive(name, arguments, options, content, lineno,
436 content_offset, block_text, state, state_machine,
436 content_offset, block_text, state, state_machine,
437 ):
437 ):
438
438
439 debug = ipython_directive.DEBUG
439 debug = ipython_directive.DEBUG
440 shell.is_suppress = options.has_key('suppress')
440 shell.is_suppress = options.has_key('suppress')
441 shell.is_doctest = options.has_key('doctest')
441 shell.is_doctest = options.has_key('doctest')
442 shell.is_verbatim = options.has_key('verbatim')
442 shell.is_verbatim = options.has_key('verbatim')
443
443
444 #print 'ipy', shell.is_suppress, options
444 #print 'ipy', shell.is_suppress, options
445 parts = '\n'.join(content).split('\n\n')
445 parts = '\n'.join(content).split('\n\n')
446 lines = ['.. sourcecode:: ipython', '']
446 lines = ['.. sourcecode:: ipython', '']
447
447
448 figures = []
448 figures = []
449 for part in parts:
449 for part in parts:
450 block = block_parser(part)
450 block = block_parser(part)
451
451
452 if len(block):
452 if len(block):
453 rows, figure = shell.process_block(block)
453 rows, figure = shell.process_block(block)
454 for row in rows:
454 for row in rows:
455 lines.extend([' %s'%line for line in row.split('\n')])
455 lines.extend([' %s'%line for line in row.split('\n')])
456
456
457 if figure is not None:
457 if figure is not None:
458 figures.append(figure)
458 figures.append(figure)
459
459
460 for figure in figures:
460 for figure in figures:
461 lines.append('')
461 lines.append('')
462 lines.extend(figure.split('\n'))
462 lines.extend(figure.split('\n'))
463 lines.append('')
463 lines.append('')
464
464
465 #print lines
465 #print lines
466 if len(lines)>2:
466 if len(lines)>2:
467 if debug:
467 if debug:
468 print '\n'.join(lines)
468 print '\n'.join(lines)
469 else:
469 else:
470 #print 'INSERTING %d lines'%len(lines)
470 #print 'INSERTING %d lines'%len(lines)
471 state_machine.insert_input(
471 state_machine.insert_input(
472 lines, state_machine.input_lines.source(0))
472 lines, state_machine.input_lines.source(0))
473
473
474 return []
474 return []
475
475
476 ipython_directive.DEBUG = False
476 ipython_directive.DEBUG = False
477 ipython_directive.DEBUG = True # dbg
477 ipython_directive.DEBUG = True # dbg
478
478
479 # Enable as a proper Sphinx directive
479 # Enable as a proper Sphinx directive
480 def setup(app):
480 def setup(app):
481 setup.app = app
481 setup.app = app
482 options = {'suppress': directives.flag,
482 options = {'suppress': directives.flag,
483 'doctest': directives.flag,
483 'doctest': directives.flag,
484 'verbatim': directives.flag,
484 'verbatim': directives.flag,
485 }
485 }
486
486
487 app.add_directive('ipython', ipython_directive, True, (0, 2, 0), **options)
487 app.add_directive('ipython', ipython_directive, True, (0, 2, 0), **options)
488
488
489
489
490 # Simple smoke test, needs to be converted to a proper automatic test.
490 # Simple smoke test, needs to be converted to a proper automatic test.
491 def test():
491 def test():
492
492
493 examples = [
493 examples = [
494 r"""
494 r"""
495 In [9]: pwd
495 In [9]: pwd
496 Out[9]: '/home/jdhunter/py4science/book'
496 Out[9]: '/home/jdhunter/py4science/book'
497
497
498 In [10]: cd bookdata/
498 In [10]: cd bookdata/
499 /home/jdhunter/py4science/book/bookdata
499 /home/jdhunter/py4science/book/bookdata
500
500
501 In [2]: from pylab import *
501 In [2]: from pylab import *
502
502
503 In [2]: ion()
503 In [2]: ion()
504
504
505 In [3]: im = imread('stinkbug.png')
505 In [3]: im = imread('stinkbug.png')
506
506
507 @savefig mystinkbug.png width=4in
507 @savefig mystinkbug.png width=4in
508 In [4]: imshow(im)
508 In [4]: imshow(im)
509 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
509 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
510
510
511 """,
511 """,
512 r"""
512 r"""
513
513
514 In [1]: x = 'hello world'
514 In [1]: x = 'hello world'
515
515
516 # string methods can be
516 # string methods can be
517 # used to alter the string
517 # used to alter the string
518 @doctest
518 @doctest
519 In [2]: x.upper()
519 In [2]: x.upper()
520 Out[2]: 'HELLO WORLD'
520 Out[2]: 'HELLO WORLD'
521
521
522 @verbatim
522 @verbatim
523 In [3]: x.st<TAB>
523 In [3]: x.st<TAB>
524 x.startswith x.strip
524 x.startswith x.strip
525 """,
525 """,
526 r"""
526 r"""
527
527
528 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
528 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
529 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
529 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
530
530
531 In [131]: print url.split('&')
531 In [131]: print url.split('&')
532 ['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']
532 ['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']
533
533
534 In [60]: import urllib
534 In [60]: import urllib
535
535
536 """,
536 """,
537 r"""\
537 r"""\
538
538
539 In [133]: import numpy.random
539 In [133]: import numpy.random
540
540
541 @suppress
541 @suppress
542 In [134]: numpy.random.seed(2358)
542 In [134]: numpy.random.seed(2358)
543
543
544 @doctest
544 @doctest
545 In [135]: np.random.rand(10,2)
545 In [135]: np.random.rand(10,2)
546 Out[135]:
546 Out[135]:
547 array([[ 0.64524308, 0.59943846],
547 array([[ 0.64524308, 0.59943846],
548 [ 0.47102322, 0.8715456 ],
548 [ 0.47102322, 0.8715456 ],
549 [ 0.29370834, 0.74776844],
549 [ 0.29370834, 0.74776844],
550 [ 0.99539577, 0.1313423 ],
550 [ 0.99539577, 0.1313423 ],
551 [ 0.16250302, 0.21103583],
551 [ 0.16250302, 0.21103583],
552 [ 0.81626524, 0.1312433 ],
552 [ 0.81626524, 0.1312433 ],
553 [ 0.67338089, 0.72302393],
553 [ 0.67338089, 0.72302393],
554 [ 0.7566368 , 0.07033696],
554 [ 0.7566368 , 0.07033696],
555 [ 0.22591016, 0.77731835],
555 [ 0.22591016, 0.77731835],
556 [ 0.0072729 , 0.34273127]])
556 [ 0.0072729 , 0.34273127]])
557
557
558 """,
558 """,
559
559
560 r"""
560 r"""
561 In [106]: print x
561 In [106]: print x
562 jdh
562 jdh
563
563
564 In [109]: for i in range(10):
564 In [109]: for i in range(10):
565 .....: print i
565 .....: print i
566 .....:
566 .....:
567 .....:
567 .....:
568 0
568 0
569 1
569 1
570 2
570 2
571 3
571 3
572 4
572 4
573 5
573 5
574 6
574 6
575 7
575 7
576 8
576 8
577 9
577 9
578 """,
578 """,
579
579
580 r"""
580 r"""
581
581
582 In [144]: from pylab import *
582 In [144]: from pylab import *
583
583
584 In [145]: ion()
584 In [145]: ion()
585
585
586 # use a semicolon to suppress the output
586 # use a semicolon to suppress the output
587 @savefig test_hist.png width=4in
587 @savefig test_hist.png width=4in
588 In [151]: hist(np.random.randn(10000), 100);
588 In [151]: hist(np.random.randn(10000), 100);
589
589
590
590
591 @savefig test_plot.png width=4in
591 @savefig test_plot.png width=4in
592 In [151]: plot(np.random.randn(10000), 'o');
592 In [151]: plot(np.random.randn(10000), 'o');
593 """,
593 """,
594
594
595 r"""
595 r"""
596 # use a semicolon to suppress the output
596 # use a semicolon to suppress the output
597 In [151]: plt.clf()
597 In [151]: plt.clf()
598
598
599 @savefig plot_simple.png width=4in
599 @savefig plot_simple.png width=4in
600 In [151]: plot([1,2,3])
600 In [151]: plot([1,2,3])
601
601
602 @savefig hist_simple.png width=4in
602 @savefig hist_simple.png width=4in
603 In [151]: hist(np.random.randn(10000), 100);
603 In [151]: hist(np.random.randn(10000), 100);
604
604
605 """,
605 """,
606 r"""
606 r"""
607 # update the current fig
607 # update the current fig
608 In [151]: ylabel('number')
608 In [151]: ylabel('number')
609
609
610 In [152]: title('normal distribution')
610 In [152]: title('normal distribution')
611
611
612
612
613 @savefig hist_with_text.png
613 @savefig hist_with_text.png
614 In [153]: grid(True)
614 In [153]: grid(True)
615
615
616 """,
616 """,
617 ]
617 ]
618
618
619 #ipython_directive.DEBUG = True # dbg
619 #ipython_directive.DEBUG = True # dbg
620 #options = dict(suppress=True) # dbg
620 #options = dict(suppress=True) # dbg
621 options = dict()
621 options = dict()
622 for example in examples:
622 for example in examples:
623 content = example.split('\n')
623 content = example.split('\n')
624 ipython_directive('debug', arguments=None, options=options,
624 ipython_directive('debug', arguments=None, options=options,
625 content=content, lineno=0,
625 content=content, lineno=0,
626 content_offset=None, block_text=None,
626 content_offset=None, block_text=None,
627 state=None, state_machine=None,
627 state=None, state_machine=None,
628 )
628 )
629
629
630 # Run test suite as a script
630 # Run test suite as a script
631 if __name__=='__main__':
631 if __name__=='__main__':
632 if not os.path.isdir('_static'):
632 if not os.path.isdir('_static'):
633 os.mkdir('_static')
633 os.mkdir('_static')
634 test()
634 test()
635 print 'All OK? Check figures in _static/'
635 print 'All OK? Check figures in _static/'
General Comments 0
You need to be logged in to leave comments. Login now