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