##// END OF EJS Templates
Support parallel sphinx building
Michael Droettboom -
Show More
@@ -1,27 +1,28 b''
1 """
1 """
2 reST directive for syntax-highlighting ipython interactive sessions.
2 reST directive for syntax-highlighting ipython interactive sessions.
3
3
4 """
4 """
5
5
6 from sphinx import highlighting
6 from sphinx import highlighting
7 from IPython.lib.lexers import IPyLexer
7 from IPython.lib.lexers import IPyLexer
8
8
9 def setup(app):
9 def setup(app):
10 """Setup as a sphinx extension."""
10 """Setup as a sphinx extension."""
11
11
12 # This is only a lexer, so adding it below to pygments appears sufficient.
12 # This is only a lexer, so adding it below to pygments appears sufficient.
13 # But if somebody knows what the right API usage should be to do that via
13 # But if somebody knows what the right API usage should be to do that via
14 # sphinx, by all means fix it here. At least having this setup.py
14 # sphinx, by all means fix it here. At least having this setup.py
15 # suppresses the sphinx warning we'd get without it.
15 # suppresses the sphinx warning we'd get without it.
16 pass
16 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
17 return metadata
17
18
18 # Register the extension as a valid pygments lexer.
19 # Register the extension as a valid pygments lexer.
19 # Alternatively, we could register the lexer with pygments instead. This would
20 # Alternatively, we could register the lexer with pygments instead. This would
20 # require using setuptools entrypoints: http://pygments.org/docs/plugins
21 # require using setuptools entrypoints: http://pygments.org/docs/plugins
21
22
22 ipy2 = IPyLexer(python3=False)
23 ipy2 = IPyLexer(python3=False)
23 ipy3 = IPyLexer(python3=True)
24 ipy3 = IPyLexer(python3=True)
24
25
25 highlighting.lexers['ipython'] = ipy2
26 highlighting.lexers['ipython'] = ipy2
26 highlighting.lexers['ipython2'] = ipy2
27 highlighting.lexers['ipython2'] = ipy2
27 highlighting.lexers['ipython3'] = ipy3
28 highlighting.lexers['ipython3'] = ipy3
@@ -1,1185 +1,1188 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Sphinx directive to support embedded IPython code.
3 Sphinx directive to support embedded IPython code.
4
4
5 This directive allows pasting of entire interactive IPython sessions, prompts
5 This directive allows pasting of entire interactive IPython sessions, prompts
6 and all, and their code will actually get re-executed at doc build time, with
6 and all, and their code will actually get re-executed at doc build time, with
7 all prompts renumbered sequentially. It also allows you to input code as a pure
7 all prompts renumbered sequentially. It also allows you to input code as a pure
8 python input by giving the argument python to the directive. The output looks
8 python input by giving the argument python to the directive. The output looks
9 like an interactive ipython section.
9 like an interactive ipython section.
10
10
11 To enable this directive, simply list it in your Sphinx ``conf.py`` file
11 To enable this directive, simply list it in your Sphinx ``conf.py`` file
12 (making sure the directory where you placed it is visible to sphinx, as is
12 (making sure the directory where you placed it is visible to sphinx, as is
13 needed for all Sphinx directives). For example, to enable syntax highlighting
13 needed for all Sphinx directives). For example, to enable syntax highlighting
14 and the IPython directive::
14 and the IPython directive::
15
15
16 extensions = ['IPython.sphinxext.ipython_console_highlighting',
16 extensions = ['IPython.sphinxext.ipython_console_highlighting',
17 'IPython.sphinxext.ipython_directive']
17 'IPython.sphinxext.ipython_directive']
18
18
19 The IPython directive outputs code-blocks with the language 'ipython'. So
19 The IPython directive outputs code-blocks with the language 'ipython'. So
20 if you do not have the syntax highlighting extension enabled as well, then
20 if you do not have the syntax highlighting extension enabled as well, then
21 all rendered code-blocks will be uncolored. By default this directive assumes
21 all rendered code-blocks will be uncolored. By default this directive assumes
22 that your prompts are unchanged IPython ones, but this can be customized.
22 that your prompts are unchanged IPython ones, but this can be customized.
23 The configurable options that can be placed in conf.py are:
23 The configurable options that can be placed in conf.py are:
24
24
25 ipython_savefig_dir:
25 ipython_savefig_dir:
26 The directory in which to save the figures. This is relative to the
26 The directory in which to save the figures. This is relative to the
27 Sphinx source directory. The default is `html_static_path`.
27 Sphinx source directory. The default is `html_static_path`.
28 ipython_rgxin:
28 ipython_rgxin:
29 The compiled regular expression to denote the start of IPython input
29 The compiled regular expression to denote the start of IPython input
30 lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You
30 lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You
31 shouldn't need to change this.
31 shouldn't need to change this.
32 ipython_rgxout:
32 ipython_rgxout:
33 The compiled regular expression to denote the start of IPython output
33 The compiled regular expression to denote the start of IPython output
34 lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You
34 lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You
35 shouldn't need to change this.
35 shouldn't need to change this.
36 ipython_promptin:
36 ipython_promptin:
37 The string to represent the IPython input prompt in the generated ReST.
37 The string to represent the IPython input prompt in the generated ReST.
38 The default is 'In [%d]:'. This expects that the line numbers are used
38 The default is 'In [%d]:'. This expects that the line numbers are used
39 in the prompt.
39 in the prompt.
40 ipython_promptout:
40 ipython_promptout:
41 The string to represent the IPython prompt in the generated ReST. The
41 The string to represent the IPython prompt in the generated ReST. The
42 default is 'Out [%d]:'. This expects that the line numbers are used
42 default is 'Out [%d]:'. This expects that the line numbers are used
43 in the prompt.
43 in the prompt.
44 ipython_mplbackend:
44 ipython_mplbackend:
45 The string which specifies if the embedded Sphinx shell should import
45 The string which specifies if the embedded Sphinx shell should import
46 Matplotlib and set the backend. The value specifies a backend that is
46 Matplotlib and set the backend. The value specifies a backend that is
47 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
47 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
48 executed. If not specified in conf.py, then the default value of 'agg' is
48 executed. If not specified in conf.py, then the default value of 'agg' is
49 used. To use the IPython directive without matplotlib as a dependency, set
49 used. To use the IPython directive without matplotlib as a dependency, set
50 the value to `None`. It may end up that matplotlib is still imported
50 the value to `None`. It may end up that matplotlib is still imported
51 if the user specifies so in `ipython_execlines` or makes use of the
51 if the user specifies so in `ipython_execlines` or makes use of the
52 @savefig pseudo decorator.
52 @savefig pseudo decorator.
53 ipython_execlines:
53 ipython_execlines:
54 A list of strings to be exec'd in the embedded Sphinx shell. Typical
54 A list of strings to be exec'd in the embedded Sphinx shell. Typical
55 usage is to make certain packages always available. Set this to an empty
55 usage is to make certain packages always available. Set this to an empty
56 list if you wish to have no imports always available. If specified in
56 list if you wish to have no imports always available. If specified in
57 conf.py as `None`, then it has the effect of making no imports available.
57 conf.py as `None`, then it has the effect of making no imports available.
58 If omitted from conf.py altogether, then the default value of
58 If omitted from conf.py altogether, then the default value of
59 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
59 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
60 ipython_holdcount
60 ipython_holdcount
61 When the @suppress pseudo-decorator is used, the execution count can be
61 When the @suppress pseudo-decorator is used, the execution count can be
62 incremented or not. The default behavior is to hold the execution count,
62 incremented or not. The default behavior is to hold the execution count,
63 corresponding to a value of `True`. Set this to `False` to increment
63 corresponding to a value of `True`. Set this to `False` to increment
64 the execution count after each suppressed command.
64 the execution count after each suppressed command.
65
65
66 As an example, to use the IPython directive when `matplotlib` is not available,
66 As an example, to use the IPython directive when `matplotlib` is not available,
67 one sets the backend to `None`::
67 one sets the backend to `None`::
68
68
69 ipython_mplbackend = None
69 ipython_mplbackend = None
70
70
71 An example usage of the directive is:
71 An example usage of the directive is:
72
72
73 .. code-block:: rst
73 .. code-block:: rst
74
74
75 .. ipython::
75 .. ipython::
76
76
77 In [1]: x = 1
77 In [1]: x = 1
78
78
79 In [2]: y = x**2
79 In [2]: y = x**2
80
80
81 In [3]: print(y)
81 In [3]: print(y)
82
82
83 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
83 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
84 documentation.
84 documentation.
85
85
86 Pseudo-Decorators
86 Pseudo-Decorators
87 =================
87 =================
88
88
89 Note: Only one decorator is supported per input. If more than one decorator
89 Note: Only one decorator is supported per input. If more than one decorator
90 is specified, then only the last one is used.
90 is specified, then only the last one is used.
91
91
92 In addition to the Pseudo-Decorators/options described at the above link,
92 In addition to the Pseudo-Decorators/options described at the above link,
93 several enhancements have been made. The directive will emit a message to the
93 several enhancements have been made. The directive will emit a message to the
94 console at build-time if code-execution resulted in an exception or warning.
94 console at build-time if code-execution resulted in an exception or warning.
95 You can suppress these on a per-block basis by specifying the :okexcept:
95 You can suppress these on a per-block basis by specifying the :okexcept:
96 or :okwarning: options:
96 or :okwarning: options:
97
97
98 .. code-block:: rst
98 .. code-block:: rst
99
99
100 .. ipython::
100 .. ipython::
101 :okexcept:
101 :okexcept:
102 :okwarning:
102 :okwarning:
103
103
104 In [1]: 1/0
104 In [1]: 1/0
105 In [2]: # raise warning.
105 In [2]: # raise warning.
106
106
107 ToDo
107 ToDo
108 ----
108 ----
109
109
110 - Turn the ad-hoc test() function into a real test suite.
110 - Turn the ad-hoc test() function into a real test suite.
111 - Break up ipython-specific functionality from matplotlib stuff into better
111 - Break up ipython-specific functionality from matplotlib stuff into better
112 separated code.
112 separated code.
113
113
114 Authors
114 Authors
115 -------
115 -------
116
116
117 - John D Hunter: orignal author.
117 - John D Hunter: orignal author.
118 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
118 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
119 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
119 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
120 - Skipper Seabold, refactoring, cleanups, pure python addition
120 - Skipper Seabold, refactoring, cleanups, pure python addition
121 """
121 """
122 from __future__ import print_function
122 from __future__ import print_function
123
123
124 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
125 # Imports
125 # Imports
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127
127
128 # Stdlib
128 # Stdlib
129 import atexit
129 import atexit
130 import os
130 import os
131 import re
131 import re
132 import sys
132 import sys
133 import tempfile
133 import tempfile
134 import ast
134 import ast
135 import warnings
135 import warnings
136 import shutil
136 import shutil
137
137
138
138
139 # Third-party
139 # Third-party
140 from docutils.parsers.rst import directives
140 from docutils.parsers.rst import directives
141 from sphinx.util.compat import Directive
141 from sphinx.util.compat import Directive
142
142
143 # Our own
143 # Our own
144 from traitlets.config import Config
144 from traitlets.config import Config
145 from IPython import InteractiveShell
145 from IPython import InteractiveShell
146 from IPython.core.profiledir import ProfileDir
146 from IPython.core.profiledir import ProfileDir
147 from IPython.utils import io
147 from IPython.utils import io
148 from IPython.utils.py3compat import PY3
148 from IPython.utils.py3compat import PY3
149
149
150 if PY3:
150 if PY3:
151 from io import StringIO
151 from io import StringIO
152 else:
152 else:
153 from StringIO import StringIO
153 from StringIO import StringIO
154
154
155 #-----------------------------------------------------------------------------
155 #-----------------------------------------------------------------------------
156 # Globals
156 # Globals
157 #-----------------------------------------------------------------------------
157 #-----------------------------------------------------------------------------
158 # for tokenizing blocks
158 # for tokenizing blocks
159 COMMENT, INPUT, OUTPUT = range(3)
159 COMMENT, INPUT, OUTPUT = range(3)
160
160
161 #-----------------------------------------------------------------------------
161 #-----------------------------------------------------------------------------
162 # Functions and class declarations
162 # Functions and class declarations
163 #-----------------------------------------------------------------------------
163 #-----------------------------------------------------------------------------
164
164
165 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
165 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
166 """
166 """
167 part is a string of ipython text, comprised of at most one
167 part is a string of ipython text, comprised of at most one
168 input, one output, comments, and blank lines. The block parser
168 input, one output, comments, and blank lines. The block parser
169 parses the text into a list of::
169 parses the text into a list of::
170
170
171 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
171 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
172
172
173 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
173 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
174 data is, depending on the type of token::
174 data is, depending on the type of token::
175
175
176 COMMENT : the comment string
176 COMMENT : the comment string
177
177
178 INPUT: the (DECORATOR, INPUT_LINE, REST) where
178 INPUT: the (DECORATOR, INPUT_LINE, REST) where
179 DECORATOR: the input decorator (or None)
179 DECORATOR: the input decorator (or None)
180 INPUT_LINE: the input as string (possibly multi-line)
180 INPUT_LINE: the input as string (possibly multi-line)
181 REST : any stdout generated by the input line (not OUTPUT)
181 REST : any stdout generated by the input line (not OUTPUT)
182
182
183 OUTPUT: the output string, possibly multi-line
183 OUTPUT: the output string, possibly multi-line
184
184
185 """
185 """
186 block = []
186 block = []
187 lines = part.split('\n')
187 lines = part.split('\n')
188 N = len(lines)
188 N = len(lines)
189 i = 0
189 i = 0
190 decorator = None
190 decorator = None
191 while 1:
191 while 1:
192
192
193 if i==N:
193 if i==N:
194 # nothing left to parse -- the last line
194 # nothing left to parse -- the last line
195 break
195 break
196
196
197 line = lines[i]
197 line = lines[i]
198 i += 1
198 i += 1
199 line_stripped = line.strip()
199 line_stripped = line.strip()
200 if line_stripped.startswith('#'):
200 if line_stripped.startswith('#'):
201 block.append((COMMENT, line))
201 block.append((COMMENT, line))
202 continue
202 continue
203
203
204 if line_stripped.startswith('@'):
204 if line_stripped.startswith('@'):
205 # Here is where we assume there is, at most, one decorator.
205 # Here is where we assume there is, at most, one decorator.
206 # Might need to rethink this.
206 # Might need to rethink this.
207 decorator = line_stripped
207 decorator = line_stripped
208 continue
208 continue
209
209
210 # does this look like an input line?
210 # does this look like an input line?
211 matchin = rgxin.match(line)
211 matchin = rgxin.match(line)
212 if matchin:
212 if matchin:
213 lineno, inputline = int(matchin.group(1)), matchin.group(2)
213 lineno, inputline = int(matchin.group(1)), matchin.group(2)
214
214
215 # the ....: continuation string
215 # the ....: continuation string
216 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
216 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
217 Nc = len(continuation)
217 Nc = len(continuation)
218 # input lines can continue on for more than one line, if
218 # input lines can continue on for more than one line, if
219 # we have a '\' line continuation char or a function call
219 # we have a '\' line continuation char or a function call
220 # echo line 'print'. The input line can only be
220 # echo line 'print'. The input line can only be
221 # terminated by the end of the block or an output line, so
221 # terminated by the end of the block or an output line, so
222 # we parse out the rest of the input line if it is
222 # we parse out the rest of the input line if it is
223 # multiline as well as any echo text
223 # multiline as well as any echo text
224
224
225 rest = []
225 rest = []
226 while i<N:
226 while i<N:
227
227
228 # look ahead; if the next line is blank, or a comment, or
228 # look ahead; if the next line is blank, or a comment, or
229 # an output line, we're done
229 # an output line, we're done
230
230
231 nextline = lines[i]
231 nextline = lines[i]
232 matchout = rgxout.match(nextline)
232 matchout = rgxout.match(nextline)
233 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
233 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
234 if matchout or nextline.startswith('#'):
234 if matchout or nextline.startswith('#'):
235 break
235 break
236 elif nextline.startswith(continuation):
236 elif nextline.startswith(continuation):
237 # The default ipython_rgx* treat the space following the colon as optional.
237 # The default ipython_rgx* treat the space following the colon as optional.
238 # However, If the space is there we must consume it or code
238 # However, If the space is there we must consume it or code
239 # employing the cython_magic extension will fail to execute.
239 # employing the cython_magic extension will fail to execute.
240 #
240 #
241 # This works with the default ipython_rgx* patterns,
241 # This works with the default ipython_rgx* patterns,
242 # If you modify them, YMMV.
242 # If you modify them, YMMV.
243 nextline = nextline[Nc:]
243 nextline = nextline[Nc:]
244 if nextline and nextline[0] == ' ':
244 if nextline and nextline[0] == ' ':
245 nextline = nextline[1:]
245 nextline = nextline[1:]
246
246
247 inputline += '\n' + nextline
247 inputline += '\n' + nextline
248 else:
248 else:
249 rest.append(nextline)
249 rest.append(nextline)
250 i+= 1
250 i+= 1
251
251
252 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
252 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
253 continue
253 continue
254
254
255 # if it looks like an output line grab all the text to the end
255 # if it looks like an output line grab all the text to the end
256 # of the block
256 # of the block
257 matchout = rgxout.match(line)
257 matchout = rgxout.match(line)
258 if matchout:
258 if matchout:
259 lineno, output = int(matchout.group(1)), matchout.group(2)
259 lineno, output = int(matchout.group(1)), matchout.group(2)
260 if i<N-1:
260 if i<N-1:
261 output = '\n'.join([output] + lines[i:])
261 output = '\n'.join([output] + lines[i:])
262
262
263 block.append((OUTPUT, output))
263 block.append((OUTPUT, output))
264 break
264 break
265
265
266 return block
266 return block
267
267
268
268
269 class EmbeddedSphinxShell(object):
269 class EmbeddedSphinxShell(object):
270 """An embedded IPython instance to run inside Sphinx"""
270 """An embedded IPython instance to run inside Sphinx"""
271
271
272 def __init__(self, exec_lines=None):
272 def __init__(self, exec_lines=None):
273
273
274 self.cout = StringIO()
274 self.cout = StringIO()
275
275
276 if exec_lines is None:
276 if exec_lines is None:
277 exec_lines = []
277 exec_lines = []
278
278
279 # Create config object for IPython
279 # Create config object for IPython
280 config = Config()
280 config = Config()
281 config.HistoryManager.hist_file = ':memory:'
281 config.HistoryManager.hist_file = ':memory:'
282 config.InteractiveShell.autocall = False
282 config.InteractiveShell.autocall = False
283 config.InteractiveShell.autoindent = False
283 config.InteractiveShell.autoindent = False
284 config.InteractiveShell.colors = 'NoColor'
284 config.InteractiveShell.colors = 'NoColor'
285
285
286 # create a profile so instance history isn't saved
286 # create a profile so instance history isn't saved
287 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
287 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
288 profname = 'auto_profile_sphinx_build'
288 profname = 'auto_profile_sphinx_build'
289 pdir = os.path.join(tmp_profile_dir,profname)
289 pdir = os.path.join(tmp_profile_dir,profname)
290 profile = ProfileDir.create_profile_dir(pdir)
290 profile = ProfileDir.create_profile_dir(pdir)
291
291
292 # Create and initialize global ipython, but don't start its mainloop.
292 # Create and initialize global ipython, but don't start its mainloop.
293 # This will persist across different EmbededSphinxShell instances.
293 # This will persist across different EmbededSphinxShell instances.
294 IP = InteractiveShell.instance(config=config, profile_dir=profile)
294 IP = InteractiveShell.instance(config=config, profile_dir=profile)
295 atexit.register(self.cleanup)
295 atexit.register(self.cleanup)
296
296
297 # io.stdout redirect must be done after instantiating InteractiveShell
297 # io.stdout redirect must be done after instantiating InteractiveShell
298 io.stdout = self.cout
298 io.stdout = self.cout
299 io.stderr = self.cout
299 io.stderr = self.cout
300
300
301 # For debugging, so we can see normal output, use this:
301 # For debugging, so we can see normal output, use this:
302 #from IPython.utils.io import Tee
302 #from IPython.utils.io import Tee
303 #io.stdout = Tee(self.cout, channel='stdout') # dbg
303 #io.stdout = Tee(self.cout, channel='stdout') # dbg
304 #io.stderr = Tee(self.cout, channel='stderr') # dbg
304 #io.stderr = Tee(self.cout, channel='stderr') # dbg
305
305
306 # Store a few parts of IPython we'll need.
306 # Store a few parts of IPython we'll need.
307 self.IP = IP
307 self.IP = IP
308 self.user_ns = self.IP.user_ns
308 self.user_ns = self.IP.user_ns
309 self.user_global_ns = self.IP.user_global_ns
309 self.user_global_ns = self.IP.user_global_ns
310
310
311 self.input = ''
311 self.input = ''
312 self.output = ''
312 self.output = ''
313 self.tmp_profile_dir = tmp_profile_dir
313 self.tmp_profile_dir = tmp_profile_dir
314
314
315 self.is_verbatim = False
315 self.is_verbatim = False
316 self.is_doctest = False
316 self.is_doctest = False
317 self.is_suppress = False
317 self.is_suppress = False
318
318
319 # Optionally, provide more detailed information to shell.
319 # Optionally, provide more detailed information to shell.
320 # this is assigned by the SetUp method of IPythonDirective
320 # this is assigned by the SetUp method of IPythonDirective
321 # to point at itself.
321 # to point at itself.
322 #
322 #
323 # So, you can access handy things at self.directive.state
323 # So, you can access handy things at self.directive.state
324 self.directive = None
324 self.directive = None
325
325
326 # on the first call to the savefig decorator, we'll import
326 # on the first call to the savefig decorator, we'll import
327 # pyplot as plt so we can make a call to the plt.gcf().savefig
327 # pyplot as plt so we can make a call to the plt.gcf().savefig
328 self._pyplot_imported = False
328 self._pyplot_imported = False
329
329
330 # Prepopulate the namespace.
330 # Prepopulate the namespace.
331 for line in exec_lines:
331 for line in exec_lines:
332 self.process_input_line(line, store_history=False)
332 self.process_input_line(line, store_history=False)
333
333
334 def cleanup(self):
334 def cleanup(self):
335 shutil.rmtree(self.tmp_profile_dir, ignore_errors=True)
335 shutil.rmtree(self.tmp_profile_dir, ignore_errors=True)
336
336
337 def clear_cout(self):
337 def clear_cout(self):
338 self.cout.seek(0)
338 self.cout.seek(0)
339 self.cout.truncate(0)
339 self.cout.truncate(0)
340
340
341 def process_input_line(self, line, store_history=True):
341 def process_input_line(self, line, store_history=True):
342 """process the input, capturing stdout"""
342 """process the input, capturing stdout"""
343
343
344 stdout = sys.stdout
344 stdout = sys.stdout
345 splitter = self.IP.input_splitter
345 splitter = self.IP.input_splitter
346 try:
346 try:
347 sys.stdout = self.cout
347 sys.stdout = self.cout
348 splitter.push(line)
348 splitter.push(line)
349 more = splitter.push_accepts_more()
349 more = splitter.push_accepts_more()
350 if not more:
350 if not more:
351 source_raw = splitter.raw_reset()
351 source_raw = splitter.raw_reset()
352 self.IP.run_cell(source_raw, store_history=store_history)
352 self.IP.run_cell(source_raw, store_history=store_history)
353 finally:
353 finally:
354 sys.stdout = stdout
354 sys.stdout = stdout
355
355
356 def process_image(self, decorator):
356 def process_image(self, decorator):
357 """
357 """
358 # build out an image directive like
358 # build out an image directive like
359 # .. image:: somefile.png
359 # .. image:: somefile.png
360 # :width 4in
360 # :width 4in
361 #
361 #
362 # from an input like
362 # from an input like
363 # savefig somefile.png width=4in
363 # savefig somefile.png width=4in
364 """
364 """
365 savefig_dir = self.savefig_dir
365 savefig_dir = self.savefig_dir
366 source_dir = self.source_dir
366 source_dir = self.source_dir
367 saveargs = decorator.split(' ')
367 saveargs = decorator.split(' ')
368 filename = saveargs[1]
368 filename = saveargs[1]
369 # insert relative path to image file in source
369 # insert relative path to image file in source
370 outfile = os.path.relpath(os.path.join(savefig_dir,filename),
370 outfile = os.path.relpath(os.path.join(savefig_dir,filename),
371 source_dir)
371 source_dir)
372
372
373 imagerows = ['.. image:: %s'%outfile]
373 imagerows = ['.. image:: %s'%outfile]
374
374
375 for kwarg in saveargs[2:]:
375 for kwarg in saveargs[2:]:
376 arg, val = kwarg.split('=')
376 arg, val = kwarg.split('=')
377 arg = arg.strip()
377 arg = arg.strip()
378 val = val.strip()
378 val = val.strip()
379 imagerows.append(' :%s: %s'%(arg, val))
379 imagerows.append(' :%s: %s'%(arg, val))
380
380
381 image_file = os.path.basename(outfile) # only return file name
381 image_file = os.path.basename(outfile) # only return file name
382 image_directive = '\n'.join(imagerows)
382 image_directive = '\n'.join(imagerows)
383 return image_file, image_directive
383 return image_file, image_directive
384
384
385 # Callbacks for each type of token
385 # Callbacks for each type of token
386 def process_input(self, data, input_prompt, lineno):
386 def process_input(self, data, input_prompt, lineno):
387 """
387 """
388 Process data block for INPUT token.
388 Process data block for INPUT token.
389
389
390 """
390 """
391 decorator, input, rest = data
391 decorator, input, rest = data
392 image_file = None
392 image_file = None
393 image_directive = None
393 image_directive = None
394
394
395 is_verbatim = decorator=='@verbatim' or self.is_verbatim
395 is_verbatim = decorator=='@verbatim' or self.is_verbatim
396 is_doctest = (decorator is not None and \
396 is_doctest = (decorator is not None and \
397 decorator.startswith('@doctest')) or self.is_doctest
397 decorator.startswith('@doctest')) or self.is_doctest
398 is_suppress = decorator=='@suppress' or self.is_suppress
398 is_suppress = decorator=='@suppress' or self.is_suppress
399 is_okexcept = decorator=='@okexcept' or self.is_okexcept
399 is_okexcept = decorator=='@okexcept' or self.is_okexcept
400 is_okwarning = decorator=='@okwarning' or self.is_okwarning
400 is_okwarning = decorator=='@okwarning' or self.is_okwarning
401 is_savefig = decorator is not None and \
401 is_savefig = decorator is not None and \
402 decorator.startswith('@savefig')
402 decorator.startswith('@savefig')
403
403
404 input_lines = input.split('\n')
404 input_lines = input.split('\n')
405 if len(input_lines) > 1:
405 if len(input_lines) > 1:
406 if input_lines[-1] != "":
406 if input_lines[-1] != "":
407 input_lines.append('') # make sure there's a blank line
407 input_lines.append('') # make sure there's a blank line
408 # so splitter buffer gets reset
408 # so splitter buffer gets reset
409
409
410 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
410 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
411
411
412 if is_savefig:
412 if is_savefig:
413 image_file, image_directive = self.process_image(decorator)
413 image_file, image_directive = self.process_image(decorator)
414
414
415 ret = []
415 ret = []
416 is_semicolon = False
416 is_semicolon = False
417
417
418 # Hold the execution count, if requested to do so.
418 # Hold the execution count, if requested to do so.
419 if is_suppress and self.hold_count:
419 if is_suppress and self.hold_count:
420 store_history = False
420 store_history = False
421 else:
421 else:
422 store_history = True
422 store_history = True
423
423
424 # Note: catch_warnings is not thread safe
424 # Note: catch_warnings is not thread safe
425 with warnings.catch_warnings(record=True) as ws:
425 with warnings.catch_warnings(record=True) as ws:
426 for i, line in enumerate(input_lines):
426 for i, line in enumerate(input_lines):
427 if line.endswith(';'):
427 if line.endswith(';'):
428 is_semicolon = True
428 is_semicolon = True
429
429
430 if i == 0:
430 if i == 0:
431 # process the first input line
431 # process the first input line
432 if is_verbatim:
432 if is_verbatim:
433 self.process_input_line('')
433 self.process_input_line('')
434 self.IP.execution_count += 1 # increment it anyway
434 self.IP.execution_count += 1 # increment it anyway
435 else:
435 else:
436 # only submit the line in non-verbatim mode
436 # only submit the line in non-verbatim mode
437 self.process_input_line(line, store_history=store_history)
437 self.process_input_line(line, store_history=store_history)
438 formatted_line = '%s %s'%(input_prompt, line)
438 formatted_line = '%s %s'%(input_prompt, line)
439 else:
439 else:
440 # process a continuation line
440 # process a continuation line
441 if not is_verbatim:
441 if not is_verbatim:
442 self.process_input_line(line, store_history=store_history)
442 self.process_input_line(line, store_history=store_history)
443
443
444 formatted_line = '%s %s'%(continuation, line)
444 formatted_line = '%s %s'%(continuation, line)
445
445
446 if not is_suppress:
446 if not is_suppress:
447 ret.append(formatted_line)
447 ret.append(formatted_line)
448
448
449 if not is_suppress and len(rest.strip()) and is_verbatim:
449 if not is_suppress and len(rest.strip()) and is_verbatim:
450 # The "rest" is the standard output of the input. This needs to be
450 # The "rest" is the standard output of the input. This needs to be
451 # added when in verbatim mode. If there is no "rest", then we don't
451 # added when in verbatim mode. If there is no "rest", then we don't
452 # add it, as the new line will be added by the processed output.
452 # add it, as the new line will be added by the processed output.
453 ret.append(rest)
453 ret.append(rest)
454
454
455 # Fetch the processed output. (This is not the submitted output.)
455 # Fetch the processed output. (This is not the submitted output.)
456 self.cout.seek(0)
456 self.cout.seek(0)
457 processed_output = self.cout.read()
457 processed_output = self.cout.read()
458 if not is_suppress and not is_semicolon:
458 if not is_suppress and not is_semicolon:
459 #
459 #
460 # In IPythonDirective.run, the elements of `ret` are eventually
460 # In IPythonDirective.run, the elements of `ret` are eventually
461 # combined such that '' entries correspond to newlines. So if
461 # combined such that '' entries correspond to newlines. So if
462 # `processed_output` is equal to '', then the adding it to `ret`
462 # `processed_output` is equal to '', then the adding it to `ret`
463 # ensures that there is a blank line between consecutive inputs
463 # ensures that there is a blank line between consecutive inputs
464 # that have no outputs, as in:
464 # that have no outputs, as in:
465 #
465 #
466 # In [1]: x = 4
466 # In [1]: x = 4
467 #
467 #
468 # In [2]: x = 5
468 # In [2]: x = 5
469 #
469 #
470 # When there is processed output, it has a '\n' at the tail end. So
470 # When there is processed output, it has a '\n' at the tail end. So
471 # adding the output to `ret` will provide the necessary spacing
471 # adding the output to `ret` will provide the necessary spacing
472 # between consecutive input/output blocks, as in:
472 # between consecutive input/output blocks, as in:
473 #
473 #
474 # In [1]: x
474 # In [1]: x
475 # Out[1]: 5
475 # Out[1]: 5
476 #
476 #
477 # In [2]: x
477 # In [2]: x
478 # Out[2]: 5
478 # Out[2]: 5
479 #
479 #
480 # When there is stdout from the input, it also has a '\n' at the
480 # When there is stdout from the input, it also has a '\n' at the
481 # tail end, and so this ensures proper spacing as well. E.g.:
481 # tail end, and so this ensures proper spacing as well. E.g.:
482 #
482 #
483 # In [1]: print x
483 # In [1]: print x
484 # 5
484 # 5
485 #
485 #
486 # In [2]: x = 5
486 # In [2]: x = 5
487 #
487 #
488 # When in verbatim mode, `processed_output` is empty (because
488 # When in verbatim mode, `processed_output` is empty (because
489 # nothing was passed to IP. Sometimes the submitted code block has
489 # nothing was passed to IP. Sometimes the submitted code block has
490 # an Out[] portion and sometimes it does not. When it does not, we
490 # an Out[] portion and sometimes it does not. When it does not, we
491 # need to ensure proper spacing, so we have to add '' to `ret`.
491 # need to ensure proper spacing, so we have to add '' to `ret`.
492 # However, if there is an Out[] in the submitted code, then we do
492 # However, if there is an Out[] in the submitted code, then we do
493 # not want to add a newline as `process_output` has stuff to add.
493 # not want to add a newline as `process_output` has stuff to add.
494 # The difficulty is that `process_input` doesn't know if
494 # The difficulty is that `process_input` doesn't know if
495 # `process_output` will be called---so it doesn't know if there is
495 # `process_output` will be called---so it doesn't know if there is
496 # Out[] in the code block. The requires that we include a hack in
496 # Out[] in the code block. The requires that we include a hack in
497 # `process_block`. See the comments there.
497 # `process_block`. See the comments there.
498 #
498 #
499 ret.append(processed_output)
499 ret.append(processed_output)
500 elif is_semicolon:
500 elif is_semicolon:
501 # Make sure there is a newline after the semicolon.
501 # Make sure there is a newline after the semicolon.
502 ret.append('')
502 ret.append('')
503
503
504 # context information
504 # context information
505 filename = "Unknown"
505 filename = "Unknown"
506 lineno = 0
506 lineno = 0
507 if self.directive.state:
507 if self.directive.state:
508 filename = self.directive.state.document.current_source
508 filename = self.directive.state.document.current_source
509 lineno = self.directive.state.document.current_line
509 lineno = self.directive.state.document.current_line
510
510
511 # output any exceptions raised during execution to stdout
511 # output any exceptions raised during execution to stdout
512 # unless :okexcept: has been specified.
512 # unless :okexcept: has been specified.
513 if not is_okexcept and "Traceback" in processed_output:
513 if not is_okexcept and "Traceback" in processed_output:
514 s = "\nException in %s at block ending on line %s\n" % (filename, lineno)
514 s = "\nException in %s at block ending on line %s\n" % (filename, lineno)
515 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
515 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
516 sys.stdout.write('\n\n>>>' + ('-' * 73))
516 sys.stdout.write('\n\n>>>' + ('-' * 73))
517 sys.stdout.write(s)
517 sys.stdout.write(s)
518 sys.stdout.write(processed_output)
518 sys.stdout.write(processed_output)
519 sys.stdout.write('<<<' + ('-' * 73) + '\n\n')
519 sys.stdout.write('<<<' + ('-' * 73) + '\n\n')
520
520
521 # output any warning raised during execution to stdout
521 # output any warning raised during execution to stdout
522 # unless :okwarning: has been specified.
522 # unless :okwarning: has been specified.
523 if not is_okwarning:
523 if not is_okwarning:
524 for w in ws:
524 for w in ws:
525 s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno)
525 s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno)
526 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
526 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
527 sys.stdout.write('\n\n>>>' + ('-' * 73))
527 sys.stdout.write('\n\n>>>' + ('-' * 73))
528 sys.stdout.write(s)
528 sys.stdout.write(s)
529 sys.stdout.write(('-' * 76) + '\n')
529 sys.stdout.write(('-' * 76) + '\n')
530 s=warnings.formatwarning(w.message, w.category,
530 s=warnings.formatwarning(w.message, w.category,
531 w.filename, w.lineno, w.line)
531 w.filename, w.lineno, w.line)
532 sys.stdout.write(s)
532 sys.stdout.write(s)
533 sys.stdout.write('<<<' + ('-' * 73) + '\n')
533 sys.stdout.write('<<<' + ('-' * 73) + '\n')
534
534
535 self.cout.truncate(0)
535 self.cout.truncate(0)
536
536
537 return (ret, input_lines, processed_output,
537 return (ret, input_lines, processed_output,
538 is_doctest, decorator, image_file, image_directive)
538 is_doctest, decorator, image_file, image_directive)
539
539
540
540
541 def process_output(self, data, output_prompt, input_lines, output,
541 def process_output(self, data, output_prompt, input_lines, output,
542 is_doctest, decorator, image_file):
542 is_doctest, decorator, image_file):
543 """
543 """
544 Process data block for OUTPUT token.
544 Process data block for OUTPUT token.
545
545
546 """
546 """
547 # Recall: `data` is the submitted output, and `output` is the processed
547 # Recall: `data` is the submitted output, and `output` is the processed
548 # output from `input_lines`.
548 # output from `input_lines`.
549
549
550 TAB = ' ' * 4
550 TAB = ' ' * 4
551
551
552 if is_doctest and output is not None:
552 if is_doctest and output is not None:
553
553
554 found = output # This is the processed output
554 found = output # This is the processed output
555 found = found.strip()
555 found = found.strip()
556 submitted = data.strip()
556 submitted = data.strip()
557
557
558 if self.directive is None:
558 if self.directive is None:
559 source = 'Unavailable'
559 source = 'Unavailable'
560 content = 'Unavailable'
560 content = 'Unavailable'
561 else:
561 else:
562 source = self.directive.state.document.current_source
562 source = self.directive.state.document.current_source
563 content = self.directive.content
563 content = self.directive.content
564 # Add tabs and join into a single string.
564 # Add tabs and join into a single string.
565 content = '\n'.join([TAB + line for line in content])
565 content = '\n'.join([TAB + line for line in content])
566
566
567 # Make sure the output contains the output prompt.
567 # Make sure the output contains the output prompt.
568 ind = found.find(output_prompt)
568 ind = found.find(output_prompt)
569 if ind < 0:
569 if ind < 0:
570 e = ('output does not contain output prompt\n\n'
570 e = ('output does not contain output prompt\n\n'
571 'Document source: {0}\n\n'
571 'Document source: {0}\n\n'
572 'Raw content: \n{1}\n\n'
572 'Raw content: \n{1}\n\n'
573 'Input line(s):\n{TAB}{2}\n\n'
573 'Input line(s):\n{TAB}{2}\n\n'
574 'Output line(s):\n{TAB}{3}\n\n')
574 'Output line(s):\n{TAB}{3}\n\n')
575 e = e.format(source, content, '\n'.join(input_lines),
575 e = e.format(source, content, '\n'.join(input_lines),
576 repr(found), TAB=TAB)
576 repr(found), TAB=TAB)
577 raise RuntimeError(e)
577 raise RuntimeError(e)
578 found = found[len(output_prompt):].strip()
578 found = found[len(output_prompt):].strip()
579
579
580 # Handle the actual doctest comparison.
580 # Handle the actual doctest comparison.
581 if decorator.strip() == '@doctest':
581 if decorator.strip() == '@doctest':
582 # Standard doctest
582 # Standard doctest
583 if found != submitted:
583 if found != submitted:
584 e = ('doctest failure\n\n'
584 e = ('doctest failure\n\n'
585 'Document source: {0}\n\n'
585 'Document source: {0}\n\n'
586 'Raw content: \n{1}\n\n'
586 'Raw content: \n{1}\n\n'
587 'On input line(s):\n{TAB}{2}\n\n'
587 'On input line(s):\n{TAB}{2}\n\n'
588 'we found output:\n{TAB}{3}\n\n'
588 'we found output:\n{TAB}{3}\n\n'
589 'instead of the expected:\n{TAB}{4}\n\n')
589 'instead of the expected:\n{TAB}{4}\n\n')
590 e = e.format(source, content, '\n'.join(input_lines),
590 e = e.format(source, content, '\n'.join(input_lines),
591 repr(found), repr(submitted), TAB=TAB)
591 repr(found), repr(submitted), TAB=TAB)
592 raise RuntimeError(e)
592 raise RuntimeError(e)
593 else:
593 else:
594 self.custom_doctest(decorator, input_lines, found, submitted)
594 self.custom_doctest(decorator, input_lines, found, submitted)
595
595
596 # When in verbatim mode, this holds additional submitted output
596 # When in verbatim mode, this holds additional submitted output
597 # to be written in the final Sphinx output.
597 # to be written in the final Sphinx output.
598 # https://github.com/ipython/ipython/issues/5776
598 # https://github.com/ipython/ipython/issues/5776
599 out_data = []
599 out_data = []
600
600
601 is_verbatim = decorator=='@verbatim' or self.is_verbatim
601 is_verbatim = decorator=='@verbatim' or self.is_verbatim
602 if is_verbatim and data.strip():
602 if is_verbatim and data.strip():
603 # Note that `ret` in `process_block` has '' as its last element if
603 # Note that `ret` in `process_block` has '' as its last element if
604 # the code block was in verbatim mode. So if there is no submitted
604 # the code block was in verbatim mode. So if there is no submitted
605 # output, then we will have proper spacing only if we do not add
605 # output, then we will have proper spacing only if we do not add
606 # an additional '' to `out_data`. This is why we condition on
606 # an additional '' to `out_data`. This is why we condition on
607 # `and data.strip()`.
607 # `and data.strip()`.
608
608
609 # The submitted output has no output prompt. If we want the
609 # The submitted output has no output prompt. If we want the
610 # prompt and the code to appear, we need to join them now
610 # prompt and the code to appear, we need to join them now
611 # instead of adding them separately---as this would create an
611 # instead of adding them separately---as this would create an
612 # undesired newline. How we do this ultimately depends on the
612 # undesired newline. How we do this ultimately depends on the
613 # format of the output regex. I'll do what works for the default
613 # format of the output regex. I'll do what works for the default
614 # prompt for now, and we might have to adjust if it doesn't work
614 # prompt for now, and we might have to adjust if it doesn't work
615 # in other cases. Finally, the submitted output does not have
615 # in other cases. Finally, the submitted output does not have
616 # a trailing newline, so we must add it manually.
616 # a trailing newline, so we must add it manually.
617 out_data.append("{0} {1}\n".format(output_prompt, data))
617 out_data.append("{0} {1}\n".format(output_prompt, data))
618
618
619 return out_data
619 return out_data
620
620
621 def process_comment(self, data):
621 def process_comment(self, data):
622 """Process data fPblock for COMMENT token."""
622 """Process data fPblock for COMMENT token."""
623 if not self.is_suppress:
623 if not self.is_suppress:
624 return [data]
624 return [data]
625
625
626 def save_image(self, image_file):
626 def save_image(self, image_file):
627 """
627 """
628 Saves the image file to disk.
628 Saves the image file to disk.
629 """
629 """
630 self.ensure_pyplot()
630 self.ensure_pyplot()
631 command = 'plt.gcf().savefig("%s")'%image_file
631 command = 'plt.gcf().savefig("%s")'%image_file
632 #print 'SAVEFIG', command # dbg
632 #print 'SAVEFIG', command # dbg
633 self.process_input_line('bookmark ipy_thisdir', store_history=False)
633 self.process_input_line('bookmark ipy_thisdir', store_history=False)
634 self.process_input_line('cd -b ipy_savedir', store_history=False)
634 self.process_input_line('cd -b ipy_savedir', store_history=False)
635 self.process_input_line(command, store_history=False)
635 self.process_input_line(command, store_history=False)
636 self.process_input_line('cd -b ipy_thisdir', store_history=False)
636 self.process_input_line('cd -b ipy_thisdir', store_history=False)
637 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
637 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
638 self.clear_cout()
638 self.clear_cout()
639
639
640 def process_block(self, block):
640 def process_block(self, block):
641 """
641 """
642 process block from the block_parser and return a list of processed lines
642 process block from the block_parser and return a list of processed lines
643 """
643 """
644 ret = []
644 ret = []
645 output = None
645 output = None
646 input_lines = None
646 input_lines = None
647 lineno = self.IP.execution_count
647 lineno = self.IP.execution_count
648
648
649 input_prompt = self.promptin % lineno
649 input_prompt = self.promptin % lineno
650 output_prompt = self.promptout % lineno
650 output_prompt = self.promptout % lineno
651 image_file = None
651 image_file = None
652 image_directive = None
652 image_directive = None
653
653
654 found_input = False
654 found_input = False
655 for token, data in block:
655 for token, data in block:
656 if token == COMMENT:
656 if token == COMMENT:
657 out_data = self.process_comment(data)
657 out_data = self.process_comment(data)
658 elif token == INPUT:
658 elif token == INPUT:
659 found_input = True
659 found_input = True
660 (out_data, input_lines, output, is_doctest,
660 (out_data, input_lines, output, is_doctest,
661 decorator, image_file, image_directive) = \
661 decorator, image_file, image_directive) = \
662 self.process_input(data, input_prompt, lineno)
662 self.process_input(data, input_prompt, lineno)
663 elif token == OUTPUT:
663 elif token == OUTPUT:
664 if not found_input:
664 if not found_input:
665
665
666 TAB = ' ' * 4
666 TAB = ' ' * 4
667 linenumber = 0
667 linenumber = 0
668 source = 'Unavailable'
668 source = 'Unavailable'
669 content = 'Unavailable'
669 content = 'Unavailable'
670 if self.directive:
670 if self.directive:
671 linenumber = self.directive.state.document.current_line
671 linenumber = self.directive.state.document.current_line
672 source = self.directive.state.document.current_source
672 source = self.directive.state.document.current_source
673 content = self.directive.content
673 content = self.directive.content
674 # Add tabs and join into a single string.
674 # Add tabs and join into a single string.
675 content = '\n'.join([TAB + line for line in content])
675 content = '\n'.join([TAB + line for line in content])
676
676
677 e = ('\n\nInvalid block: Block contains an output prompt '
677 e = ('\n\nInvalid block: Block contains an output prompt '
678 'without an input prompt.\n\n'
678 'without an input prompt.\n\n'
679 'Document source: {0}\n\n'
679 'Document source: {0}\n\n'
680 'Content begins at line {1}: \n\n{2}\n\n'
680 'Content begins at line {1}: \n\n{2}\n\n'
681 'Problematic block within content: \n\n{TAB}{3}\n\n')
681 'Problematic block within content: \n\n{TAB}{3}\n\n')
682 e = e.format(source, linenumber, content, block, TAB=TAB)
682 e = e.format(source, linenumber, content, block, TAB=TAB)
683
683
684 # Write, rather than include in exception, since Sphinx
684 # Write, rather than include in exception, since Sphinx
685 # will truncate tracebacks.
685 # will truncate tracebacks.
686 sys.stdout.write(e)
686 sys.stdout.write(e)
687 raise RuntimeError('An invalid block was detected.')
687 raise RuntimeError('An invalid block was detected.')
688
688
689 out_data = \
689 out_data = \
690 self.process_output(data, output_prompt, input_lines,
690 self.process_output(data, output_prompt, input_lines,
691 output, is_doctest, decorator,
691 output, is_doctest, decorator,
692 image_file)
692 image_file)
693 if out_data:
693 if out_data:
694 # Then there was user submitted output in verbatim mode.
694 # Then there was user submitted output in verbatim mode.
695 # We need to remove the last element of `ret` that was
695 # We need to remove the last element of `ret` that was
696 # added in `process_input`, as it is '' and would introduce
696 # added in `process_input`, as it is '' and would introduce
697 # an undesirable newline.
697 # an undesirable newline.
698 assert(ret[-1] == '')
698 assert(ret[-1] == '')
699 del ret[-1]
699 del ret[-1]
700
700
701 if out_data:
701 if out_data:
702 ret.extend(out_data)
702 ret.extend(out_data)
703
703
704 # save the image files
704 # save the image files
705 if image_file is not None:
705 if image_file is not None:
706 self.save_image(image_file)
706 self.save_image(image_file)
707
707
708 return ret, image_directive
708 return ret, image_directive
709
709
710 def ensure_pyplot(self):
710 def ensure_pyplot(self):
711 """
711 """
712 Ensures that pyplot has been imported into the embedded IPython shell.
712 Ensures that pyplot has been imported into the embedded IPython shell.
713
713
714 Also, makes sure to set the backend appropriately if not set already.
714 Also, makes sure to set the backend appropriately if not set already.
715
715
716 """
716 """
717 # We are here if the @figure pseudo decorator was used. Thus, it's
717 # We are here if the @figure pseudo decorator was used. Thus, it's
718 # possible that we could be here even if python_mplbackend were set to
718 # possible that we could be here even if python_mplbackend were set to
719 # `None`. That's also strange and perhaps worthy of raising an
719 # `None`. That's also strange and perhaps worthy of raising an
720 # exception, but for now, we just set the backend to 'agg'.
720 # exception, but for now, we just set the backend to 'agg'.
721
721
722 if not self._pyplot_imported:
722 if not self._pyplot_imported:
723 if 'matplotlib.backends' not in sys.modules:
723 if 'matplotlib.backends' not in sys.modules:
724 # Then ipython_matplotlib was set to None but there was a
724 # Then ipython_matplotlib was set to None but there was a
725 # call to the @figure decorator (and ipython_execlines did
725 # call to the @figure decorator (and ipython_execlines did
726 # not set a backend).
726 # not set a backend).
727 #raise Exception("No backend was set, but @figure was used!")
727 #raise Exception("No backend was set, but @figure was used!")
728 import matplotlib
728 import matplotlib
729 matplotlib.use('agg')
729 matplotlib.use('agg')
730
730
731 # Always import pyplot into embedded shell.
731 # Always import pyplot into embedded shell.
732 self.process_input_line('import matplotlib.pyplot as plt',
732 self.process_input_line('import matplotlib.pyplot as plt',
733 store_history=False)
733 store_history=False)
734 self._pyplot_imported = True
734 self._pyplot_imported = True
735
735
736 def process_pure_python(self, content):
736 def process_pure_python(self, content):
737 """
737 """
738 content is a list of strings. it is unedited directive content
738 content is a list of strings. it is unedited directive content
739
739
740 This runs it line by line in the InteractiveShell, prepends
740 This runs it line by line in the InteractiveShell, prepends
741 prompts as needed capturing stderr and stdout, then returns
741 prompts as needed capturing stderr and stdout, then returns
742 the content as a list as if it were ipython code
742 the content as a list as if it were ipython code
743 """
743 """
744 output = []
744 output = []
745 savefig = False # keep up with this to clear figure
745 savefig = False # keep up with this to clear figure
746 multiline = False # to handle line continuation
746 multiline = False # to handle line continuation
747 multiline_start = None
747 multiline_start = None
748 fmtin = self.promptin
748 fmtin = self.promptin
749
749
750 ct = 0
750 ct = 0
751
751
752 for lineno, line in enumerate(content):
752 for lineno, line in enumerate(content):
753
753
754 line_stripped = line.strip()
754 line_stripped = line.strip()
755 if not len(line):
755 if not len(line):
756 output.append(line)
756 output.append(line)
757 continue
757 continue
758
758
759 # handle decorators
759 # handle decorators
760 if line_stripped.startswith('@'):
760 if line_stripped.startswith('@'):
761 output.extend([line])
761 output.extend([line])
762 if 'savefig' in line:
762 if 'savefig' in line:
763 savefig = True # and need to clear figure
763 savefig = True # and need to clear figure
764 continue
764 continue
765
765
766 # handle comments
766 # handle comments
767 if line_stripped.startswith('#'):
767 if line_stripped.startswith('#'):
768 output.extend([line])
768 output.extend([line])
769 continue
769 continue
770
770
771 # deal with lines checking for multiline
771 # deal with lines checking for multiline
772 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
772 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
773 if not multiline:
773 if not multiline:
774 modified = u"%s %s" % (fmtin % ct, line_stripped)
774 modified = u"%s %s" % (fmtin % ct, line_stripped)
775 output.append(modified)
775 output.append(modified)
776 ct += 1
776 ct += 1
777 try:
777 try:
778 ast.parse(line_stripped)
778 ast.parse(line_stripped)
779 output.append(u'')
779 output.append(u'')
780 except Exception: # on a multiline
780 except Exception: # on a multiline
781 multiline = True
781 multiline = True
782 multiline_start = lineno
782 multiline_start = lineno
783 else: # still on a multiline
783 else: # still on a multiline
784 modified = u'%s %s' % (continuation, line)
784 modified = u'%s %s' % (continuation, line)
785 output.append(modified)
785 output.append(modified)
786
786
787 # if the next line is indented, it should be part of multiline
787 # if the next line is indented, it should be part of multiline
788 if len(content) > lineno + 1:
788 if len(content) > lineno + 1:
789 nextline = content[lineno + 1]
789 nextline = content[lineno + 1]
790 if len(nextline) - len(nextline.lstrip()) > 3:
790 if len(nextline) - len(nextline.lstrip()) > 3:
791 continue
791 continue
792 try:
792 try:
793 mod = ast.parse(
793 mod = ast.parse(
794 '\n'.join(content[multiline_start:lineno+1]))
794 '\n'.join(content[multiline_start:lineno+1]))
795 if isinstance(mod.body[0], ast.FunctionDef):
795 if isinstance(mod.body[0], ast.FunctionDef):
796 # check to see if we have the whole function
796 # check to see if we have the whole function
797 for element in mod.body[0].body:
797 for element in mod.body[0].body:
798 if isinstance(element, ast.Return):
798 if isinstance(element, ast.Return):
799 multiline = False
799 multiline = False
800 else:
800 else:
801 output.append(u'')
801 output.append(u'')
802 multiline = False
802 multiline = False
803 except Exception:
803 except Exception:
804 pass
804 pass
805
805
806 if savefig: # clear figure if plotted
806 if savefig: # clear figure if plotted
807 self.ensure_pyplot()
807 self.ensure_pyplot()
808 self.process_input_line('plt.clf()', store_history=False)
808 self.process_input_line('plt.clf()', store_history=False)
809 self.clear_cout()
809 self.clear_cout()
810 savefig = False
810 savefig = False
811
811
812 return output
812 return output
813
813
814 def custom_doctest(self, decorator, input_lines, found, submitted):
814 def custom_doctest(self, decorator, input_lines, found, submitted):
815 """
815 """
816 Perform a specialized doctest.
816 Perform a specialized doctest.
817
817
818 """
818 """
819 from .custom_doctests import doctests
819 from .custom_doctests import doctests
820
820
821 args = decorator.split()
821 args = decorator.split()
822 doctest_type = args[1]
822 doctest_type = args[1]
823 if doctest_type in doctests:
823 if doctest_type in doctests:
824 doctests[doctest_type](self, args, input_lines, found, submitted)
824 doctests[doctest_type](self, args, input_lines, found, submitted)
825 else:
825 else:
826 e = "Invalid option to @doctest: {0}".format(doctest_type)
826 e = "Invalid option to @doctest: {0}".format(doctest_type)
827 raise Exception(e)
827 raise Exception(e)
828
828
829
829
830 class IPythonDirective(Directive):
830 class IPythonDirective(Directive):
831
831
832 has_content = True
832 has_content = True
833 required_arguments = 0
833 required_arguments = 0
834 optional_arguments = 4 # python, suppress, verbatim, doctest
834 optional_arguments = 4 # python, suppress, verbatim, doctest
835 final_argumuent_whitespace = True
835 final_argumuent_whitespace = True
836 option_spec = { 'python': directives.unchanged,
836 option_spec = { 'python': directives.unchanged,
837 'suppress' : directives.flag,
837 'suppress' : directives.flag,
838 'verbatim' : directives.flag,
838 'verbatim' : directives.flag,
839 'doctest' : directives.flag,
839 'doctest' : directives.flag,
840 'okexcept': directives.flag,
840 'okexcept': directives.flag,
841 'okwarning': directives.flag
841 'okwarning': directives.flag
842 }
842 }
843
843
844 shell = None
844 shell = None
845
845
846 seen_docs = set()
846 seen_docs = set()
847
847
848 def get_config_options(self):
848 def get_config_options(self):
849 # contains sphinx configuration variables
849 # contains sphinx configuration variables
850 config = self.state.document.settings.env.config
850 config = self.state.document.settings.env.config
851
851
852 # get config variables to set figure output directory
852 # get config variables to set figure output directory
853 outdir = self.state.document.settings.env.app.outdir
853 outdir = self.state.document.settings.env.app.outdir
854 savefig_dir = config.ipython_savefig_dir
854 savefig_dir = config.ipython_savefig_dir
855 source_dir = os.path.dirname(self.state.document.current_source)
855 source_dir = os.path.dirname(self.state.document.current_source)
856 if savefig_dir is None:
856 if savefig_dir is None:
857 savefig_dir = config.html_static_path or '_static'
857 savefig_dir = config.html_static_path or '_static'
858 if isinstance(savefig_dir, list):
858 if isinstance(savefig_dir, list):
859 savefig_dir = os.path.join(*savefig_dir)
859 savefig_dir = os.path.join(*savefig_dir)
860 savefig_dir = os.path.join(outdir, savefig_dir)
860 savefig_dir = os.path.join(outdir, savefig_dir)
861
861
862 # get regex and prompt stuff
862 # get regex and prompt stuff
863 rgxin = config.ipython_rgxin
863 rgxin = config.ipython_rgxin
864 rgxout = config.ipython_rgxout
864 rgxout = config.ipython_rgxout
865 promptin = config.ipython_promptin
865 promptin = config.ipython_promptin
866 promptout = config.ipython_promptout
866 promptout = config.ipython_promptout
867 mplbackend = config.ipython_mplbackend
867 mplbackend = config.ipython_mplbackend
868 exec_lines = config.ipython_execlines
868 exec_lines = config.ipython_execlines
869 hold_count = config.ipython_holdcount
869 hold_count = config.ipython_holdcount
870
870
871 return (savefig_dir, source_dir, rgxin, rgxout,
871 return (savefig_dir, source_dir, rgxin, rgxout,
872 promptin, promptout, mplbackend, exec_lines, hold_count)
872 promptin, promptout, mplbackend, exec_lines, hold_count)
873
873
874 def setup(self):
874 def setup(self):
875 # Get configuration values.
875 # Get configuration values.
876 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
876 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
877 mplbackend, exec_lines, hold_count) = self.get_config_options()
877 mplbackend, exec_lines, hold_count) = self.get_config_options()
878
878
879 if self.shell is None:
879 if self.shell is None:
880 # We will be here many times. However, when the
880 # We will be here many times. However, when the
881 # EmbeddedSphinxShell is created, its interactive shell member
881 # EmbeddedSphinxShell is created, its interactive shell member
882 # is the same for each instance.
882 # is the same for each instance.
883
883
884 if mplbackend:
884 if mplbackend:
885 import matplotlib
885 import matplotlib
886 # Repeated calls to use() will not hurt us since `mplbackend`
886 # Repeated calls to use() will not hurt us since `mplbackend`
887 # is the same each time.
887 # is the same each time.
888 matplotlib.use(mplbackend)
888 matplotlib.use(mplbackend)
889
889
890 # Must be called after (potentially) importing matplotlib and
890 # Must be called after (potentially) importing matplotlib and
891 # setting its backend since exec_lines might import pylab.
891 # setting its backend since exec_lines might import pylab.
892 self.shell = EmbeddedSphinxShell(exec_lines)
892 self.shell = EmbeddedSphinxShell(exec_lines)
893
893
894 # Store IPython directive to enable better error messages
894 # Store IPython directive to enable better error messages
895 self.shell.directive = self
895 self.shell.directive = self
896
896
897 # reset the execution count if we haven't processed this doc
897 # reset the execution count if we haven't processed this doc
898 #NOTE: this may be borked if there are multiple seen_doc tmp files
898 #NOTE: this may be borked if there are multiple seen_doc tmp files
899 #check time stamp?
899 #check time stamp?
900 if not self.state.document.current_source in self.seen_docs:
900 if not self.state.document.current_source in self.seen_docs:
901 self.shell.IP.history_manager.reset()
901 self.shell.IP.history_manager.reset()
902 self.shell.IP.execution_count = 1
902 self.shell.IP.execution_count = 1
903 self.shell.IP.prompt_manager.width = 0
903 self.shell.IP.prompt_manager.width = 0
904 self.seen_docs.add(self.state.document.current_source)
904 self.seen_docs.add(self.state.document.current_source)
905
905
906 # and attach to shell so we don't have to pass them around
906 # and attach to shell so we don't have to pass them around
907 self.shell.rgxin = rgxin
907 self.shell.rgxin = rgxin
908 self.shell.rgxout = rgxout
908 self.shell.rgxout = rgxout
909 self.shell.promptin = promptin
909 self.shell.promptin = promptin
910 self.shell.promptout = promptout
910 self.shell.promptout = promptout
911 self.shell.savefig_dir = savefig_dir
911 self.shell.savefig_dir = savefig_dir
912 self.shell.source_dir = source_dir
912 self.shell.source_dir = source_dir
913 self.shell.hold_count = hold_count
913 self.shell.hold_count = hold_count
914
914
915 # setup bookmark for saving figures directory
915 # setup bookmark for saving figures directory
916 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
916 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
917 store_history=False)
917 store_history=False)
918 self.shell.clear_cout()
918 self.shell.clear_cout()
919
919
920 return rgxin, rgxout, promptin, promptout
920 return rgxin, rgxout, promptin, promptout
921
921
922 def teardown(self):
922 def teardown(self):
923 # delete last bookmark
923 # delete last bookmark
924 self.shell.process_input_line('bookmark -d ipy_savedir',
924 self.shell.process_input_line('bookmark -d ipy_savedir',
925 store_history=False)
925 store_history=False)
926 self.shell.clear_cout()
926 self.shell.clear_cout()
927
927
928 def run(self):
928 def run(self):
929 debug = False
929 debug = False
930
930
931 #TODO, any reason block_parser can't be a method of embeddable shell
931 #TODO, any reason block_parser can't be a method of embeddable shell
932 # then we wouldn't have to carry these around
932 # then we wouldn't have to carry these around
933 rgxin, rgxout, promptin, promptout = self.setup()
933 rgxin, rgxout, promptin, promptout = self.setup()
934
934
935 options = self.options
935 options = self.options
936 self.shell.is_suppress = 'suppress' in options
936 self.shell.is_suppress = 'suppress' in options
937 self.shell.is_doctest = 'doctest' in options
937 self.shell.is_doctest = 'doctest' in options
938 self.shell.is_verbatim = 'verbatim' in options
938 self.shell.is_verbatim = 'verbatim' in options
939 self.shell.is_okexcept = 'okexcept' in options
939 self.shell.is_okexcept = 'okexcept' in options
940 self.shell.is_okwarning = 'okwarning' in options
940 self.shell.is_okwarning = 'okwarning' in options
941
941
942 # handle pure python code
942 # handle pure python code
943 if 'python' in self.arguments:
943 if 'python' in self.arguments:
944 content = self.content
944 content = self.content
945 self.content = self.shell.process_pure_python(content)
945 self.content = self.shell.process_pure_python(content)
946
946
947 # parts consists of all text within the ipython-block.
947 # parts consists of all text within the ipython-block.
948 # Each part is an input/output block.
948 # Each part is an input/output block.
949 parts = '\n'.join(self.content).split('\n\n')
949 parts = '\n'.join(self.content).split('\n\n')
950
950
951 lines = ['.. code-block:: ipython', '']
951 lines = ['.. code-block:: ipython', '']
952 figures = []
952 figures = []
953
953
954 for part in parts:
954 for part in parts:
955 block = block_parser(part, rgxin, rgxout, promptin, promptout)
955 block = block_parser(part, rgxin, rgxout, promptin, promptout)
956 if len(block):
956 if len(block):
957 rows, figure = self.shell.process_block(block)
957 rows, figure = self.shell.process_block(block)
958 for row in rows:
958 for row in rows:
959 lines.extend([' {0}'.format(line)
959 lines.extend([' {0}'.format(line)
960 for line in row.split('\n')])
960 for line in row.split('\n')])
961
961
962 if figure is not None:
962 if figure is not None:
963 figures.append(figure)
963 figures.append(figure)
964
964
965 for figure in figures:
965 for figure in figures:
966 lines.append('')
966 lines.append('')
967 lines.extend(figure.split('\n'))
967 lines.extend(figure.split('\n'))
968 lines.append('')
968 lines.append('')
969
969
970 if len(lines) > 2:
970 if len(lines) > 2:
971 if debug:
971 if debug:
972 print('\n'.join(lines))
972 print('\n'.join(lines))
973 else:
973 else:
974 # This has to do with input, not output. But if we comment
974 # This has to do with input, not output. But if we comment
975 # these lines out, then no IPython code will appear in the
975 # these lines out, then no IPython code will appear in the
976 # final output.
976 # final output.
977 self.state_machine.insert_input(
977 self.state_machine.insert_input(
978 lines, self.state_machine.input_lines.source(0))
978 lines, self.state_machine.input_lines.source(0))
979
979
980 # cleanup
980 # cleanup
981 self.teardown()
981 self.teardown()
982
982
983 return []
983 return []
984
984
985 # Enable as a proper Sphinx directive
985 # Enable as a proper Sphinx directive
986 def setup(app):
986 def setup(app):
987 setup.app = app
987 setup.app = app
988
988
989 app.add_directive('ipython', IPythonDirective)
989 app.add_directive('ipython', IPythonDirective)
990 app.add_config_value('ipython_savefig_dir', None, 'env')
990 app.add_config_value('ipython_savefig_dir', None, 'env')
991 app.add_config_value('ipython_rgxin',
991 app.add_config_value('ipython_rgxin',
992 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
992 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
993 app.add_config_value('ipython_rgxout',
993 app.add_config_value('ipython_rgxout',
994 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
994 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
995 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
995 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
996 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
996 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
997
997
998 # We could just let matplotlib pick whatever is specified as the default
998 # We could just let matplotlib pick whatever is specified as the default
999 # backend in the matplotlibrc file, but this would cause issues if the
999 # backend in the matplotlibrc file, but this would cause issues if the
1000 # backend didn't work in headless environments. For this reason, 'agg'
1000 # backend didn't work in headless environments. For this reason, 'agg'
1001 # is a good default backend choice.
1001 # is a good default backend choice.
1002 app.add_config_value('ipython_mplbackend', 'agg', 'env')
1002 app.add_config_value('ipython_mplbackend', 'agg', 'env')
1003
1003
1004 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
1004 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
1005 # __init__ method will treat it as [].
1005 # __init__ method will treat it as [].
1006 execlines = ['import numpy as np', 'import matplotlib.pyplot as plt']
1006 execlines = ['import numpy as np', 'import matplotlib.pyplot as plt']
1007 app.add_config_value('ipython_execlines', execlines, 'env')
1007 app.add_config_value('ipython_execlines', execlines, 'env')
1008
1008
1009 app.add_config_value('ipython_holdcount', True, 'env')
1009 app.add_config_value('ipython_holdcount', True, 'env')
1010
1010
1011 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
1012 return metadata
1013
1011 # Simple smoke test, needs to be converted to a proper automatic test.
1014 # Simple smoke test, needs to be converted to a proper automatic test.
1012 def test():
1015 def test():
1013
1016
1014 examples = [
1017 examples = [
1015 r"""
1018 r"""
1016 In [9]: pwd
1019 In [9]: pwd
1017 Out[9]: '/home/jdhunter/py4science/book'
1020 Out[9]: '/home/jdhunter/py4science/book'
1018
1021
1019 In [10]: cd bookdata/
1022 In [10]: cd bookdata/
1020 /home/jdhunter/py4science/book/bookdata
1023 /home/jdhunter/py4science/book/bookdata
1021
1024
1022 In [2]: from pylab import *
1025 In [2]: from pylab import *
1023
1026
1024 In [2]: ion()
1027 In [2]: ion()
1025
1028
1026 In [3]: im = imread('stinkbug.png')
1029 In [3]: im = imread('stinkbug.png')
1027
1030
1028 @savefig mystinkbug.png width=4in
1031 @savefig mystinkbug.png width=4in
1029 In [4]: imshow(im)
1032 In [4]: imshow(im)
1030 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
1033 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
1031
1034
1032 """,
1035 """,
1033 r"""
1036 r"""
1034
1037
1035 In [1]: x = 'hello world'
1038 In [1]: x = 'hello world'
1036
1039
1037 # string methods can be
1040 # string methods can be
1038 # used to alter the string
1041 # used to alter the string
1039 @doctest
1042 @doctest
1040 In [2]: x.upper()
1043 In [2]: x.upper()
1041 Out[2]: 'HELLO WORLD'
1044 Out[2]: 'HELLO WORLD'
1042
1045
1043 @verbatim
1046 @verbatim
1044 In [3]: x.st<TAB>
1047 In [3]: x.st<TAB>
1045 x.startswith x.strip
1048 x.startswith x.strip
1046 """,
1049 """,
1047 r"""
1050 r"""
1048
1051
1049 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
1052 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
1050 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
1053 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
1051
1054
1052 In [131]: print url.split('&')
1055 In [131]: print url.split('&')
1053 ['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']
1056 ['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']
1054
1057
1055 In [60]: import urllib
1058 In [60]: import urllib
1056
1059
1057 """,
1060 """,
1058 r"""\
1061 r"""\
1059
1062
1060 In [133]: import numpy.random
1063 In [133]: import numpy.random
1061
1064
1062 @suppress
1065 @suppress
1063 In [134]: numpy.random.seed(2358)
1066 In [134]: numpy.random.seed(2358)
1064
1067
1065 @doctest
1068 @doctest
1066 In [135]: numpy.random.rand(10,2)
1069 In [135]: numpy.random.rand(10,2)
1067 Out[135]:
1070 Out[135]:
1068 array([[ 0.64524308, 0.59943846],
1071 array([[ 0.64524308, 0.59943846],
1069 [ 0.47102322, 0.8715456 ],
1072 [ 0.47102322, 0.8715456 ],
1070 [ 0.29370834, 0.74776844],
1073 [ 0.29370834, 0.74776844],
1071 [ 0.99539577, 0.1313423 ],
1074 [ 0.99539577, 0.1313423 ],
1072 [ 0.16250302, 0.21103583],
1075 [ 0.16250302, 0.21103583],
1073 [ 0.81626524, 0.1312433 ],
1076 [ 0.81626524, 0.1312433 ],
1074 [ 0.67338089, 0.72302393],
1077 [ 0.67338089, 0.72302393],
1075 [ 0.7566368 , 0.07033696],
1078 [ 0.7566368 , 0.07033696],
1076 [ 0.22591016, 0.77731835],
1079 [ 0.22591016, 0.77731835],
1077 [ 0.0072729 , 0.34273127]])
1080 [ 0.0072729 , 0.34273127]])
1078
1081
1079 """,
1082 """,
1080
1083
1081 r"""
1084 r"""
1082 In [106]: print x
1085 In [106]: print x
1083 jdh
1086 jdh
1084
1087
1085 In [109]: for i in range(10):
1088 In [109]: for i in range(10):
1086 .....: print i
1089 .....: print i
1087 .....:
1090 .....:
1088 .....:
1091 .....:
1089 0
1092 0
1090 1
1093 1
1091 2
1094 2
1092 3
1095 3
1093 4
1096 4
1094 5
1097 5
1095 6
1098 6
1096 7
1099 7
1097 8
1100 8
1098 9
1101 9
1099 """,
1102 """,
1100
1103
1101 r"""
1104 r"""
1102
1105
1103 In [144]: from pylab import *
1106 In [144]: from pylab import *
1104
1107
1105 In [145]: ion()
1108 In [145]: ion()
1106
1109
1107 # use a semicolon to suppress the output
1110 # use a semicolon to suppress the output
1108 @savefig test_hist.png width=4in
1111 @savefig test_hist.png width=4in
1109 In [151]: hist(np.random.randn(10000), 100);
1112 In [151]: hist(np.random.randn(10000), 100);
1110
1113
1111
1114
1112 @savefig test_plot.png width=4in
1115 @savefig test_plot.png width=4in
1113 In [151]: plot(np.random.randn(10000), 'o');
1116 In [151]: plot(np.random.randn(10000), 'o');
1114 """,
1117 """,
1115
1118
1116 r"""
1119 r"""
1117 # use a semicolon to suppress the output
1120 # use a semicolon to suppress the output
1118 In [151]: plt.clf()
1121 In [151]: plt.clf()
1119
1122
1120 @savefig plot_simple.png width=4in
1123 @savefig plot_simple.png width=4in
1121 In [151]: plot([1,2,3])
1124 In [151]: plot([1,2,3])
1122
1125
1123 @savefig hist_simple.png width=4in
1126 @savefig hist_simple.png width=4in
1124 In [151]: hist(np.random.randn(10000), 100);
1127 In [151]: hist(np.random.randn(10000), 100);
1125
1128
1126 """,
1129 """,
1127 r"""
1130 r"""
1128 # update the current fig
1131 # update the current fig
1129 In [151]: ylabel('number')
1132 In [151]: ylabel('number')
1130
1133
1131 In [152]: title('normal distribution')
1134 In [152]: title('normal distribution')
1132
1135
1133
1136
1134 @savefig hist_with_text.png
1137 @savefig hist_with_text.png
1135 In [153]: grid(True)
1138 In [153]: grid(True)
1136
1139
1137 @doctest float
1140 @doctest float
1138 In [154]: 0.1 + 0.2
1141 In [154]: 0.1 + 0.2
1139 Out[154]: 0.3
1142 Out[154]: 0.3
1140
1143
1141 @doctest float
1144 @doctest float
1142 In [155]: np.arange(16).reshape(4,4)
1145 In [155]: np.arange(16).reshape(4,4)
1143 Out[155]:
1146 Out[155]:
1144 array([[ 0, 1, 2, 3],
1147 array([[ 0, 1, 2, 3],
1145 [ 4, 5, 6, 7],
1148 [ 4, 5, 6, 7],
1146 [ 8, 9, 10, 11],
1149 [ 8, 9, 10, 11],
1147 [12, 13, 14, 15]])
1150 [12, 13, 14, 15]])
1148
1151
1149 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1152 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1150
1153
1151 In [2]: x[0,0] = np.inf
1154 In [2]: x[0,0] = np.inf
1152
1155
1153 In [3]: x[0,1] = np.nan
1156 In [3]: x[0,1] = np.nan
1154
1157
1155 @doctest float
1158 @doctest float
1156 In [4]: x
1159 In [4]: x
1157 Out[4]:
1160 Out[4]:
1158 array([[ inf, nan, 2., 3.],
1161 array([[ inf, nan, 2., 3.],
1159 [ 4., 5., 6., 7.],
1162 [ 4., 5., 6., 7.],
1160 [ 8., 9., 10., 11.],
1163 [ 8., 9., 10., 11.],
1161 [ 12., 13., 14., 15.]])
1164 [ 12., 13., 14., 15.]])
1162
1165
1163
1166
1164 """,
1167 """,
1165 ]
1168 ]
1166 # skip local-file depending first example:
1169 # skip local-file depending first example:
1167 examples = examples[1:]
1170 examples = examples[1:]
1168
1171
1169 #ipython_directive.DEBUG = True # dbg
1172 #ipython_directive.DEBUG = True # dbg
1170 #options = dict(suppress=True) # dbg
1173 #options = dict(suppress=True) # dbg
1171 options = dict()
1174 options = dict()
1172 for example in examples:
1175 for example in examples:
1173 content = example.split('\n')
1176 content = example.split('\n')
1174 IPythonDirective('debug', arguments=None, options=options,
1177 IPythonDirective('debug', arguments=None, options=options,
1175 content=content, lineno=0,
1178 content=content, lineno=0,
1176 content_offset=None, block_text=None,
1179 content_offset=None, block_text=None,
1177 state=None, state_machine=None,
1180 state=None, state_machine=None,
1178 )
1181 )
1179
1182
1180 # Run test suite as a script
1183 # Run test suite as a script
1181 if __name__=='__main__':
1184 if __name__=='__main__':
1182 if not os.path.isdir('_static'):
1185 if not os.path.isdir('_static'):
1183 os.mkdir('_static')
1186 os.mkdir('_static')
1184 test()
1187 test()
1185 print('All OK? Check figures in _static/')
1188 print('All OK? Check figures in _static/')
@@ -1,155 +1,157 b''
1 """Define text roles for GitHub
1 """Define text roles for GitHub
2
2
3 * ghissue - Issue
3 * ghissue - Issue
4 * ghpull - Pull Request
4 * ghpull - Pull Request
5 * ghuser - User
5 * ghuser - User
6
6
7 Adapted from bitbucket example here:
7 Adapted from bitbucket example here:
8 https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py
8 https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py
9
9
10 Authors
10 Authors
11 -------
11 -------
12
12
13 * Doug Hellmann
13 * Doug Hellmann
14 * Min RK
14 * Min RK
15 """
15 """
16 #
16 #
17 # Original Copyright (c) 2010 Doug Hellmann. All rights reserved.
17 # Original Copyright (c) 2010 Doug Hellmann. All rights reserved.
18 #
18 #
19
19
20 from docutils import nodes, utils
20 from docutils import nodes, utils
21 from docutils.parsers.rst.roles import set_classes
21 from docutils.parsers.rst.roles import set_classes
22
22
23 def make_link_node(rawtext, app, type, slug, options):
23 def make_link_node(rawtext, app, type, slug, options):
24 """Create a link to a github resource.
24 """Create a link to a github resource.
25
25
26 :param rawtext: Text being replaced with link node.
26 :param rawtext: Text being replaced with link node.
27 :param app: Sphinx application context
27 :param app: Sphinx application context
28 :param type: Link type (issues, changeset, etc.)
28 :param type: Link type (issues, changeset, etc.)
29 :param slug: ID of the thing to link to
29 :param slug: ID of the thing to link to
30 :param options: Options dictionary passed to role func.
30 :param options: Options dictionary passed to role func.
31 """
31 """
32
32
33 try:
33 try:
34 base = app.config.github_project_url
34 base = app.config.github_project_url
35 if not base:
35 if not base:
36 raise AttributeError
36 raise AttributeError
37 if not base.endswith('/'):
37 if not base.endswith('/'):
38 base += '/'
38 base += '/'
39 except AttributeError as err:
39 except AttributeError as err:
40 raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
40 raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
41
41
42 ref = base + type + '/' + slug + '/'
42 ref = base + type + '/' + slug + '/'
43 set_classes(options)
43 set_classes(options)
44 prefix = "#"
44 prefix = "#"
45 if type == 'pull':
45 if type == 'pull':
46 prefix = "PR " + prefix
46 prefix = "PR " + prefix
47 node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref,
47 node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref,
48 **options)
48 **options)
49 return node
49 return node
50
50
51 def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
51 def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
52 """Link to a GitHub issue.
52 """Link to a GitHub issue.
53
53
54 Returns 2 part tuple containing list of nodes to insert into the
54 Returns 2 part tuple containing list of nodes to insert into the
55 document and a list of system messages. Both are allowed to be
55 document and a list of system messages. Both are allowed to be
56 empty.
56 empty.
57
57
58 :param name: The role name used in the document.
58 :param name: The role name used in the document.
59 :param rawtext: The entire markup snippet, with role.
59 :param rawtext: The entire markup snippet, with role.
60 :param text: The text marked with the role.
60 :param text: The text marked with the role.
61 :param lineno: The line number where rawtext appears in the input.
61 :param lineno: The line number where rawtext appears in the input.
62 :param inliner: The inliner instance that called us.
62 :param inliner: The inliner instance that called us.
63 :param options: Directive options for customization.
63 :param options: Directive options for customization.
64 :param content: The directive content for customization.
64 :param content: The directive content for customization.
65 """
65 """
66
66
67 try:
67 try:
68 issue_num = int(text)
68 issue_num = int(text)
69 if issue_num <= 0:
69 if issue_num <= 0:
70 raise ValueError
70 raise ValueError
71 except ValueError:
71 except ValueError:
72 msg = inliner.reporter.error(
72 msg = inliner.reporter.error(
73 'GitHub issue number must be a number greater than or equal to 1; '
73 'GitHub issue number must be a number greater than or equal to 1; '
74 '"%s" is invalid.' % text, line=lineno)
74 '"%s" is invalid.' % text, line=lineno)
75 prb = inliner.problematic(rawtext, rawtext, msg)
75 prb = inliner.problematic(rawtext, rawtext, msg)
76 return [prb], [msg]
76 return [prb], [msg]
77 app = inliner.document.settings.env.app
77 app = inliner.document.settings.env.app
78 #app.info('issue %r' % text)
78 #app.info('issue %r' % text)
79 if 'pull' in name.lower():
79 if 'pull' in name.lower():
80 category = 'pull'
80 category = 'pull'
81 elif 'issue' in name.lower():
81 elif 'issue' in name.lower():
82 category = 'issues'
82 category = 'issues'
83 else:
83 else:
84 msg = inliner.reporter.error(
84 msg = inliner.reporter.error(
85 'GitHub roles include "ghpull" and "ghissue", '
85 'GitHub roles include "ghpull" and "ghissue", '
86 '"%s" is invalid.' % name, line=lineno)
86 '"%s" is invalid.' % name, line=lineno)
87 prb = inliner.problematic(rawtext, rawtext, msg)
87 prb = inliner.problematic(rawtext, rawtext, msg)
88 return [prb], [msg]
88 return [prb], [msg]
89 node = make_link_node(rawtext, app, category, str(issue_num), options)
89 node = make_link_node(rawtext, app, category, str(issue_num), options)
90 return [node], []
90 return [node], []
91
91
92 def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
92 def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
93 """Link to a GitHub user.
93 """Link to a GitHub user.
94
94
95 Returns 2 part tuple containing list of nodes to insert into the
95 Returns 2 part tuple containing list of nodes to insert into the
96 document and a list of system messages. Both are allowed to be
96 document and a list of system messages. Both are allowed to be
97 empty.
97 empty.
98
98
99 :param name: The role name used in the document.
99 :param name: The role name used in the document.
100 :param rawtext: The entire markup snippet, with role.
100 :param rawtext: The entire markup snippet, with role.
101 :param text: The text marked with the role.
101 :param text: The text marked with the role.
102 :param lineno: The line number where rawtext appears in the input.
102 :param lineno: The line number where rawtext appears in the input.
103 :param inliner: The inliner instance that called us.
103 :param inliner: The inliner instance that called us.
104 :param options: Directive options for customization.
104 :param options: Directive options for customization.
105 :param content: The directive content for customization.
105 :param content: The directive content for customization.
106 """
106 """
107 app = inliner.document.settings.env.app
107 app = inliner.document.settings.env.app
108 #app.info('user link %r' % text)
108 #app.info('user link %r' % text)
109 ref = 'https://www.github.com/' + text
109 ref = 'https://www.github.com/' + text
110 node = nodes.reference(rawtext, text, refuri=ref, **options)
110 node = nodes.reference(rawtext, text, refuri=ref, **options)
111 return [node], []
111 return [node], []
112
112
113 def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
113 def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
114 """Link to a GitHub commit.
114 """Link to a GitHub commit.
115
115
116 Returns 2 part tuple containing list of nodes to insert into the
116 Returns 2 part tuple containing list of nodes to insert into the
117 document and a list of system messages. Both are allowed to be
117 document and a list of system messages. Both are allowed to be
118 empty.
118 empty.
119
119
120 :param name: The role name used in the document.
120 :param name: The role name used in the document.
121 :param rawtext: The entire markup snippet, with role.
121 :param rawtext: The entire markup snippet, with role.
122 :param text: The text marked with the role.
122 :param text: The text marked with the role.
123 :param lineno: The line number where rawtext appears in the input.
123 :param lineno: The line number where rawtext appears in the input.
124 :param inliner: The inliner instance that called us.
124 :param inliner: The inliner instance that called us.
125 :param options: Directive options for customization.
125 :param options: Directive options for customization.
126 :param content: The directive content for customization.
126 :param content: The directive content for customization.
127 """
127 """
128 app = inliner.document.settings.env.app
128 app = inliner.document.settings.env.app
129 #app.info('user link %r' % text)
129 #app.info('user link %r' % text)
130 try:
130 try:
131 base = app.config.github_project_url
131 base = app.config.github_project_url
132 if not base:
132 if not base:
133 raise AttributeError
133 raise AttributeError
134 if not base.endswith('/'):
134 if not base.endswith('/'):
135 base += '/'
135 base += '/'
136 except AttributeError as err:
136 except AttributeError as err:
137 raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
137 raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
138
138
139 ref = base + text
139 ref = base + text
140 node = nodes.reference(rawtext, text[:6], refuri=ref, **options)
140 node = nodes.reference(rawtext, text[:6], refuri=ref, **options)
141 return [node], []
141 return [node], []
142
142
143
143
144 def setup(app):
144 def setup(app):
145 """Install the plugin.
145 """Install the plugin.
146
146
147 :param app: Sphinx application context.
147 :param app: Sphinx application context.
148 """
148 """
149 app.info('Initializing GitHub plugin')
149 app.info('Initializing GitHub plugin')
150 app.add_role('ghissue', ghissue_role)
150 app.add_role('ghissue', ghissue_role)
151 app.add_role('ghpull', ghissue_role)
151 app.add_role('ghpull', ghissue_role)
152 app.add_role('ghuser', ghuser_role)
152 app.add_role('ghuser', ghuser_role)
153 app.add_role('ghcommit', ghcommit_role)
153 app.add_role('ghcommit', ghcommit_role)
154 app.add_config_value('github_project_url', None, 'env')
154 app.add_config_value('github_project_url', None, 'env')
155 return
155
156 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
157 return metadata
@@ -1,42 +1,45 b''
1 import re
1 import re
2 from sphinx import addnodes
2 from sphinx import addnodes
3 from sphinx.domains.std import StandardDomain
3 from sphinx.domains.std import StandardDomain
4 from sphinx.roles import XRefRole
4 from sphinx.roles import XRefRole
5
5
6 name_re = re.compile(r"[\w_]+")
6 name_re = re.compile(r"[\w_]+")
7
7
8 def parse_magic(env, sig, signode):
8 def parse_magic(env, sig, signode):
9 m = name_re.match(sig)
9 m = name_re.match(sig)
10 if not m:
10 if not m:
11 raise Exception("Invalid magic command: %s" % sig)
11 raise Exception("Invalid magic command: %s" % sig)
12 name = "%" + sig
12 name = "%" + sig
13 signode += addnodes.desc_name(name, name)
13 signode += addnodes.desc_name(name, name)
14 return m.group(0)
14 return m.group(0)
15
15
16 class LineMagicRole(XRefRole):
16 class LineMagicRole(XRefRole):
17 """Cross reference role displayed with a % prefix"""
17 """Cross reference role displayed with a % prefix"""
18 prefix = "%"
18 prefix = "%"
19
19
20 def process_link(self, env, refnode, has_explicit_title, title, target):
20 def process_link(self, env, refnode, has_explicit_title, title, target):
21 if not has_explicit_title:
21 if not has_explicit_title:
22 title = self.prefix + title.lstrip("%")
22 title = self.prefix + title.lstrip("%")
23 target = target.lstrip("%")
23 target = target.lstrip("%")
24 return title, target
24 return title, target
25
25
26 def parse_cell_magic(env, sig, signode):
26 def parse_cell_magic(env, sig, signode):
27 m = name_re.match(sig)
27 m = name_re.match(sig)
28 if not m:
28 if not m:
29 raise ValueError("Invalid cell magic: %s" % sig)
29 raise ValueError("Invalid cell magic: %s" % sig)
30 name = "%%" + sig
30 name = "%%" + sig
31 signode += addnodes.desc_name(name, name)
31 signode += addnodes.desc_name(name, name)
32 return m.group(0)
32 return m.group(0)
33
33
34 class CellMagicRole(LineMagicRole):
34 class CellMagicRole(LineMagicRole):
35 """Cross reference role displayed with a %% prefix"""
35 """Cross reference role displayed with a %% prefix"""
36 prefix = "%%"
36 prefix = "%%"
37
37
38 def setup(app):
38 def setup(app):
39 app.add_object_type('magic', 'magic', 'pair: %s; magic command', parse_magic)
39 app.add_object_type('magic', 'magic', 'pair: %s; magic command', parse_magic)
40 StandardDomain.roles['magic'] = LineMagicRole()
40 StandardDomain.roles['magic'] = LineMagicRole()
41 app.add_object_type('cellmagic', 'cellmagic', 'pair: %s; cell magic', parse_cell_magic)
41 app.add_object_type('cellmagic', 'cellmagic', 'pair: %s; cell magic', parse_cell_magic)
42 StandardDomain.roles['cellmagic'] = CellMagicRole()
42 StandardDomain.roles['cellmagic'] = CellMagicRole()
43
44 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
45 return metadata
General Comments 0
You need to be logged in to leave comments. Login now