##// END OF EJS Templates
Updated Sphinx extension ipython_directive to work with 0.11.
Skipper Seabold -
Show More
@@ -1,635 +1,813 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 all prompts renumbered sequentially.
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
8 like an interactive ipython section.
7 9
8 10 To enable this directive, simply list it in your Sphinx ``conf.py`` file
9 11 (making sure the directory where you placed it is visible to sphinx, as is
10 12 needed for all Sphinx directives).
11 13
12 14 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
14 config file will configure this directive for the following input/output
15 prompts ``Yade [1]:`` and ``-> [1]:``::
16
17 import ipython_directive as id
18 id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*')
19 id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*')
20 id.fmtin ='Yade [%d]:'
21 id.fmtout=' -> [%d]:'
22
23 from IPython import Config
24 id.CONFIG = Config(
25 prompt_in1="Yade [\#]:",
26 prompt_in2=" .\D..",
27 prompt_out=" -> [\#]:"
28 )
29 id.reconfig_shell()
30
31 import ipython_console_highlighting as ich
32 ich.IPythonConsoleLexer.input_prompt=
33 re.compile("(Yade \[[0-9]+\]: )|( \.\.\.+:)")
34 ich.IPythonConsoleLexer.output_prompt=
35 re.compile("(( -> )|(Out)\[[0-9]+\]: )|( \.\.\.+:)")
36 ich.IPythonConsoleLexer.continue_prompt=re.compile(" \.\.\.+:")
37
15 but this can be customized. The configurable options that can be placed in
16 conf.py are
17
18 ipython_savefig_dir:
19 The directory in which to save the figures. This is relative to the
20 Sphinx source directory. The default is `html_static_path`.
21 ipython_rgxin:
22 The compiled regular expression to denote the start of IPython input
23 lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You
24 shouldn't need to change this.
25 ipython_rgxout:
26 The compiled regular expression to denote the start of IPython output
27 lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You
28 shouldn't need to change this.
29 ipython_promptin:
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
32 in the prompt.
33 ipython_promptout:
34
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
37 in the prompt.
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 - Make sure %bookmarks used internally are removed on exit.
46
47 45
48 46 Authors
49 47 -------
50 48
51 49 - John D Hunter: orignal author.
52 50 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
53 51 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
52 - Skipper Seabold, refactoring, cleanups, pure python addition
54 53 """
55 54
56 55 #-----------------------------------------------------------------------------
57 56 # Imports
58 57 #-----------------------------------------------------------------------------
59 58
60 59 # Stdlib
61 60 import cStringIO
62 61 import os
63 62 import re
64 63 import sys
64 import tempfile
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 from docutils import nodes
77 from sphinx.util.compat import Directive
76 78
77 79 matplotlib.use('Agg')
78 80
79 81 # Our own
80 82 from IPython import Config, InteractiveShell
81 from IPython.utils.io import Term
83 from IPython.core.profiledir import ProfileDir
84 from IPython.utils import io
82 85
83 86 #-----------------------------------------------------------------------------
84 87 # Globals
85 88 #-----------------------------------------------------------------------------
86
87 sphinx_version = sphinx.__version__.split(".")
88 # The split is necessary for sphinx beta versions where the string is
89 # '6b1'
90 sphinx_version = tuple([int(re.split('[a-z]', x)[0])
91 for x in sphinx_version[:2]])
92
89 # for tokenizing blocks
93 90 COMMENT, INPUT, OUTPUT = range(3)
94 CONFIG = Config()
95 rgxin = re.compile('In \[(\d+)\]:\s?(.*)\s*')
96 rgxout = re.compile('Out\[(\d+)\]:\s?(.*)\s*')
97 fmtin = 'In [%d]:'
98 fmtout = 'Out[%d]:'
99 91
100 92 #-----------------------------------------------------------------------------
101 93 # Functions and class declarations
102 94 #-----------------------------------------------------------------------------
103 def block_parser(part):
95 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
104 96 """
105 97 part is a string of ipython text, comprised of at most one
106 98 input, one ouput, comments, and blank lines. The block parser
107 99 parses the text into a list of::
108 100
109 101 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
110 102
111 103 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
112 104 data is, depending on the type of token::
113 105
114 106 COMMENT : the comment string
115 107
116 108 INPUT: the (DECORATOR, INPUT_LINE, REST) where
117 109 DECORATOR: the input decorator (or None)
118 110 INPUT_LINE: the input as string (possibly multi-line)
119 111 REST : any stdout generated by the input line (not OUTPUT)
120 112
121 113
122 114 OUTPUT: the output string, possibly multi-line
123 115 """
124 116
125 117 block = []
126 118 lines = part.split('\n')
127 119 N = len(lines)
128 120 i = 0
129 121 decorator = None
130 122 while 1:
131 123
132 124 if i==N:
133 125 # nothing left to parse -- the last line
134 126 break
135 127
136 128 line = lines[i]
137 129 i += 1
138 130 line_stripped = line.strip()
139 131 if line_stripped.startswith('#'):
140 132 block.append((COMMENT, line))
141 133 continue
142 134
143 135 if line_stripped.startswith('@'):
144 136 # we're assuming at most one decorator -- may need to
145 137 # rethink
146 138 decorator = line_stripped
147 139 continue
148 140
149 141 # does this look like an input line?
150 142 matchin = rgxin.match(line)
151 143 if matchin:
152 144 lineno, inputline = int(matchin.group(1)), matchin.group(2)
153 145
154 146 # the ....: continuation string
155 147 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
156 148 Nc = len(continuation)
157 149 # input lines can continue on for more than one line, if
158 150 # we have a '\' line continuation char or a function call
159 151 # echo line 'print'. The input line can only be
160 152 # terminated by the end of the block or an output line, so
161 153 # we parse out the rest of the input line if it is
162 154 # multiline as well as any echo text
163 155
164 156 rest = []
165 157 while i<N:
166 158
167 159 # look ahead; if the next line is blank, or a comment, or
168 160 # an output line, we're done
169 161
170 162 nextline = lines[i]
171 163 matchout = rgxout.match(nextline)
172 164 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
173 165 if matchout or nextline.startswith('#'):
174 166 break
175 167 elif nextline.startswith(continuation):
176 168 inputline += '\n' + nextline[Nc:]
177 169 else:
178 170 rest.append(nextline)
179 171 i+= 1
180 172
181 173 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
182 174 continue
183 175
184 176 # if it looks like an output line grab all the text to the end
185 177 # of the block
186 178 matchout = rgxout.match(line)
187 179 if matchout:
188 180 lineno, output = int(matchout.group(1)), matchout.group(2)
189 181 if i<N-1:
190 182 output = '\n'.join([output] + lines[i:])
191 183
192 184 block.append((OUTPUT, output))
193 185 break
194 186
195 187 return block
196 188
197
198 189 class EmbeddedSphinxShell(object):
199 190 """An embedded IPython instance to run inside Sphinx"""
200 191
201 192 def __init__(self):
202 193
203 194 self.cout = cStringIO.StringIO()
204 Term.cout = self.cout
205 Term.cerr = self.cout
206 195
207 # For debugging, so we can see normal output, use this:
208 # from IPython.utils.io import Tee
209 #Term.cout = Tee(self.cout, channel='stdout') # dbg
210 #Term.cerr = Tee(self.cout, channel='stderr') # dbg
211 196
212 197 # Create config object for IPython
213 198 config = Config()
214 199 config.Global.display_banner = False
215 200 config.Global.exec_lines = ['import numpy as np',
216 201 'from pylab import *'
217 202 ]
218 203 config.InteractiveShell.autocall = False
219 204 config.InteractiveShell.autoindent = False
220 205 config.InteractiveShell.colors = 'NoColor'
221 206
207 # create a profile so instance history isn't saved
208 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
209 profname = 'auto_profile_sphinx_build'
210 pdir = os.path.join(tmp_profile_dir,profname)
211 profile = ProfileDir.create_profile_dir(pdir)
212
222 213 # Create and initialize ipython, but don't start its mainloop
223 IP = InteractiveShell.instance(config=config)
214 IP = InteractiveShell.instance(config=config, profile_dir=profile)
215 # io.stdout redirect must be done *after* instantiating InteractiveShell
216 io.stdout = self.cout
217 io.stderr = self.cout
218
219 # For debugging, so we can see normal output, use this:
220 #from IPython.utils.io import Tee
221 #io.stdout = Tee(self.cout, channel='stdout') # dbg
222 #io.stderr = Tee(self.cout, channel='stderr') # dbg
224 223
225 224 # Store a few parts of IPython we'll need.
226 225 self.IP = IP
227 226 self.user_ns = self.IP.user_ns
228 227 self.user_global_ns = self.IP.user_global_ns
229
228
230 229 self.input = ''
231 230 self.output = ''
232 231
233 232 self.is_verbatim = False
234 233 self.is_doctest = False
235 234 self.is_suppress = False
236 235
237 236 # on the first call to the savefig decorator, we'll import
238 237 # pyplot as plt so we can make a call to the plt.gcf().savefig
239 238 self._pyplot_imported = False
240 239
241 # we need bookmark the current dir first so we can save
242 # relative to it
243 self.process_input_line('bookmark ipy_basedir')
240 def clear_cout(self):
244 241 self.cout.seek(0)
245 242 self.cout.truncate(0)
246 243
247 def process_input_line(self, line):
244 def process_input_line(self, line, store_history=True):
248 245 """process the input, capturing stdout"""
249 246 #print "input='%s'"%self.input
250 247 stdout = sys.stdout
248 splitter = self.IP.input_splitter
251 249 try:
252 250 sys.stdout = self.cout
253 self.IP.push_line(line)
251 splitter.push(line)
252 more = splitter.push_accepts_more()
253 if not more:
254 source_raw = splitter.source_raw_reset()[1]
255 self.IP.run_cell(source_raw, store_history=store_history)
254 256 finally:
255 257 sys.stdout = stdout
256 258
259 def process_image(self, decorator):
260 """
261 # build out an image directive like
262 # .. image:: somefile.png
263 # :width 4in
264 #
265 # from an input like
266 # savefig somefile.png width=4in
267 """
268 savefig_dir = self.savefig_dir
269 source_dir = self.source_dir
270 saveargs = decorator.split(' ')
271 filename = saveargs[1]
272 # insert relative path to image file in source
273 outfile = os.path.relpath(os.path.join(savefig_dir,filename),
274 source_dir)
275
276 imagerows = ['.. image:: %s'%outfile]
277
278 for kwarg in saveargs[2:]:
279 arg, val = kwarg.split('=')
280 arg = arg.strip()
281 val = val.strip()
282 imagerows.append(' :%s: %s'%(arg, val))
283
284 image_file = os.path.basename(outfile) # only return file name
285 image_directive = '\n'.join(imagerows)
286 return image_file, image_directive
287
288
257 289 # Callbacks for each type of token
258 290 def process_input(self, data, input_prompt, lineno):
259 291 """Process data block for INPUT token."""
260 292 decorator, input, rest = data
261 293 image_file = None
294 image_directive = None
262 295 #print 'INPUT:', data # dbg
263 296 is_verbatim = decorator=='@verbatim' or self.is_verbatim
264 297 is_doctest = decorator=='@doctest' or self.is_doctest
265 298 is_suppress = decorator=='@suppress' or self.is_suppress
266 299 is_savefig = decorator is not None and \
267 300 decorator.startswith('@savefig')
268 301
269 302 input_lines = input.split('\n')
270 303
271 304 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
272 305 Nc = len(continuation)
273 306
274 307 if is_savefig:
275 saveargs = decorator.split(' ')
276 filename = saveargs[1]
277 outfile = os.path.join('_static/%s'%filename)
278 # build out an image directive like
279 # .. image:: somefile.png
280 # :width 4in
281 #
282 # from an input like
283 # savefig somefile.png width=4in
284 imagerows = ['.. image:: %s'%outfile]
285
286 for kwarg in saveargs[2:]:
287 arg, val = kwarg.split('=')
288 arg = arg.strip()
289 val = val.strip()
290 imagerows.append(' :%s: %s'%(arg, val))
291
292 image_file = outfile
293 image_directive = '\n'.join(imagerows)
294
295 # TODO: can we get "rest" from ipython
296 #self.process_input_line('\n'.join(input_lines))
308 image_file, image_directive = self.process_image(decorator)
297 309
298 310 ret = []
299 311 is_semicolon = False
312 store_history = True
300 313
301 314 for i, line in enumerate(input_lines):
302 315 if line.endswith(';'):
303 316 is_semicolon = True
317 if is_semicolon or is_suppress:
318 store_history = False
304 319
305 320 if i==0:
306 321 # process the first input line
307 322 if is_verbatim:
308 323 self.process_input_line('')
324 self.IP.execution_count += 1 # increment it anyway
309 325 else:
310 326 # only submit the line in non-verbatim mode
311 self.process_input_line(line)
327 self.process_input_line(line, store_history=store_history)
312 328 formatted_line = '%s %s'%(input_prompt, line)
313 329 else:
314 330 # process a continuation line
315 331 if not is_verbatim:
316 self.process_input_line(line)
332 self.process_input_line(line, store_history=store_history)
317 333
318 334 formatted_line = '%s %s'%(continuation, line)
319 335
320 336 if not is_suppress:
321 337 ret.append(formatted_line)
322 338
323 339 if not is_suppress:
324 340 if len(rest.strip()):
325 341 if is_verbatim:
326 342 # the "rest" is the standard output of the
327 343 # input, which needs to be added in
328 344 # verbatim mode
329 345 ret.append(rest)
330 346
331 347 self.cout.seek(0)
332 348 output = self.cout.read()
333 349 if not is_suppress and not is_semicolon:
334 350 ret.append(output)
335 351
336 352 self.cout.truncate(0)
337 return ret, input_lines, output, is_doctest, image_file
353 return (ret, input_lines, output, is_doctest, image_file,
354 image_directive)
338 355 #print 'OUTPUT', output # dbg
339 356
340 357 def process_output(self, data, output_prompt,
341 358 input_lines, output, is_doctest, image_file):
342 359 """Process data block for OUTPUT token."""
343 360 if is_doctest:
344 361 submitted = data.strip()
345 362 found = output
346 363 if found is not None:
347 364 found = found.strip()
348
365
349 366 # XXX - fperez: in 0.11, 'output' never comes with the prompt
350 367 # in it, just the actual output text. So I think all this code
351 368 # can be nuked...
352 ## ind = found.find(output_prompt)
353 ## if ind<0:
354 ## e='output prompt="%s" does not match out line=%s' % \
355 ## (output_prompt, found)
356 ## raise RuntimeError(e)
357 ## found = found[len(output_prompt):].strip()
369
370 # the above comment does not appear to be accurate... (minrk)
371
372 ind = found.find(output_prompt)
373 if ind<0:
374 e='output prompt="%s" does not match out line=%s' % \
375 (output_prompt, found)
376 raise RuntimeError(e)
377 found = found[len(output_prompt):].strip()
358 378
359 379 if found!=submitted:
360 380 e = ('doctest failure for input_lines="%s" with '
361 381 'found_output="%s" and submitted output="%s"' %
362 382 (input_lines, found, submitted) )
363 383 raise RuntimeError(e)
364 384 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
365 385
366 386 def process_comment(self, data):
367 """Process data block for COMMENT token."""
387 """Process data fPblock for COMMENT token."""
368 388 if not self.is_suppress:
369 389 return [data]
370 390
391 def save_image(self, image_file):
392 """
393 Saves the image file to disk.
394 """
395 self.ensure_pyplot()
396 command = 'plt.gcf().savefig("%s")'%image_file
397 #print 'SAVEFIG', command # dbg
398 self.process_input_line('bookmark ipy_thisdir', store_history=False)
399 self.process_input_line('cd -b ipy_savedir', store_history=False)
400 self.process_input_line(command, store_history=False)
401 self.process_input_line('cd -b ipy_thisdir', store_history=False)
402 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
403 self.clear_cout()
404
405
371 406 def process_block(self, block):
372 407 """
373 408 process block from the block_parser and return a list of processed lines
374 409 """
375
376 410 ret = []
377 411 output = None
378 412 input_lines = None
413 lineno = self.IP.execution_count
379 414
380 m = rgxin.match(str(self.IP.outputcache.prompt1).strip())
381 lineno = int(m.group(1))
382
383 input_prompt = fmtin%lineno
384 output_prompt = fmtout%lineno
415 input_prompt = self.promptin%lineno
416 output_prompt = self.promptout%lineno
385 417 image_file = None
386 418 image_directive = None
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,
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
391 # serves to highlight the coupling. Next we need to clean it up...
419
392 420 for token, data in block:
393 421 if token==COMMENT:
394 422 out_data = self.process_comment(data)
395 423 elif token==INPUT:
396 out_data, input_lines, output, is_doctest, image_file= \
424 (out_data, input_lines, output, is_doctest, image_file,
425 image_directive) = \
397 426 self.process_input(data, input_prompt, lineno)
398 427 elif token==OUTPUT:
399 428 out_data = \
400 429 self.process_output(data, output_prompt,
401 430 input_lines, output, is_doctest,
402 431 image_file)
403 432 if out_data:
404 433 ret.extend(out_data)
405 434
435 # save the image files
406 436 if image_file is not None:
407 self.ensure_pyplot()
408 command = 'plt.gcf().savefig("%s")'%image_file
409 print 'SAVEFIG', command # dbg
410 self.process_input_line('bookmark ipy_thisdir')
411 self.process_input_line('cd -b ipy_basedir')
412 self.process_input_line(command)
413 self.process_input_line('cd -b ipy_thisdir')
414 self.cout.seek(0)
415 self.cout.truncate(0)
437 self.save_image(image_file)
438
416 439 return ret, image_directive
417 440
418 441 def ensure_pyplot(self):
419 442 if self._pyplot_imported:
420 443 return
421 self.process_input_line('import matplotlib.pyplot as plt')
444 self.process_input_line('import matplotlib.pyplot as plt',
445 store_history=False)
422 446
423 # A global instance used below. XXX: not sure why this can't be created inside
424 # ipython_directive itself.
425 shell = EmbeddedSphinxShell()
447 def process_pure_python(self, content):
448 """
449 content is a list of strings. it is unedited directive conent
450
451 This runs it line by line in the InteractiveShell, prepends
452 prompts as needed capturing stderr and stdout, then returns
453 the content as a list as if it were ipython code
454 """
455 output = []
456 savefig = False # keep up with this to clear figure
457 multiline = False # to handle line continuation
458 fmtin = self.promptin
459
460 for lineno, line in enumerate(content):
461
462 line_stripped = line.strip()
463
464 if not len(line):
465 output.append(line) # preserve empty lines in output
466 continue
467
468 # handle decorators
469 if line_stripped.startswith('@'):
470 output.extend([line])
471 if 'savefig' in line:
472 savefig = True # and need to clear figure
473 continue
474
475 # handle comments
476 if line_stripped.startswith('#'):
477 output.extend([line])
478 continue
479
480 # deal with multilines
481 if not multiline: # not currently on a multiline
482
483 if line_stripped.endswith('\\'): # now we are
484 multiline = True
485 cont_len = len(str(lineno)) + 2
486 line_to_process = line.strip('\\')
487 output.extend([u"%s %s" % (fmtin%lineno,line)])
488 continue
489 else: # no we're still not
490 line_to_process = line.strip('\\')
491 else: # we are currently on a multiline
492 line_to_process += line.strip('\\')
493 if line_stripped.endswith('\\'): # and we still are
494 continuation = '.' * cont_len
495 output.extend([(u' %s: '+line_stripped) % continuation])
496 continue
497 # else go ahead and run this multiline then carry on
498
499 # get output of line
500 self.process_input_line(unicode(line_to_process.strip()),
501 store_history=False)
502 out_line = self.cout.getvalue()
503 self.clear_cout()
504
505 # clear current figure if plotted
506 if savefig:
507 self.ensure_pyplot()
508 self.process_input_line('plt.clf()', store_history=False)
509 self.clear_cout()
510 savefig = False
511
512 # line numbers don't actually matter, they're replaced later
513 if not multiline:
514 in_line = u"%s %s" % (fmtin%lineno,line)
515
516 output.extend([in_line])
517 else:
518 output.extend([(u' %s: '+line_stripped) % continuation])
519 multiline = False
520 if len(out_line):
521 output.extend([out_line])
522 output.extend([u''])
523
524 return output
525
526 class IpythonDirective(Directive):
527
528 has_content = True
529 required_arguments = 0
530 optional_arguments = 4 # python, suppress, verbatim, doctest
531 final_argumuent_whitespace = True
532 option_spec = { 'python': directives.unchanged,
533 'suppress' : directives.flag,
534 'verbatim' : directives.flag,
535 'doctest' : directives.flag,
536 }
426 537
427 def reconfig_shell():
428 """Called after setting module-level variables to re-instantiate
429 with the set values (since shell is instantiated first at import-time
430 when module variables have default values)"""
431 global shell
432 538 shell = EmbeddedSphinxShell()
433 539
540 def get_config_options(self):
541 # contains sphinx configuration variables
542 config = self.state.document.settings.env.config
543
544 # get config variables to set figure output directory
545 confdir = self.state.document.settings.env.app.confdir
546 savefig_dir = config.ipython_savefig_dir
547 source_dir = os.path.dirname(self.state.document.current_source)
548 if savefig_dir is None:
549 savefig_dir = config.html_static_path
550 if isinstance(savefig_dir, list):
551 savefig_dir = savefig_dir[0] # safe to assume only one path?
552 savefig_dir = os.path.join(confdir, savefig_dir)
553
554 # get regex and prompt stuff
555 rgxin = config.ipython_rgxin
556 rgxout = config.ipython_rgxout
557 promptin = config.ipython_promptin
558 promptout = config.ipython_promptout
559
560 return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout
561
562 def setup(self):
563 # get config values
564 (savefig_dir, source_dir, rgxin,
565 rgxout, promptin, promptout) = self.get_config_options()
566
567 # and attach to shell so we don't have to pass them around
568 self.shell.rgxin = rgxin
569 self.shell.rgxout = rgxout
570 self.shell.promptin = promptin
571 self.shell.promptout = promptout
572 self.shell.savefig_dir = savefig_dir
573 self.shell.source_dir = source_dir
434 574
435 def ipython_directive(name, arguments, options, content, lineno,
436 content_offset, block_text, state, state_machine,
437 ):
575 # setup bookmark for saving figures directory
438 576
439 debug = ipython_directive.DEBUG
440 shell.is_suppress = options.has_key('suppress')
441 shell.is_doctest = options.has_key('doctest')
442 shell.is_verbatim = options.has_key('verbatim')
577 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
578 store_history=False)
579 self.shell.clear_cout()
443 580
444 #print 'ipy', shell.is_suppress, options
445 parts = '\n'.join(content).split('\n\n')
446 lines = ['.. sourcecode:: ipython', '']
581 return rgxin, rgxout, promptin, promptout
447 582
448 figures = []
449 for part in parts:
450 block = block_parser(part)
451 583
452 if len(block):
453 rows, figure = shell.process_block(block)
454 for row in rows:
455 lines.extend([' %s'%line for line in row.split('\n')])
584 def teardown(self):
585 # delete last bookmark
586 self.shell.process_input_line('bookmark -d ipy_savedir',
587 store_history=False)
588 self.shell.clear_cout()
456 589
457 if figure is not None:
458 figures.append(figure)
590 def run(self):
591 debug = False
459 592
460 for figure in figures:
461 lines.append('')
462 lines.extend(figure.split('\n'))
463 lines.append('')
593 #TODO, any reason block_parser can't be a method of embeddable shell
594 # then we wouldn't have to carry these around
595 rgxin, rgxout, promptin, promptout = self.setup()
464 596
465 #print lines
466 if len(lines)>2:
467 if debug:
468 print '\n'.join(lines)
469 else:
470 #print 'INSERTING %d lines'%len(lines)
471 state_machine.insert_input(
472 lines, state_machine.input_lines.source(0))
597 options = self.options
598 self.shell.is_suppress = 'suppress' in options
599 self.shell.is_doctest = 'doctest' in options
600 self.shell.is_verbatim = 'verbatim' in options
473 601
474 return []
475 602
476 ipython_directive.DEBUG = False
477 ipython_directive.DEBUG = True # dbg
603 # handle pure python code
604 if 'python' in self.arguments:
605 content = self.content
606 self.content = self.shell.process_pure_python(content)
607
608 parts = '\n'.join(self.content).split('\n\n')
609
610 lines = ['.. code-block:: ipython','']
611 figures = []
612
613 for part in parts:
614
615 block = block_parser(part, rgxin, rgxout, promptin, promptout)
616
617 if len(block):
618 rows, figure = self.shell.process_block(block)
619 for row in rows:
620 lines.extend([' %s'%line for line in row.split('\n')])
621
622 if figure is not None:
623 figures.append(figure)
624
625 #text = '\n'.join(lines)
626 #figs = '\n'.join(figures)
627
628 for figure in figures:
629 lines.append('')
630 lines.extend(figure.split('\n'))
631 lines.append('')
632
633 #print lines
634 if len(lines)>2:
635 if debug:
636 print '\n'.join(lines)
637 else: #NOTE: this raises some errors, what's it for?
638 #print 'INSERTING %d lines'%len(lines)
639 self.state_machine.insert_input(
640 lines, self.state_machine.input_lines.source(0))
641
642 text = '\n'.join(lines)
643 txtnode = nodes.literal_block(text, text)
644 txtnode['language'] = 'ipython'
645 #imgnode = nodes.image(figs)
646
647 # cleanup
648 self.teardown()
649
650 return []#, imgnode]
478 651
479 652 # Enable as a proper Sphinx directive
480 653 def setup(app):
481 654 setup.app = app
482 options = {'suppress': directives.flag,
483 'doctest': directives.flag,
484 'verbatim': directives.flag,
485 }
486 655
487 app.add_directive('ipython', ipython_directive, True, (0, 2, 0), **options)
656 app.add_directive('ipython', IpythonDirective)
657 app.add_config_value('ipython_savefig_dir', None, True)
658 app.add_config_value('ipython_rgxin',
659 re.compile('In \[(\d+)\]:\s?(.*)\s*'), True)
660 app.add_config_value('ipython_rgxout',
661 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True)
662 app.add_config_value('ipython_promptin', 'In [%d]:', True)
663 app.add_config_value('ipython_promptout', 'Out[%d]:', True)
488 664
489 665
490 666 # Simple smoke test, needs to be converted to a proper automatic test.
491 667 def test():
492 668
493 669 examples = [
494 670 r"""
495 671 In [9]: pwd
496 672 Out[9]: '/home/jdhunter/py4science/book'
497 673
498 674 In [10]: cd bookdata/
499 675 /home/jdhunter/py4science/book/bookdata
500 676
501 677 In [2]: from pylab import *
502 678
503 679 In [2]: ion()
504 680
505 681 In [3]: im = imread('stinkbug.png')
506 682
507 683 @savefig mystinkbug.png width=4in
508 684 In [4]: imshow(im)
509 685 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
510
686
511 687 """,
512 688 r"""
513 689
514 690 In [1]: x = 'hello world'
515 691
516 692 # string methods can be
517 693 # used to alter the string
518 694 @doctest
519 695 In [2]: x.upper()
520 696 Out[2]: 'HELLO WORLD'
521 697
522 698 @verbatim
523 699 In [3]: x.st<TAB>
524 700 x.startswith x.strip
525 701 """,
526 702 r"""
527 703
528 704 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
529 705 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
530 706
531 707 In [131]: print url.split('&')
532 708 ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv']
533 709
534 710 In [60]: import urllib
535 711
536 712 """,
537 713 r"""\
538 714
539 715 In [133]: import numpy.random
540 716
541 717 @suppress
542 718 In [134]: numpy.random.seed(2358)
543 719
544 720 @doctest
545 In [135]: np.random.rand(10,2)
721 In [135]: numpy.random.rand(10,2)
546 722 Out[135]:
547 723 array([[ 0.64524308, 0.59943846],
548 724 [ 0.47102322, 0.8715456 ],
549 725 [ 0.29370834, 0.74776844],
550 726 [ 0.99539577, 0.1313423 ],
551 727 [ 0.16250302, 0.21103583],
552 728 [ 0.81626524, 0.1312433 ],
553 729 [ 0.67338089, 0.72302393],
554 730 [ 0.7566368 , 0.07033696],
555 731 [ 0.22591016, 0.77731835],
556 732 [ 0.0072729 , 0.34273127]])
557 733
558 734 """,
559 735
560 736 r"""
561 737 In [106]: print x
562 738 jdh
563 739
564 740 In [109]: for i in range(10):
565 741 .....: print i
566 742 .....:
567 743 .....:
568 744 0
569 745 1
570 746 2
571 747 3
572 748 4
573 749 5
574 750 6
575 751 7
576 752 8
577 753 9
578 754 """,
579 755
580 756 r"""
581 757
582 758 In [144]: from pylab import *
583 759
584 760 In [145]: ion()
585 761
586 762 # use a semicolon to suppress the output
587 763 @savefig test_hist.png width=4in
588 764 In [151]: hist(np.random.randn(10000), 100);
589 765
590 766
591 767 @savefig test_plot.png width=4in
592 768 In [151]: plot(np.random.randn(10000), 'o');
593 769 """,
594 770
595 771 r"""
596 772 # use a semicolon to suppress the output
597 773 In [151]: plt.clf()
598 774
599 775 @savefig plot_simple.png width=4in
600 776 In [151]: plot([1,2,3])
601 777
602 778 @savefig hist_simple.png width=4in
603 779 In [151]: hist(np.random.randn(10000), 100);
604 780
605 781 """,
606 782 r"""
607 783 # update the current fig
608 784 In [151]: ylabel('number')
609 785
610 786 In [152]: title('normal distribution')
611 787
612 788
613 789 @savefig hist_with_text.png
614 790 In [153]: grid(True)
615 791
616 792 """,
617 793 ]
794 # skip local-file depending first example:
795 examples = examples[1:]
618 796
619 797 #ipython_directive.DEBUG = True # dbg
620 798 #options = dict(suppress=True) # dbg
621 799 options = dict()
622 800 for example in examples:
623 801 content = example.split('\n')
624 802 ipython_directive('debug', arguments=None, options=options,
625 803 content=content, lineno=0,
626 804 content_offset=None, block_text=None,
627 805 state=None, state_machine=None,
628 806 )
629 807
630 808 # Run test suite as a script
631 809 if __name__=='__main__':
632 810 if not os.path.isdir('_static'):
633 811 os.mkdir('_static')
634 812 test()
635 813 print 'All OK? Check figures in _static/'
General Comments 0
You need to be logged in to leave comments. Login now