##// END OF EJS Templates
Docstring updates.
chebee7i -
Show More
@@ -1,155 +1,155 b''
1 1 """
2 Module containing additional handlers for the IPython directive's @doctest.
2 Handlers for IPythonDirective's @doctest pseudo-decorator.
3 3
4 4 The Sphinx extension that provides support for embedded IPython code provides
5 5 a pseudo-decorator @doctest, which treats the input/output block as a
6 6 doctest, raising a RuntimeError during doc generation if the actual output
7 7 (after running the input) does not match the expected output.
8 8
9 9 An example usage is:
10 10
11 11 .. code-block:: rst
12 12
13 13 .. ipython::
14 14
15 15 In [1]: x = 1
16 16
17 17 @doctest
18 18 In [2]: x + 2
19 19 Out[3]: 3
20 20
21 21 One can also provide arguments to the decorator. The first argument should be
22 22 the name of a custom handler. The specification of any other arguments is
23 23 determined by the handler. For example,
24 24
25 25 .. code-block:: rst
26 26
27 27 .. ipython::
28 28
29 29 @doctest float
30 30 In [154]: 0.1 + 0.2
31 31 Out[154]: 0.3
32 32
33 33 allows the actual output ``0.30000000000000004`` to match the expected output
34 34 due to a comparison with `np.allclose`.
35 35
36 36 This module contains handlers for the @doctest pseudo-decorator. Handlers
37 37 should have the following function signature::
38 38
39 39 handler(sphinx_shell, args, input_lines, found, submitted)
40 40
41 41 where `sphinx_shell` is the embedded Sphinx shell, `args` contains the list
42 42 of arguments that follow: '@doctest handler_name', `input_lines` contains
43 43 a list of the lines relevant to the current doctest, `found` is a string
44 44 containing the output from the IPython shell, and `submitted` is a string
45 45 containing the expected output from the IPython shell.
46 46
47 47 Handlers must be registered in the `doctests` dict at the end of this module.
48 48
49 49 """
50 50
51 51 def str_to_array(s):
52 52 """
53 53 Simplistic converter of strings from repr to float NumPy arrays.
54 54
55 55 If the repr representation has ellipsis in it, then this will fail.
56 56
57 57 Parameters
58 58 ----------
59 59 s : str
60 60 The repr version of a NumPy array.
61 61
62 62 Examples
63 63 --------
64 64 >>> s = "array([ 0.3, inf, nan])"
65 65 >>> a = str_to_array(s)
66 66
67 67 """
68 68 import numpy as np
69 69
70 70 # Need to make sure eval() knows about inf and nan.
71 71 # This also assumes default printoptions for NumPy.
72 72 from numpy import inf, nan
73 73
74 74 if s.startswith(u'array'):
75 75 # Remove array( and )
76 76 s = s[6:-1]
77 77
78 78 if s.startswith(u'['):
79 79 a = np.array(eval(s), dtype=float)
80 80 else:
81 81 # Assume its a regular float. Force 1D so we can index into it.
82 82 a = np.atleast_1d(float(s))
83 83 return a
84 84
85 85 def float_doctest(sphinx_shell, args, input_lines, found, submitted):
86 86 """
87 87 Doctest which allow the submitted output to vary slightly from the input.
88 88
89 89 Here is how it might appear in an rst file:
90 90
91 91 .. code-block:: rst
92 92
93 93 .. ipython::
94 94
95 95 @doctest float
96 96 In [1]: 0.1 + 0.2
97 97 Out[1]: 0.3
98 98
99 99 """
100 100 import numpy as np
101 101
102 102 if len(args) == 2:
103 103 rtol = 1e-05
104 104 atol = 1e-08
105 105 else:
106 106 # Both must be specified if any are specified.
107 107 try:
108 108 rtol = float(args[2])
109 109 atol = float(args[3])
110 110 except IndexError:
111 111 e = ("Both `rtol` and `atol` must be specified "
112 112 "if either are specified: {0}".format(args))
113 113 raise IndexError(e)
114 114
115 115 try:
116 116 submitted = str_to_array(submitted)
117 117 found = str_to_array(found)
118 118 except:
119 119 # For example, if the array is huge and there are ellipsis in it.
120 120 error = True
121 121 else:
122 122 found_isnan = np.isnan(found)
123 123 submitted_isnan = np.isnan(submitted)
124 124 error = not np.allclose(found_isnan, submitted_isnan)
125 125 error |= not np.allclose(found[~found_isnan],
126 126 submitted[~submitted_isnan],
127 127 rtol=rtol, atol=atol)
128 128
129 129 TAB = ' ' * 4
130 130 directive = sphinx_shell.directive
131 131 if directive is None:
132 132 source = 'Unavailable'
133 133 content = 'Unavailable'
134 134 else:
135 135 source = directive.state.document.current_source
136 136 # Add tabs and make into a single string.
137 137 content = '\n'.join([TAB + line for line in directive.content])
138 138
139 139 if error:
140 140
141 141 e = ('doctest float comparison failure\n\n'
142 142 'Document source: {0}\n\n'
143 143 'Raw content: \n{1}\n\n'
144 144 'On input line(s):\n{TAB}{2}\n\n'
145 145 'we found output:\n{TAB}{3}\n\n'
146 146 'instead of the expected:\n{TAB}{4}\n\n')
147 147 e = e.format(source, content, '\n'.join(input_lines), repr(found),
148 148 repr(submitted), TAB=TAB)
149 149 raise RuntimeError(e)
150 150
151 151 # dict of allowable doctest handlers. The key represents the first argument
152 152 # that must be given to @doctest in order to activate the handler.
153 153 doctests = {
154 154 'float': float_doctest,
155 155 }
@@ -1,982 +1,983 b''
1 1 # -*- coding: utf-8 -*-
2 """Sphinx directive to support embedded IPython code.
2 """
3 Sphinx directive to support embedded IPython code.
3 4
4 5 This directive allows pasting of entire interactive IPython sessions, prompts
5 6 and all, and their code will actually get re-executed at doc build time, with
6 7 all prompts renumbered sequentially. It also allows you to input code as a pure
7 8 python input by giving the argument python to the directive. The output looks
8 9 like an interactive ipython section.
9 10
10 11 To enable this directive, simply list it in your Sphinx ``conf.py`` file
11 12 (making sure the directory where you placed it is visible to sphinx, as is
12 13 needed for all Sphinx directives). For example, to enable syntax highlighting
13 14 and the IPython directive::
14 15
15 16 extensions = ['IPython.sphinxext.ipython_console_highlighting',
16 17 'IPython.sphinxext.ipython_directive']
17 18
18 19 The IPython directive outputs code-blocks with the language 'ipython'. So
19 20 if you do not have the syntax highlighting extension enabled as well, then
20 21 all rendered code-blocks will be uncolored. By default this directive assumes
21 22 that your prompts are unchanged IPython ones, but this can be customized.
22 23 The configurable options that can be placed in conf.py are:
23 24
24 25 ipython_savefig_dir:
25 26 The directory in which to save the figures. This is relative to the
26 27 Sphinx source directory. The default is `html_static_path`.
27 28 ipython_rgxin:
28 29 The compiled regular expression to denote the start of IPython input
29 30 lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You
30 31 shouldn't need to change this.
31 32 ipython_rgxout:
32 33 The compiled regular expression to denote the start of IPython output
33 34 lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You
34 35 shouldn't need to change this.
35 36 ipython_promptin:
36 37 The string to represent the IPython input prompt in the generated ReST.
37 38 The default is 'In [%d]:'. This expects that the line numbers are used
38 39 in the prompt.
39 40 ipython_promptout:
40 41 The string to represent the IPython prompt in the generated ReST. The
41 42 default is 'Out [%d]:'. This expects that the line numbers are used
42 43 in the prompt.
43 44 ipython_mplbackend:
44 45 The string which specifies if the embedded Sphinx shell should import
45 46 Matplotlib and set the backend. The value specifies a backend that is
46 47 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
47 48 executed. If not specified in conf.py, then the default value of 'agg' is
48 49 used. To use the IPython directive without matplotlib as a dependency, set
49 50 the value to `None`. It may end up that matplotlib is still imported
50 51 if the user specifies so in `ipython_execlines` or makes use of the
51 52 @savefig pseudo decorator.
52 53 ipython_execlines:
53 54 A list of strings to be exec'd in the embedded Sphinx shell. Typical
54 55 usage is to make certain packages always available. Set this to an empty
55 56 list if you wish to have no imports always available. If specified in
56 57 conf.py as `None`, then it has the effect of making no imports available.
57 58 If omitted from conf.py altogether, then the default value of
58 59 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
59 60
60 61 As an example, to use the IPython directive when `matplotlib` is not available,
61 62 one sets the backend to `None`::
62 63
63 64 ipython_mplbackend = None
64 65
65 66 An example usage of the directive is:
66 67
67 68 .. code-block:: rst
68 69
69 70 .. ipython::
70 71
71 72 In [1]: x = 1
72 73
73 74 In [2]: y = x**2
74 75
75 76 In [3]: print(y)
76 77
77 78 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
78 79 documentation.
79 80
80 81 ToDo
81 82 ----
82 83
83 84 - Turn the ad-hoc test() function into a real test suite.
84 85 - Break up ipython-specific functionality from matplotlib stuff into better
85 86 separated code.
86 87
87 88 Authors
88 89 -------
89 90
90 91 - John D Hunter: orignal author.
91 92 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
92 93 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
93 94 - Skipper Seabold, refactoring, cleanups, pure python addition
94 95 """
95 96 from __future__ import print_function
96 97
97 98 #-----------------------------------------------------------------------------
98 99 # Imports
99 100 #-----------------------------------------------------------------------------
100 101
101 102 # Stdlib
102 103 import os
103 104 import re
104 105 import sys
105 106 import tempfile
106 107 import ast
107 108
108 109 # To keep compatibility with various python versions
109 110 try:
110 111 from hashlib import md5
111 112 except ImportError:
112 113 from md5 import md5
113 114
114 115 # Third-party
115 116 import sphinx
116 117 from docutils.parsers.rst import directives
117 118 from docutils import nodes
118 119 from sphinx.util.compat import Directive
119 120
120 121 # Our own
121 122 from IPython import Config, InteractiveShell
122 123 from IPython.core.profiledir import ProfileDir
123 124 from IPython.utils import io
124 125 from IPython.utils.py3compat import PY3
125 126
126 127 if PY3:
127 128 from io import StringIO
128 129 else:
129 130 from StringIO import StringIO
130 131
131 132 #-----------------------------------------------------------------------------
132 133 # Globals
133 134 #-----------------------------------------------------------------------------
134 135 # for tokenizing blocks
135 136 COMMENT, INPUT, OUTPUT = range(3)
136 137
137 138 #-----------------------------------------------------------------------------
138 139 # Functions and class declarations
139 140 #-----------------------------------------------------------------------------
140 141
141 142 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
142 143 """
143 144 part is a string of ipython text, comprised of at most one
144 145 input, one ouput, comments, and blank lines. The block parser
145 146 parses the text into a list of::
146 147
147 148 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
148 149
149 150 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
150 151 data is, depending on the type of token::
151 152
152 153 COMMENT : the comment string
153 154
154 155 INPUT: the (DECORATOR, INPUT_LINE, REST) where
155 156 DECORATOR: the input decorator (or None)
156 157 INPUT_LINE: the input as string (possibly multi-line)
157 158 REST : any stdout generated by the input line (not OUTPUT)
158 159
159 160 OUTPUT: the output string, possibly multi-line
160 161
161 162 """
162 163 block = []
163 164 lines = part.split('\n')
164 165 N = len(lines)
165 166 i = 0
166 167 decorator = None
167 168 while 1:
168 169
169 170 if i==N:
170 171 # nothing left to parse -- the last line
171 172 break
172 173
173 174 line = lines[i]
174 175 i += 1
175 176 line_stripped = line.strip()
176 177 if line_stripped.startswith('#'):
177 178 block.append((COMMENT, line))
178 179 continue
179 180
180 181 if line_stripped.startswith('@'):
181 182 # we're assuming at most one decorator -- may need to
182 183 # rethink
183 184 decorator = line_stripped
184 185 continue
185 186
186 187 # does this look like an input line?
187 188 matchin = rgxin.match(line)
188 189 if matchin:
189 190 lineno, inputline = int(matchin.group(1)), matchin.group(2)
190 191
191 192 # the ....: continuation string
192 193 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
193 194 Nc = len(continuation)
194 195 # input lines can continue on for more than one line, if
195 196 # we have a '\' line continuation char or a function call
196 197 # echo line 'print'. The input line can only be
197 198 # terminated by the end of the block or an output line, so
198 199 # we parse out the rest of the input line if it is
199 200 # multiline as well as any echo text
200 201
201 202 rest = []
202 203 while i<N:
203 204
204 205 # look ahead; if the next line is blank, or a comment, or
205 206 # an output line, we're done
206 207
207 208 nextline = lines[i]
208 209 matchout = rgxout.match(nextline)
209 210 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
210 211 if matchout or nextline.startswith('#'):
211 212 break
212 213 elif nextline.startswith(continuation):
213 214 inputline += '\n' + nextline[Nc:]
214 215 else:
215 216 rest.append(nextline)
216 217 i+= 1
217 218
218 219 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
219 220 continue
220 221
221 222 # if it looks like an output line grab all the text to the end
222 223 # of the block
223 224 matchout = rgxout.match(line)
224 225 if matchout:
225 226 lineno, output = int(matchout.group(1)), matchout.group(2)
226 227 if i<N-1:
227 228 output = '\n'.join([output] + lines[i:])
228 229
229 230 block.append((OUTPUT, output))
230 231 break
231 232
232 233 return block
233 234
234 235
235 236 class EmbeddedSphinxShell(object):
236 237 """An embedded IPython instance to run inside Sphinx"""
237 238
238 239 def __init__(self, exec_lines=None):
239 240
240 241 self.cout = StringIO()
241 242
242 243 if exec_lines is None:
243 244 exec_lines = []
244 245
245 246 # Create config object for IPython
246 247 config = Config()
247 248 config.InteractiveShell.autocall = False
248 249 config.InteractiveShell.autoindent = False
249 250 config.InteractiveShell.colors = 'NoColor'
250 251
251 252 # create a profile so instance history isn't saved
252 253 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
253 254 profname = 'auto_profile_sphinx_build'
254 255 pdir = os.path.join(tmp_profile_dir,profname)
255 256 profile = ProfileDir.create_profile_dir(pdir)
256 257
257 258 # Create and initialize global ipython, but don't start its mainloop.
258 259 # This will persist across different EmbededSphinxShell instances.
259 260 IP = InteractiveShell.instance(config=config, profile_dir=profile)
260 261
261 262 # io.stdout redirect must be done after instantiating InteractiveShell
262 263 io.stdout = self.cout
263 264 io.stderr = self.cout
264 265
265 266 # For debugging, so we can see normal output, use this:
266 267 #from IPython.utils.io import Tee
267 268 #io.stdout = Tee(self.cout, channel='stdout') # dbg
268 269 #io.stderr = Tee(self.cout, channel='stderr') # dbg
269 270
270 271 # Store a few parts of IPython we'll need.
271 272 self.IP = IP
272 273 self.user_ns = self.IP.user_ns
273 274 self.user_global_ns = self.IP.user_global_ns
274 275
275 276 self.input = ''
276 277 self.output = ''
277 278
278 279 self.is_verbatim = False
279 280 self.is_doctest = False
280 281 self.is_suppress = False
281 282
282 283 # Optionally, provide more detailed information to shell.
283 284 self.directive = None
284 285
285 286 # on the first call to the savefig decorator, we'll import
286 287 # pyplot as plt so we can make a call to the plt.gcf().savefig
287 288 self._pyplot_imported = False
288 289
289 290 # Prepopulate the namespace.
290 291 for line in exec_lines:
291 292 self.process_input_line(line, store_history=False)
292 293
293 294 def clear_cout(self):
294 295 self.cout.seek(0)
295 296 self.cout.truncate(0)
296 297
297 298 def process_input_line(self, line, store_history=True):
298 299 """process the input, capturing stdout"""
299 300
300 301 stdout = sys.stdout
301 302 splitter = self.IP.input_splitter
302 303 try:
303 304 sys.stdout = self.cout
304 305 splitter.push(line)
305 306 more = splitter.push_accepts_more()
306 307 if not more:
307 308 source_raw = splitter.source_raw_reset()[1]
308 309 self.IP.run_cell(source_raw, store_history=store_history)
309 310 finally:
310 311 sys.stdout = stdout
311 312
312 313 def process_image(self, decorator):
313 314 """
314 315 # build out an image directive like
315 316 # .. image:: somefile.png
316 317 # :width 4in
317 318 #
318 319 # from an input like
319 320 # savefig somefile.png width=4in
320 321 """
321 322 savefig_dir = self.savefig_dir
322 323 source_dir = self.source_dir
323 324 saveargs = decorator.split(' ')
324 325 filename = saveargs[1]
325 326 # insert relative path to image file in source
326 327 outfile = os.path.relpath(os.path.join(savefig_dir,filename),
327 328 source_dir)
328 329
329 330 imagerows = ['.. image:: %s'%outfile]
330 331
331 332 for kwarg in saveargs[2:]:
332 333 arg, val = kwarg.split('=')
333 334 arg = arg.strip()
334 335 val = val.strip()
335 336 imagerows.append(' :%s: %s'%(arg, val))
336 337
337 338 image_file = os.path.basename(outfile) # only return file name
338 339 image_directive = '\n'.join(imagerows)
339 340 return image_file, image_directive
340 341
341 342 # Callbacks for each type of token
342 343 def process_input(self, data, input_prompt, lineno):
343 344 """
344 345 Process data block for INPUT token.
345 346
346 347 """
347 348 decorator, input, rest = data
348 349 image_file = None
349 350 image_directive = None
350 351
351 352 is_verbatim = decorator=='@verbatim' or self.is_verbatim
352 353 is_doctest = (decorator is not None and \
353 354 decorator.startswith('@doctest')) or self.is_doctest
354 355 is_suppress = decorator=='@suppress' or self.is_suppress
355 356 is_savefig = decorator is not None and \
356 357 decorator.startswith('@savefig')
357 358
358 359 input_lines = input.split('\n')
359 360 if len(input_lines) > 1:
360 361 if input_lines[-1] != "":
361 362 input_lines.append('') # make sure there's a blank line
362 363 # so splitter buffer gets reset
363 364
364 365 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
365 366 Nc = len(continuation)
366 367
367 368 if is_savefig:
368 369 image_file, image_directive = self.process_image(decorator)
369 370
370 371 ret = []
371 372 is_semicolon = False
372 373
373 374 for i, line in enumerate(input_lines):
374 375 if line.endswith(';'):
375 376 is_semicolon = True
376 377
377 378 if i==0:
378 379 # process the first input line
379 380 if is_verbatim:
380 381 self.process_input_line('')
381 382 self.IP.execution_count += 1 # increment it anyway
382 383 else:
383 384 # only submit the line in non-verbatim mode
384 385 self.process_input_line(line, store_history=True)
385 386 formatted_line = '%s %s'%(input_prompt, line)
386 387 else:
387 388 # process a continuation line
388 389 if not is_verbatim:
389 390 self.process_input_line(line, store_history=True)
390 391
391 392 formatted_line = '%s %s'%(continuation, line)
392 393
393 394 if not is_suppress:
394 395 ret.append(formatted_line)
395 396
396 397 if not is_suppress and len(rest.strip()) and is_verbatim:
397 398 # the "rest" is the standard output of the
398 399 # input, which needs to be added in
399 400 # verbatim mode
400 401 ret.append(rest)
401 402
402 403 self.cout.seek(0)
403 404 output = self.cout.read()
404 405 if not is_suppress and not is_semicolon:
405 406 ret.append(output)
406 407 elif is_semicolon: # get spacing right
407 408 ret.append('')
408 409
409 410 self.cout.truncate(0)
410 411 return (ret, input_lines, output, is_doctest, decorator, image_file,
411 412 image_directive)
412 413
413 414
414 415 def process_output(self, data, output_prompt,
415 416 input_lines, output, is_doctest, decorator, image_file):
416 417 """
417 418 Process data block for OUTPUT token.
418 419
419 420 """
420 421 TAB = ' ' * 4
421 422
422 423 if is_doctest and output is not None:
423 424
424 425 found = output
425 426 found = found.strip()
426 427 submitted = data.strip()
427 428
428 429 if self.directive is None:
429 430 source = 'Unavailable'
430 431 content = 'Unavailable'
431 432 else:
432 433 source = self.directive.state.document.current_source
433 434 content = self.directive.content
434 435 # Add tabs and join into a single string.
435 436 content = '\n'.join([TAB + line for line in content])
436 437
437 438 # Make sure the output contains the output prompt.
438 439 ind = found.find(output_prompt)
439 440 if ind < 0:
440 441 e = ('output does not contain output prompt\n\n'
441 442 'Document source: {0}\n\n'
442 443 'Raw content: \n{1}\n\n'
443 444 'Input line(s):\n{TAB}{2}\n\n'
444 445 'Output line(s):\n{TAB}{3}\n\n')
445 446 e = e.format(source, content, '\n'.join(input_lines),
446 447 repr(found), TAB=TAB)
447 448 raise RuntimeError(e)
448 449 found = found[len(output_prompt):].strip()
449 450
450 451 # Handle the actual doctest comparison.
451 452 if decorator.strip() == '@doctest':
452 453 # Standard doctest
453 454 if found != submitted:
454 455 e = ('doctest failure\n\n'
455 456 'Document source: {0}\n\n'
456 457 'Raw content: \n{1}\n\n'
457 458 'On input line(s):\n{TAB}{2}\n\n'
458 459 'we found output:\n{TAB}{3}\n\n'
459 460 'instead of the expected:\n{TAB}{4}\n\n')
460 461 e = e.format(source, content, '\n'.join(input_lines),
461 462 repr(found), repr(submitted), TAB=TAB)
462 463 raise RuntimeError(e)
463 464 else:
464 465 self.custom_doctest(decorator, input_lines, found, submitted)
465 466
466 467 def process_comment(self, data):
467 468 """Process data fPblock for COMMENT token."""
468 469 if not self.is_suppress:
469 470 return [data]
470 471
471 472 def save_image(self, image_file):
472 473 """
473 474 Saves the image file to disk.
474 475 """
475 476 self.ensure_pyplot()
476 477 command = 'plt.gcf().savefig("%s")'%image_file
477 478 #print 'SAVEFIG', command # dbg
478 479 self.process_input_line('bookmark ipy_thisdir', store_history=False)
479 480 self.process_input_line('cd -b ipy_savedir', store_history=False)
480 481 self.process_input_line(command, store_history=False)
481 482 self.process_input_line('cd -b ipy_thisdir', store_history=False)
482 483 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
483 484 self.clear_cout()
484 485
485 486 def process_block(self, block):
486 487 """
487 488 process block from the block_parser and return a list of processed lines
488 489 """
489 490 ret = []
490 491 output = None
491 492 input_lines = None
492 493 lineno = self.IP.execution_count
493 494
494 495 input_prompt = self.promptin % lineno
495 496 output_prompt = self.promptout % lineno
496 497 image_file = None
497 498 image_directive = None
498 499
499 500 for token, data in block:
500 501 if token == COMMENT:
501 502 out_data = self.process_comment(data)
502 503 elif token == INPUT:
503 504 (out_data, input_lines, output, is_doctest, decorator,
504 505 image_file, image_directive) = \
505 506 self.process_input(data, input_prompt, lineno)
506 507 elif token == OUTPUT:
507 508 out_data = \
508 509 self.process_output(data, output_prompt,
509 510 input_lines, output, is_doctest,
510 511 decorator, image_file)
511 512 if out_data:
512 513 ret.extend(out_data)
513 514
514 515 # save the image files
515 516 if image_file is not None:
516 517 self.save_image(image_file)
517 518
518 519 return ret, image_directive
519 520
520 521 def ensure_pyplot(self):
521 522 """
522 523 Ensures that pyplot has been imported into the embedded IPython shell.
523 524
524 525 Also, makes sure to set the backend appropriately if not set already.
525 526
526 527 """
527 528 # We are here if the @figure pseudo decorator was used. Thus, it's
528 529 # possible that we could be here even if python_mplbackend were set to
529 530 # `None`. That's also strange and perhaps worthy of raising an
530 531 # exception, but for now, we just set the backend to 'agg'.
531 532
532 533 if not self._pyplot_imported:
533 534 if 'matplotlib.backends' not in sys.modules:
534 535 # Then ipython_matplotlib was set to None but there was a
535 536 # call to the @figure decorator (and ipython_execlines did
536 537 # not set a backend).
537 538 #raise Exception("No backend was set, but @figure was used!")
538 539 import matplotlib
539 540 matplotlib.use('agg')
540 541
541 542 # Always import pyplot into embedded shell.
542 543 self.process_input_line('import matplotlib.pyplot as plt',
543 544 store_history=False)
544 545 self._pyplot_imported = True
545 546
546 547 def process_pure_python(self, content):
547 548 """
548 549 content is a list of strings. it is unedited directive content
549 550
550 551 This runs it line by line in the InteractiveShell, prepends
551 552 prompts as needed capturing stderr and stdout, then returns
552 553 the content as a list as if it were ipython code
553 554 """
554 555 output = []
555 556 savefig = False # keep up with this to clear figure
556 557 multiline = False # to handle line continuation
557 558 multiline_start = None
558 559 fmtin = self.promptin
559 560
560 561 ct = 0
561 562
562 563 for lineno, line in enumerate(content):
563 564
564 565 line_stripped = line.strip()
565 566 if not len(line):
566 567 output.append(line)
567 568 continue
568 569
569 570 # handle decorators
570 571 if line_stripped.startswith('@'):
571 572 output.extend([line])
572 573 if 'savefig' in line:
573 574 savefig = True # and need to clear figure
574 575 continue
575 576
576 577 # handle comments
577 578 if line_stripped.startswith('#'):
578 579 output.extend([line])
579 580 continue
580 581
581 582 # deal with lines checking for multiline
582 583 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
583 584 if not multiline:
584 585 modified = u"%s %s" % (fmtin % ct, line_stripped)
585 586 output.append(modified)
586 587 ct += 1
587 588 try:
588 589 ast.parse(line_stripped)
589 590 output.append(u'')
590 591 except Exception: # on a multiline
591 592 multiline = True
592 593 multiline_start = lineno
593 594 else: # still on a multiline
594 595 modified = u'%s %s' % (continuation, line)
595 596 output.append(modified)
596 597
597 598 # if the next line is indented, it should be part of multiline
598 599 if len(content) > lineno + 1:
599 600 nextline = content[lineno + 1]
600 601 if len(nextline) - len(nextline.lstrip()) > 3:
601 602 continue
602 603 try:
603 604 mod = ast.parse(
604 605 '\n'.join(content[multiline_start:lineno+1]))
605 606 if isinstance(mod.body[0], ast.FunctionDef):
606 607 # check to see if we have the whole function
607 608 for element in mod.body[0].body:
608 609 if isinstance(element, ast.Return):
609 610 multiline = False
610 611 else:
611 612 output.append(u'')
612 613 multiline = False
613 614 except Exception:
614 615 pass
615 616
616 617 if savefig: # clear figure if plotted
617 618 self.ensure_pyplot()
618 619 self.process_input_line('plt.clf()', store_history=False)
619 620 self.clear_cout()
620 621 savefig = False
621 622
622 623 return output
623 624
624 625 def custom_doctest(self, decorator, input_lines, found, submitted):
625 626 """
626 627 Perform a specialized doctest.
627 628
628 629 """
629 630 from .custom_doctests import doctests
630 631
631 632 args = decorator.split()
632 633 doctest_type = args[1]
633 634 if doctest_type in doctests:
634 635 doctests[doctest_type](self, args, input_lines, found, submitted)
635 636 else:
636 637 e = "Invalid option to @doctest: {0}".format(doctest_type)
637 638 raise Exception(e)
638 639
639 640
640 641 class IPythonDirective(Directive):
641 642
642 643 has_content = True
643 644 required_arguments = 0
644 645 optional_arguments = 4 # python, suppress, verbatim, doctest
645 646 final_argumuent_whitespace = True
646 647 option_spec = { 'python': directives.unchanged,
647 648 'suppress' : directives.flag,
648 649 'verbatim' : directives.flag,
649 650 'doctest' : directives.flag,
650 651 }
651 652
652 653 shell = None
653 654
654 655 seen_docs = set()
655 656
656 657 def get_config_options(self):
657 658 # contains sphinx configuration variables
658 659 config = self.state.document.settings.env.config
659 660
660 661 # get config variables to set figure output directory
661 662 confdir = self.state.document.settings.env.app.confdir
662 663 savefig_dir = config.ipython_savefig_dir
663 664 source_dir = os.path.dirname(self.state.document.current_source)
664 665 if savefig_dir is None:
665 666 savefig_dir = config.html_static_path
666 667 if isinstance(savefig_dir, list):
667 668 savefig_dir = savefig_dir[0] # safe to assume only one path?
668 669 savefig_dir = os.path.join(confdir, savefig_dir)
669 670
670 671 # get regex and prompt stuff
671 672 rgxin = config.ipython_rgxin
672 673 rgxout = config.ipython_rgxout
673 674 promptin = config.ipython_promptin
674 675 promptout = config.ipython_promptout
675 676 mplbackend = config.ipython_mplbackend
676 677 exec_lines = config.ipython_execlines
677 678
678 679 return (savefig_dir, source_dir, rgxin, rgxout,
679 680 promptin, promptout, mplbackend, exec_lines)
680 681
681 682 def setup(self):
682 683 # Get configuration values.
683 684 (savefig_dir, source_dir, rgxin, rgxout, promptin,
684 685 promptout, mplbackend, exec_lines) = self.get_config_options()
685 686
686 687 if self.shell is None:
687 688 # We will be here many times. However, when the
688 689 # EmbeddedSphinxShell is created, its interactive shell member
689 690 # is the same for each instance.
690 691
691 692 if mplbackend:
692 693 import matplotlib
693 694 # Repeated calls to use() will not hurt us since `mplbackend`
694 695 # is the same each time.
695 696 matplotlib.use(mplbackend)
696 697
697 698 # Must be called after (potentially) importing matplotlib and
698 699 # setting its backend since exec_lines might import pylab.
699 700 self.shell = EmbeddedSphinxShell(exec_lines)
700 701
701 702 # Store IPython directive to enable better error messages
702 703 self.shell.directive = self
703 704
704 705 # reset the execution count if we haven't processed this doc
705 706 #NOTE: this may be borked if there are multiple seen_doc tmp files
706 707 #check time stamp?
707 708 if not self.state.document.current_source in self.seen_docs:
708 709 self.shell.IP.history_manager.reset()
709 710 self.shell.IP.execution_count = 1
710 711 self.shell.IP.prompt_manager.width = 0
711 712 self.seen_docs.add(self.state.document.current_source)
712 713
713 714 # and attach to shell so we don't have to pass them around
714 715 self.shell.rgxin = rgxin
715 716 self.shell.rgxout = rgxout
716 717 self.shell.promptin = promptin
717 718 self.shell.promptout = promptout
718 719 self.shell.savefig_dir = savefig_dir
719 720 self.shell.source_dir = source_dir
720 721
721 722 # setup bookmark for saving figures directory
722 723 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
723 724 store_history=False)
724 725 self.shell.clear_cout()
725 726
726 727 return rgxin, rgxout, promptin, promptout
727 728
728 729 def teardown(self):
729 730 # delete last bookmark
730 731 self.shell.process_input_line('bookmark -d ipy_savedir',
731 732 store_history=False)
732 733 self.shell.clear_cout()
733 734
734 735 def run(self):
735 736 debug = False
736 737
737 738 #TODO, any reason block_parser can't be a method of embeddable shell
738 739 # then we wouldn't have to carry these around
739 740 rgxin, rgxout, promptin, promptout = self.setup()
740 741
741 742 options = self.options
742 743 self.shell.is_suppress = 'suppress' in options
743 744 self.shell.is_doctest = 'doctest' in options
744 745 self.shell.is_verbatim = 'verbatim' in options
745 746
746 747 # handle pure python code
747 748 if 'python' in self.arguments:
748 749 content = self.content
749 750 self.content = self.shell.process_pure_python(content)
750 751
751 752 parts = '\n'.join(self.content).split('\n\n')
752 753
753 754 lines = ['.. code-block:: ipython', '']
754 755 figures = []
755 756
756 757 for part in parts:
757 758 block = block_parser(part, rgxin, rgxout, promptin, promptout)
758 759 if len(block):
759 760 rows, figure = self.shell.process_block(block)
760 761 for row in rows:
761 762 lines.extend([' %s'%line for line in row.split('\n')])
762 763
763 764 if figure is not None:
764 765 figures.append(figure)
765 766
766 767 for figure in figures:
767 768 lines.append('')
768 769 lines.extend(figure.split('\n'))
769 770 lines.append('')
770 771
771 772 if len(lines)>2:
772 773 if debug:
773 774 print('\n'.join(lines))
774 775 else:
775 776 # This has to do with input, not output. But if we comment
776 777 # these lines out, then no IPython code will appear in the
777 778 # final output.
778 779 self.state_machine.insert_input(
779 780 lines, self.state_machine.input_lines.source(0))
780 781
781 782 # cleanup
782 783 self.teardown()
783 784
784 785 return []
785 786
786 787 # Enable as a proper Sphinx directive
787 788 def setup(app):
788 789 setup.app = app
789 790
790 791 app.add_directive('ipython', IPythonDirective)
791 792 app.add_config_value('ipython_savefig_dir', None, 'env')
792 793 app.add_config_value('ipython_rgxin',
793 794 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
794 795 app.add_config_value('ipython_rgxout',
795 796 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
796 797 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
797 798 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
798 799 # We could just let matplotlib pick whatever is specified as the default
799 800 # backend in the matplotlibrc file, but this would cause issues if the
800 801 # backend didn't work in headless environments. For this reason, 'agg'
801 802 # is a good default backend choice.
802 803 app.add_config_value('ipython_mplbackend', 'agg', 'env')
803 804 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
804 805 # __init__ method will treat it as [].
805 806 execlines = ['import numpy as np', 'import matplotlib.pyplot as plt']
806 807 app.add_config_value('ipython_execlines', execlines, 'env')
807 808
808 809 # Simple smoke test, needs to be converted to a proper automatic test.
809 810 def test():
810 811
811 812 examples = [
812 813 r"""
813 814 In [9]: pwd
814 815 Out[9]: '/home/jdhunter/py4science/book'
815 816
816 817 In [10]: cd bookdata/
817 818 /home/jdhunter/py4science/book/bookdata
818 819
819 820 In [2]: from pylab import *
820 821
821 822 In [2]: ion()
822 823
823 824 In [3]: im = imread('stinkbug.png')
824 825
825 826 @savefig mystinkbug.png width=4in
826 827 In [4]: imshow(im)
827 828 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
828 829
829 830 """,
830 831 r"""
831 832
832 833 In [1]: x = 'hello world'
833 834
834 835 # string methods can be
835 836 # used to alter the string
836 837 @doctest
837 838 In [2]: x.upper()
838 839 Out[2]: 'HELLO WORLD'
839 840
840 841 @verbatim
841 842 In [3]: x.st<TAB>
842 843 x.startswith x.strip
843 844 """,
844 845 r"""
845 846
846 847 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
847 848 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
848 849
849 850 In [131]: print url.split('&')
850 851 ['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']
851 852
852 853 In [60]: import urllib
853 854
854 855 """,
855 856 r"""\
856 857
857 858 In [133]: import numpy.random
858 859
859 860 @suppress
860 861 In [134]: numpy.random.seed(2358)
861 862
862 863 @doctest
863 864 In [135]: numpy.random.rand(10,2)
864 865 Out[135]:
865 866 array([[ 0.64524308, 0.59943846],
866 867 [ 0.47102322, 0.8715456 ],
867 868 [ 0.29370834, 0.74776844],
868 869 [ 0.99539577, 0.1313423 ],
869 870 [ 0.16250302, 0.21103583],
870 871 [ 0.81626524, 0.1312433 ],
871 872 [ 0.67338089, 0.72302393],
872 873 [ 0.7566368 , 0.07033696],
873 874 [ 0.22591016, 0.77731835],
874 875 [ 0.0072729 , 0.34273127]])
875 876
876 877 """,
877 878
878 879 r"""
879 880 In [106]: print x
880 881 jdh
881 882
882 883 In [109]: for i in range(10):
883 884 .....: print i
884 885 .....:
885 886 .....:
886 887 0
887 888 1
888 889 2
889 890 3
890 891 4
891 892 5
892 893 6
893 894 7
894 895 8
895 896 9
896 897 """,
897 898
898 899 r"""
899 900
900 901 In [144]: from pylab import *
901 902
902 903 In [145]: ion()
903 904
904 905 # use a semicolon to suppress the output
905 906 @savefig test_hist.png width=4in
906 907 In [151]: hist(np.random.randn(10000), 100);
907 908
908 909
909 910 @savefig test_plot.png width=4in
910 911 In [151]: plot(np.random.randn(10000), 'o');
911 912 """,
912 913
913 914 r"""
914 915 # use a semicolon to suppress the output
915 916 In [151]: plt.clf()
916 917
917 918 @savefig plot_simple.png width=4in
918 919 In [151]: plot([1,2,3])
919 920
920 921 @savefig hist_simple.png width=4in
921 922 In [151]: hist(np.random.randn(10000), 100);
922 923
923 924 """,
924 925 r"""
925 926 # update the current fig
926 927 In [151]: ylabel('number')
927 928
928 929 In [152]: title('normal distribution')
929 930
930 931
931 932 @savefig hist_with_text.png
932 933 In [153]: grid(True)
933 934
934 935 @doctest float
935 936 In [154]: 0.1 + 0.2
936 937 Out[154]: 0.3
937 938
938 939 @doctest float
939 940 In [155]: np.arange(16).reshape(4,4)
940 941 Out[155]:
941 942 array([[ 0, 1, 2, 3],
942 943 [ 4, 5, 6, 7],
943 944 [ 8, 9, 10, 11],
944 945 [12, 13, 14, 15]])
945 946
946 947 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
947 948
948 949 In [2]: x[0,0] = np.inf
949 950
950 951 In [3]: x[0,1] = np.nan
951 952
952 953 @doctest float
953 954 In [4]: x
954 955 Out[4]:
955 956 array([[ inf, nan, 2., 3.],
956 957 [ 4., 5., 6., 7.],
957 958 [ 8., 9., 10., 11.],
958 959 [ 12., 13., 14., 15.]])
959 960
960 961
961 962 """,
962 963 ]
963 964 # skip local-file depending first example:
964 965 examples = examples[1:]
965 966
966 967 #ipython_directive.DEBUG = True # dbg
967 968 #options = dict(suppress=True) # dbg
968 969 options = dict()
969 970 for example in examples:
970 971 content = example.split('\n')
971 972 IPythonDirective('debug', arguments=None, options=options,
972 973 content=content, lineno=0,
973 974 content_offset=None, block_text=None,
974 975 state=None, state_machine=None,
975 976 )
976 977
977 978 # Run test suite as a script
978 979 if __name__=='__main__':
979 980 if not os.path.isdir('_static'):
980 981 os.mkdir('_static')
981 982 test()
982 983 print('All OK? Check figures in _static/')
General Comments 0
You need to be logged in to leave comments. Login now