##// END OF EJS Templates
Merge pull request #11544 from BoboTiG/fix-invalid-seq-warnings...
Matthias Bussonnier -
r24899:4417fe97 merge
parent child Browse files
Show More
@@ -1,1248 +1,1248 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 IPython provides an extension for `Sphinx <http://www.sphinx-doc.org/>`_ to
5 IPython provides an extension for `Sphinx <http://www.sphinx-doc.org/>`_ to
6 highlight and run code.
6 highlight and run code.
7
7
8 This directive allows pasting of entire interactive IPython sessions, prompts
8 This directive allows pasting of entire interactive IPython sessions, prompts
9 and all, and their code will actually get re-executed at doc build time, with
9 and all, and their code will actually get re-executed at doc build time, with
10 all prompts renumbered sequentially. It also allows you to input code as a pure
10 all prompts renumbered sequentially. It also allows you to input code as a pure
11 python input by giving the argument python to the directive. The output looks
11 python input by giving the argument python to the directive. The output looks
12 like an interactive ipython section.
12 like an interactive ipython section.
13
13
14 Here is an example of how the IPython directive can
14 Here is an example of how the IPython directive can
15 **run** python code, at build time.
15 **run** python code, at build time.
16
16
17 .. ipython::
17 .. ipython::
18
18
19 In [1]: 1+1
19 In [1]: 1+1
20
20
21 In [1]: import datetime
21 In [1]: import datetime
22 ...: datetime.datetime.now()
22 ...: datetime.datetime.now()
23
23
24 It supports IPython construct that plain
24 It supports IPython construct that plain
25 Python does not understand (like magics):
25 Python does not understand (like magics):
26
26
27 .. ipython::
27 .. ipython::
28
28
29 In [0]: import time
29 In [0]: import time
30
30
31 In [0]: %timeit time.sleep(0.05)
31 In [0]: %timeit time.sleep(0.05)
32
32
33 This will also support top-level async when using IPython 7.0+
33 This will also support top-level async when using IPython 7.0+
34
34
35 .. ipython::
35 .. ipython::
36
36
37 In [2]: import asyncio
37 In [2]: import asyncio
38 ...: print('before')
38 ...: print('before')
39 ...: await asyncio.sleep(1)
39 ...: await asyncio.sleep(1)
40 ...: print('after')
40 ...: print('after')
41
41
42
42
43 The namespace will persist across multiple code chucks, Let's define a variable:
43 The namespace will persist across multiple code chucks, Let's define a variable:
44
44
45 .. ipython::
45 .. ipython::
46
46
47 In [0]: who = "World"
47 In [0]: who = "World"
48
48
49 And now say hello:
49 And now say hello:
50
50
51 .. ipython::
51 .. ipython::
52
52
53 In [0]: print('Hello,', who)
53 In [0]: print('Hello,', who)
54
54
55 If the current section raises an exception, you can add the ``:okexcept:`` flag
55 If the current section raises an exception, you can add the ``:okexcept:`` flag
56 to the current block, otherwise the build will fail.
56 to the current block, otherwise the build will fail.
57
57
58 .. ipython::
58 .. ipython::
59 :okexcept:
59 :okexcept:
60
60
61 In [1]: 1/0
61 In [1]: 1/0
62
62
63 IPython Sphinx directive module
63 IPython Sphinx directive module
64 ===============================
64 ===============================
65
65
66 To enable this directive, simply list it in your Sphinx ``conf.py`` file
66 To enable this directive, simply list it in your Sphinx ``conf.py`` file
67 (making sure the directory where you placed it is visible to sphinx, as is
67 (making sure the directory where you placed it is visible to sphinx, as is
68 needed for all Sphinx directives). For example, to enable syntax highlighting
68 needed for all Sphinx directives). For example, to enable syntax highlighting
69 and the IPython directive::
69 and the IPython directive::
70
70
71 extensions = ['IPython.sphinxext.ipython_console_highlighting',
71 extensions = ['IPython.sphinxext.ipython_console_highlighting',
72 'IPython.sphinxext.ipython_directive']
72 'IPython.sphinxext.ipython_directive']
73
73
74 The IPython directive outputs code-blocks with the language 'ipython'. So
74 The IPython directive outputs code-blocks with the language 'ipython'. So
75 if you do not have the syntax highlighting extension enabled as well, then
75 if you do not have the syntax highlighting extension enabled as well, then
76 all rendered code-blocks will be uncolored. By default this directive assumes
76 all rendered code-blocks will be uncolored. By default this directive assumes
77 that your prompts are unchanged IPython ones, but this can be customized.
77 that your prompts are unchanged IPython ones, but this can be customized.
78 The configurable options that can be placed in conf.py are:
78 The configurable options that can be placed in conf.py are:
79
79
80 ipython_savefig_dir:
80 ipython_savefig_dir:
81 The directory in which to save the figures. This is relative to the
81 The directory in which to save the figures. This is relative to the
82 Sphinx source directory. The default is `html_static_path`.
82 Sphinx source directory. The default is `html_static_path`.
83 ipython_rgxin:
83 ipython_rgxin:
84 The compiled regular expression to denote the start of IPython input
84 The compiled regular expression to denote the start of IPython input
85 lines. The default is ``re.compile('In \[(\d+)\]:\s?(.*)\s*')``. You
85 lines. The default is ``re.compile('In \\[(\\d+)\\]:\\s?(.*)\\s*')``. You
86 shouldn't need to change this.
86 shouldn't need to change this.
87 ipython_warning_is_error: [default to True]
87 ipython_warning_is_error: [default to True]
88 Fail the build if something unexpected happen, for example if a block raise
88 Fail the build if something unexpected happen, for example if a block raise
89 an exception but does not have the `:okexcept:` flag. The exact behavior of
89 an exception but does not have the `:okexcept:` flag. The exact behavior of
90 what is considered strict, may change between the sphinx directive version.
90 what is considered strict, may change between the sphinx directive version.
91 ipython_rgxout:
91 ipython_rgxout:
92 The compiled regular expression to denote the start of IPython output
92 The compiled regular expression to denote the start of IPython output
93 lines. The default is ``re.compile('Out\[(\d+)\]:\s?(.*)\s*')``. You
93 lines. The default is ``re.compile('Out\\[(\\d+)\\]:\\s?(.*)\\s*')``. You
94 shouldn't need to change this.
94 shouldn't need to change this.
95 ipython_promptin:
95 ipython_promptin:
96 The string to represent the IPython input prompt in the generated ReST.
96 The string to represent the IPython input prompt in the generated ReST.
97 The default is ``'In [%d]:'``. This expects that the line numbers are used
97 The default is ``'In [%d]:'``. This expects that the line numbers are used
98 in the prompt.
98 in the prompt.
99 ipython_promptout:
99 ipython_promptout:
100 The string to represent the IPython prompt in the generated ReST. The
100 The string to represent the IPython prompt in the generated ReST. The
101 default is ``'Out [%d]:'``. This expects that the line numbers are used
101 default is ``'Out [%d]:'``. This expects that the line numbers are used
102 in the prompt.
102 in the prompt.
103 ipython_mplbackend:
103 ipython_mplbackend:
104 The string which specifies if the embedded Sphinx shell should import
104 The string which specifies if the embedded Sphinx shell should import
105 Matplotlib and set the backend. The value specifies a backend that is
105 Matplotlib and set the backend. The value specifies a backend that is
106 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
106 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
107 executed. If not specified in conf.py, then the default value of 'agg' is
107 executed. If not specified in conf.py, then the default value of 'agg' is
108 used. To use the IPython directive without matplotlib as a dependency, set
108 used. To use the IPython directive without matplotlib as a dependency, set
109 the value to `None`. It may end up that matplotlib is still imported
109 the value to `None`. It may end up that matplotlib is still imported
110 if the user specifies so in `ipython_execlines` or makes use of the
110 if the user specifies so in `ipython_execlines` or makes use of the
111 @savefig pseudo decorator.
111 @savefig pseudo decorator.
112 ipython_execlines:
112 ipython_execlines:
113 A list of strings to be exec'd in the embedded Sphinx shell. Typical
113 A list of strings to be exec'd in the embedded Sphinx shell. Typical
114 usage is to make certain packages always available. Set this to an empty
114 usage is to make certain packages always available. Set this to an empty
115 list if you wish to have no imports always available. If specified in
115 list if you wish to have no imports always available. If specified in
116 ``conf.py`` as `None`, then it has the effect of making no imports available.
116 ``conf.py`` as `None`, then it has the effect of making no imports available.
117 If omitted from conf.py altogether, then the default value of
117 If omitted from conf.py altogether, then the default value of
118 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
118 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
119 ipython_holdcount
119 ipython_holdcount
120 When the @suppress pseudo-decorator is used, the execution count can be
120 When the @suppress pseudo-decorator is used, the execution count can be
121 incremented or not. The default behavior is to hold the execution count,
121 incremented or not. The default behavior is to hold the execution count,
122 corresponding to a value of `True`. Set this to `False` to increment
122 corresponding to a value of `True`. Set this to `False` to increment
123 the execution count after each suppressed command.
123 the execution count after each suppressed command.
124
124
125 As an example, to use the IPython directive when `matplotlib` is not available,
125 As an example, to use the IPython directive when `matplotlib` is not available,
126 one sets the backend to `None`::
126 one sets the backend to `None`::
127
127
128 ipython_mplbackend = None
128 ipython_mplbackend = None
129
129
130 An example usage of the directive is:
130 An example usage of the directive is:
131
131
132 .. code-block:: rst
132 .. code-block:: rst
133
133
134 .. ipython::
134 .. ipython::
135
135
136 In [1]: x = 1
136 In [1]: x = 1
137
137
138 In [2]: y = x**2
138 In [2]: y = x**2
139
139
140 In [3]: print(y)
140 In [3]: print(y)
141
141
142 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
142 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
143 documentation.
143 documentation.
144
144
145 Pseudo-Decorators
145 Pseudo-Decorators
146 =================
146 =================
147
147
148 Note: Only one decorator is supported per input. If more than one decorator
148 Note: Only one decorator is supported per input. If more than one decorator
149 is specified, then only the last one is used.
149 is specified, then only the last one is used.
150
150
151 In addition to the Pseudo-Decorators/options described at the above link,
151 In addition to the Pseudo-Decorators/options described at the above link,
152 several enhancements have been made. The directive will emit a message to the
152 several enhancements have been made. The directive will emit a message to the
153 console at build-time if code-execution resulted in an exception or warning.
153 console at build-time if code-execution resulted in an exception or warning.
154 You can suppress these on a per-block basis by specifying the :okexcept:
154 You can suppress these on a per-block basis by specifying the :okexcept:
155 or :okwarning: options:
155 or :okwarning: options:
156
156
157 .. code-block:: rst
157 .. code-block:: rst
158
158
159 .. ipython::
159 .. ipython::
160 :okexcept:
160 :okexcept:
161 :okwarning:
161 :okwarning:
162
162
163 In [1]: 1/0
163 In [1]: 1/0
164 In [2]: # raise warning.
164 In [2]: # raise warning.
165
165
166 To Do
166 To Do
167 =====
167 =====
168
168
169 - Turn the ad-hoc test() function into a real test suite.
169 - Turn the ad-hoc test() function into a real test suite.
170 - Break up ipython-specific functionality from matplotlib stuff into better
170 - Break up ipython-specific functionality from matplotlib stuff into better
171 separated code.
171 separated code.
172
172
173 """
173 """
174
174
175 # Authors
175 # Authors
176 # =======
176 # =======
177 #
177 #
178 # - John D Hunter: original author.
178 # - John D Hunter: original author.
179 # - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
179 # - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
180 # - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
180 # - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
181 # - Skipper Seabold, refactoring, cleanups, pure python addition
181 # - Skipper Seabold, refactoring, cleanups, pure python addition
182
182
183 #-----------------------------------------------------------------------------
183 #-----------------------------------------------------------------------------
184 # Imports
184 # Imports
185 #-----------------------------------------------------------------------------
185 #-----------------------------------------------------------------------------
186
186
187 # Stdlib
187 # Stdlib
188 import atexit
188 import atexit
189 import errno
189 import errno
190 import os
190 import os
191 import re
191 import re
192 import sys
192 import sys
193 import tempfile
193 import tempfile
194 import ast
194 import ast
195 import warnings
195 import warnings
196 import shutil
196 import shutil
197 from io import StringIO
197 from io import StringIO
198
198
199 # Third-party
199 # Third-party
200 from docutils.parsers.rst import directives
200 from docutils.parsers.rst import directives
201 from docutils.parsers.rst import Directive
201 from docutils.parsers.rst import Directive
202
202
203 # Our own
203 # Our own
204 from traitlets.config import Config
204 from traitlets.config import Config
205 from IPython import InteractiveShell
205 from IPython import InteractiveShell
206 from IPython.core.profiledir import ProfileDir
206 from IPython.core.profiledir import ProfileDir
207
207
208 use_matpltolib = False
208 use_matpltolib = False
209 try:
209 try:
210 import matplotlib
210 import matplotlib
211 use_matpltolib = True
211 use_matpltolib = True
212 except Exception:
212 except Exception:
213 pass
213 pass
214
214
215 #-----------------------------------------------------------------------------
215 #-----------------------------------------------------------------------------
216 # Globals
216 # Globals
217 #-----------------------------------------------------------------------------
217 #-----------------------------------------------------------------------------
218 # for tokenizing blocks
218 # for tokenizing blocks
219 COMMENT, INPUT, OUTPUT = range(3)
219 COMMENT, INPUT, OUTPUT = range(3)
220
220
221 #-----------------------------------------------------------------------------
221 #-----------------------------------------------------------------------------
222 # Functions and class declarations
222 # Functions and class declarations
223 #-----------------------------------------------------------------------------
223 #-----------------------------------------------------------------------------
224
224
225 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
225 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
226 """
226 """
227 part is a string of ipython text, comprised of at most one
227 part is a string of ipython text, comprised of at most one
228 input, one output, comments, and blank lines. The block parser
228 input, one output, comments, and blank lines. The block parser
229 parses the text into a list of::
229 parses the text into a list of::
230
230
231 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
231 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
232
232
233 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
233 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
234 data is, depending on the type of token::
234 data is, depending on the type of token::
235
235
236 COMMENT : the comment string
236 COMMENT : the comment string
237
237
238 INPUT: the (DECORATOR, INPUT_LINE, REST) where
238 INPUT: the (DECORATOR, INPUT_LINE, REST) where
239 DECORATOR: the input decorator (or None)
239 DECORATOR: the input decorator (or None)
240 INPUT_LINE: the input as string (possibly multi-line)
240 INPUT_LINE: the input as string (possibly multi-line)
241 REST : any stdout generated by the input line (not OUTPUT)
241 REST : any stdout generated by the input line (not OUTPUT)
242
242
243 OUTPUT: the output string, possibly multi-line
243 OUTPUT: the output string, possibly multi-line
244
244
245 """
245 """
246 block = []
246 block = []
247 lines = part.split('\n')
247 lines = part.split('\n')
248 N = len(lines)
248 N = len(lines)
249 i = 0
249 i = 0
250 decorator = None
250 decorator = None
251 while 1:
251 while 1:
252
252
253 if i==N:
253 if i==N:
254 # nothing left to parse -- the last line
254 # nothing left to parse -- the last line
255 break
255 break
256
256
257 line = lines[i]
257 line = lines[i]
258 i += 1
258 i += 1
259 line_stripped = line.strip()
259 line_stripped = line.strip()
260 if line_stripped.startswith('#'):
260 if line_stripped.startswith('#'):
261 block.append((COMMENT, line))
261 block.append((COMMENT, line))
262 continue
262 continue
263
263
264 if line_stripped.startswith('@'):
264 if line_stripped.startswith('@'):
265 # Here is where we assume there is, at most, one decorator.
265 # Here is where we assume there is, at most, one decorator.
266 # Might need to rethink this.
266 # Might need to rethink this.
267 decorator = line_stripped
267 decorator = line_stripped
268 continue
268 continue
269
269
270 # does this look like an input line?
270 # does this look like an input line?
271 matchin = rgxin.match(line)
271 matchin = rgxin.match(line)
272 if matchin:
272 if matchin:
273 lineno, inputline = int(matchin.group(1)), matchin.group(2)
273 lineno, inputline = int(matchin.group(1)), matchin.group(2)
274
274
275 # the ....: continuation string
275 # the ....: continuation string
276 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
276 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
277 Nc = len(continuation)
277 Nc = len(continuation)
278 # input lines can continue on for more than one line, if
278 # input lines can continue on for more than one line, if
279 # we have a '\' line continuation char or a function call
279 # we have a '\' line continuation char or a function call
280 # echo line 'print'. The input line can only be
280 # echo line 'print'. The input line can only be
281 # terminated by the end of the block or an output line, so
281 # terminated by the end of the block or an output line, so
282 # we parse out the rest of the input line if it is
282 # we parse out the rest of the input line if it is
283 # multiline as well as any echo text
283 # multiline as well as any echo text
284
284
285 rest = []
285 rest = []
286 while i<N:
286 while i<N:
287
287
288 # look ahead; if the next line is blank, or a comment, or
288 # look ahead; if the next line is blank, or a comment, or
289 # an output line, we're done
289 # an output line, we're done
290
290
291 nextline = lines[i]
291 nextline = lines[i]
292 matchout = rgxout.match(nextline)
292 matchout = rgxout.match(nextline)
293 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
293 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
294 if matchout or nextline.startswith('#'):
294 if matchout or nextline.startswith('#'):
295 break
295 break
296 elif nextline.startswith(continuation):
296 elif nextline.startswith(continuation):
297 # The default ipython_rgx* treat the space following the colon as optional.
297 # The default ipython_rgx* treat the space following the colon as optional.
298 # However, If the space is there we must consume it or code
298 # However, If the space is there we must consume it or code
299 # employing the cython_magic extension will fail to execute.
299 # employing the cython_magic extension will fail to execute.
300 #
300 #
301 # This works with the default ipython_rgx* patterns,
301 # This works with the default ipython_rgx* patterns,
302 # If you modify them, YMMV.
302 # If you modify them, YMMV.
303 nextline = nextline[Nc:]
303 nextline = nextline[Nc:]
304 if nextline and nextline[0] == ' ':
304 if nextline and nextline[0] == ' ':
305 nextline = nextline[1:]
305 nextline = nextline[1:]
306
306
307 inputline += '\n' + nextline
307 inputline += '\n' + nextline
308 else:
308 else:
309 rest.append(nextline)
309 rest.append(nextline)
310 i+= 1
310 i+= 1
311
311
312 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
312 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
313 continue
313 continue
314
314
315 # if it looks like an output line grab all the text to the end
315 # if it looks like an output line grab all the text to the end
316 # of the block
316 # of the block
317 matchout = rgxout.match(line)
317 matchout = rgxout.match(line)
318 if matchout:
318 if matchout:
319 lineno, output = int(matchout.group(1)), matchout.group(2)
319 lineno, output = int(matchout.group(1)), matchout.group(2)
320 if i<N-1:
320 if i<N-1:
321 output = '\n'.join([output] + lines[i:])
321 output = '\n'.join([output] + lines[i:])
322
322
323 block.append((OUTPUT, output))
323 block.append((OUTPUT, output))
324 break
324 break
325
325
326 return block
326 return block
327
327
328
328
329 class EmbeddedSphinxShell(object):
329 class EmbeddedSphinxShell(object):
330 """An embedded IPython instance to run inside Sphinx"""
330 """An embedded IPython instance to run inside Sphinx"""
331
331
332 def __init__(self, exec_lines=None):
332 def __init__(self, exec_lines=None):
333
333
334 self.cout = StringIO()
334 self.cout = StringIO()
335
335
336 if exec_lines is None:
336 if exec_lines is None:
337 exec_lines = []
337 exec_lines = []
338
338
339 # Create config object for IPython
339 # Create config object for IPython
340 config = Config()
340 config = Config()
341 config.HistoryManager.hist_file = ':memory:'
341 config.HistoryManager.hist_file = ':memory:'
342 config.InteractiveShell.autocall = False
342 config.InteractiveShell.autocall = False
343 config.InteractiveShell.autoindent = False
343 config.InteractiveShell.autoindent = False
344 config.InteractiveShell.colors = 'NoColor'
344 config.InteractiveShell.colors = 'NoColor'
345
345
346 # create a profile so instance history isn't saved
346 # create a profile so instance history isn't saved
347 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
347 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
348 profname = 'auto_profile_sphinx_build'
348 profname = 'auto_profile_sphinx_build'
349 pdir = os.path.join(tmp_profile_dir,profname)
349 pdir = os.path.join(tmp_profile_dir,profname)
350 profile = ProfileDir.create_profile_dir(pdir)
350 profile = ProfileDir.create_profile_dir(pdir)
351
351
352 # Create and initialize global ipython, but don't start its mainloop.
352 # Create and initialize global ipython, but don't start its mainloop.
353 # This will persist across different EmbeddedSphinxShell instances.
353 # This will persist across different EmbeddedSphinxShell instances.
354 IP = InteractiveShell.instance(config=config, profile_dir=profile)
354 IP = InteractiveShell.instance(config=config, profile_dir=profile)
355 atexit.register(self.cleanup)
355 atexit.register(self.cleanup)
356
356
357 # Store a few parts of IPython we'll need.
357 # Store a few parts of IPython we'll need.
358 self.IP = IP
358 self.IP = IP
359 self.user_ns = self.IP.user_ns
359 self.user_ns = self.IP.user_ns
360 self.user_global_ns = self.IP.user_global_ns
360 self.user_global_ns = self.IP.user_global_ns
361
361
362 self.input = ''
362 self.input = ''
363 self.output = ''
363 self.output = ''
364 self.tmp_profile_dir = tmp_profile_dir
364 self.tmp_profile_dir = tmp_profile_dir
365
365
366 self.is_verbatim = False
366 self.is_verbatim = False
367 self.is_doctest = False
367 self.is_doctest = False
368 self.is_suppress = False
368 self.is_suppress = False
369
369
370 # Optionally, provide more detailed information to shell.
370 # Optionally, provide more detailed information to shell.
371 # this is assigned by the SetUp method of IPythonDirective
371 # this is assigned by the SetUp method of IPythonDirective
372 # to point at itself.
372 # to point at itself.
373 #
373 #
374 # So, you can access handy things at self.directive.state
374 # So, you can access handy things at self.directive.state
375 self.directive = None
375 self.directive = None
376
376
377 # on the first call to the savefig decorator, we'll import
377 # on the first call to the savefig decorator, we'll import
378 # pyplot as plt so we can make a call to the plt.gcf().savefig
378 # pyplot as plt so we can make a call to the plt.gcf().savefig
379 self._pyplot_imported = False
379 self._pyplot_imported = False
380
380
381 # Prepopulate the namespace.
381 # Prepopulate the namespace.
382 for line in exec_lines:
382 for line in exec_lines:
383 self.process_input_line(line, store_history=False)
383 self.process_input_line(line, store_history=False)
384
384
385 def cleanup(self):
385 def cleanup(self):
386 shutil.rmtree(self.tmp_profile_dir, ignore_errors=True)
386 shutil.rmtree(self.tmp_profile_dir, ignore_errors=True)
387
387
388 def clear_cout(self):
388 def clear_cout(self):
389 self.cout.seek(0)
389 self.cout.seek(0)
390 self.cout.truncate(0)
390 self.cout.truncate(0)
391
391
392 def process_input_line(self, line, store_history):
392 def process_input_line(self, line, store_history):
393 return self.process_input_lines([line], store_history=store_history)
393 return self.process_input_lines([line], store_history=store_history)
394
394
395 def process_input_lines(self, lines, store_history=True):
395 def process_input_lines(self, lines, store_history=True):
396 """process the input, capturing stdout"""
396 """process the input, capturing stdout"""
397 stdout = sys.stdout
397 stdout = sys.stdout
398 source_raw = '\n'.join(lines)
398 source_raw = '\n'.join(lines)
399 try:
399 try:
400 sys.stdout = self.cout
400 sys.stdout = self.cout
401 self.IP.run_cell(source_raw, store_history=store_history)
401 self.IP.run_cell(source_raw, store_history=store_history)
402 finally:
402 finally:
403 sys.stdout = stdout
403 sys.stdout = stdout
404
404
405 def process_image(self, decorator):
405 def process_image(self, decorator):
406 """
406 """
407 # build out an image directive like
407 # build out an image directive like
408 # .. image:: somefile.png
408 # .. image:: somefile.png
409 # :width 4in
409 # :width 4in
410 #
410 #
411 # from an input like
411 # from an input like
412 # savefig somefile.png width=4in
412 # savefig somefile.png width=4in
413 """
413 """
414 savefig_dir = self.savefig_dir
414 savefig_dir = self.savefig_dir
415 source_dir = self.source_dir
415 source_dir = self.source_dir
416 saveargs = decorator.split(' ')
416 saveargs = decorator.split(' ')
417 filename = saveargs[1]
417 filename = saveargs[1]
418 # insert relative path to image file in source (as absolute path for Sphinx)
418 # insert relative path to image file in source (as absolute path for Sphinx)
419 outfile = '/' + os.path.relpath(os.path.join(savefig_dir,filename),
419 outfile = '/' + os.path.relpath(os.path.join(savefig_dir,filename),
420 source_dir)
420 source_dir)
421
421
422 imagerows = ['.. image:: %s'%outfile]
422 imagerows = ['.. image:: %s'%outfile]
423
423
424 for kwarg in saveargs[2:]:
424 for kwarg in saveargs[2:]:
425 arg, val = kwarg.split('=')
425 arg, val = kwarg.split('=')
426 arg = arg.strip()
426 arg = arg.strip()
427 val = val.strip()
427 val = val.strip()
428 imagerows.append(' :%s: %s'%(arg, val))
428 imagerows.append(' :%s: %s'%(arg, val))
429
429
430 image_file = os.path.basename(outfile) # only return file name
430 image_file = os.path.basename(outfile) # only return file name
431 image_directive = '\n'.join(imagerows)
431 image_directive = '\n'.join(imagerows)
432 return image_file, image_directive
432 return image_file, image_directive
433
433
434 # Callbacks for each type of token
434 # Callbacks for each type of token
435 def process_input(self, data, input_prompt, lineno):
435 def process_input(self, data, input_prompt, lineno):
436 """
436 """
437 Process data block for INPUT token.
437 Process data block for INPUT token.
438
438
439 """
439 """
440 decorator, input, rest = data
440 decorator, input, rest = data
441 image_file = None
441 image_file = None
442 image_directive = None
442 image_directive = None
443
443
444 is_verbatim = decorator=='@verbatim' or self.is_verbatim
444 is_verbatim = decorator=='@verbatim' or self.is_verbatim
445 is_doctest = (decorator is not None and \
445 is_doctest = (decorator is not None and \
446 decorator.startswith('@doctest')) or self.is_doctest
446 decorator.startswith('@doctest')) or self.is_doctest
447 is_suppress = decorator=='@suppress' or self.is_suppress
447 is_suppress = decorator=='@suppress' or self.is_suppress
448 is_okexcept = decorator=='@okexcept' or self.is_okexcept
448 is_okexcept = decorator=='@okexcept' or self.is_okexcept
449 is_okwarning = decorator=='@okwarning' or self.is_okwarning
449 is_okwarning = decorator=='@okwarning' or self.is_okwarning
450 is_savefig = decorator is not None and \
450 is_savefig = decorator is not None and \
451 decorator.startswith('@savefig')
451 decorator.startswith('@savefig')
452
452
453 input_lines = input.split('\n')
453 input_lines = input.split('\n')
454 if len(input_lines) > 1:
454 if len(input_lines) > 1:
455 if input_lines[-1] != "":
455 if input_lines[-1] != "":
456 input_lines.append('') # make sure there's a blank line
456 input_lines.append('') # make sure there's a blank line
457 # so splitter buffer gets reset
457 # so splitter buffer gets reset
458
458
459 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
459 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
460
460
461 if is_savefig:
461 if is_savefig:
462 image_file, image_directive = self.process_image(decorator)
462 image_file, image_directive = self.process_image(decorator)
463
463
464 ret = []
464 ret = []
465 is_semicolon = False
465 is_semicolon = False
466
466
467 # Hold the execution count, if requested to do so.
467 # Hold the execution count, if requested to do so.
468 if is_suppress and self.hold_count:
468 if is_suppress and self.hold_count:
469 store_history = False
469 store_history = False
470 else:
470 else:
471 store_history = True
471 store_history = True
472
472
473 # Note: catch_warnings is not thread safe
473 # Note: catch_warnings is not thread safe
474 with warnings.catch_warnings(record=True) as ws:
474 with warnings.catch_warnings(record=True) as ws:
475 if input_lines[0].endswith(';'):
475 if input_lines[0].endswith(';'):
476 is_semicolon = True
476 is_semicolon = True
477 #for i, line in enumerate(input_lines):
477 #for i, line in enumerate(input_lines):
478
478
479 # process the first input line
479 # process the first input line
480 if is_verbatim:
480 if is_verbatim:
481 self.process_input_lines([''])
481 self.process_input_lines([''])
482 self.IP.execution_count += 1 # increment it anyway
482 self.IP.execution_count += 1 # increment it anyway
483 else:
483 else:
484 # only submit the line in non-verbatim mode
484 # only submit the line in non-verbatim mode
485 self.process_input_lines(input_lines, store_history=store_history)
485 self.process_input_lines(input_lines, store_history=store_history)
486
486
487 if not is_suppress:
487 if not is_suppress:
488 for i, line in enumerate(input_lines):
488 for i, line in enumerate(input_lines):
489 if i == 0:
489 if i == 0:
490 formatted_line = '%s %s'%(input_prompt, line)
490 formatted_line = '%s %s'%(input_prompt, line)
491 else:
491 else:
492 formatted_line = '%s %s'%(continuation, line)
492 formatted_line = '%s %s'%(continuation, line)
493 ret.append(formatted_line)
493 ret.append(formatted_line)
494
494
495 if not is_suppress and len(rest.strip()) and is_verbatim:
495 if not is_suppress and len(rest.strip()) and is_verbatim:
496 # The "rest" is the standard output of the input. This needs to be
496 # The "rest" is the standard output of the input. This needs to be
497 # added when in verbatim mode. If there is no "rest", then we don't
497 # added when in verbatim mode. If there is no "rest", then we don't
498 # add it, as the new line will be added by the processed output.
498 # add it, as the new line will be added by the processed output.
499 ret.append(rest)
499 ret.append(rest)
500
500
501 # Fetch the processed output. (This is not the submitted output.)
501 # Fetch the processed output. (This is not the submitted output.)
502 self.cout.seek(0)
502 self.cout.seek(0)
503 processed_output = self.cout.read()
503 processed_output = self.cout.read()
504 if not is_suppress and not is_semicolon:
504 if not is_suppress and not is_semicolon:
505 #
505 #
506 # In IPythonDirective.run, the elements of `ret` are eventually
506 # In IPythonDirective.run, the elements of `ret` are eventually
507 # combined such that '' entries correspond to newlines. So if
507 # combined such that '' entries correspond to newlines. So if
508 # `processed_output` is equal to '', then the adding it to `ret`
508 # `processed_output` is equal to '', then the adding it to `ret`
509 # ensures that there is a blank line between consecutive inputs
509 # ensures that there is a blank line between consecutive inputs
510 # that have no outputs, as in:
510 # that have no outputs, as in:
511 #
511 #
512 # In [1]: x = 4
512 # In [1]: x = 4
513 #
513 #
514 # In [2]: x = 5
514 # In [2]: x = 5
515 #
515 #
516 # When there is processed output, it has a '\n' at the tail end. So
516 # When there is processed output, it has a '\n' at the tail end. So
517 # adding the output to `ret` will provide the necessary spacing
517 # adding the output to `ret` will provide the necessary spacing
518 # between consecutive input/output blocks, as in:
518 # between consecutive input/output blocks, as in:
519 #
519 #
520 # In [1]: x
520 # In [1]: x
521 # Out[1]: 5
521 # Out[1]: 5
522 #
522 #
523 # In [2]: x
523 # In [2]: x
524 # Out[2]: 5
524 # Out[2]: 5
525 #
525 #
526 # When there is stdout from the input, it also has a '\n' at the
526 # When there is stdout from the input, it also has a '\n' at the
527 # tail end, and so this ensures proper spacing as well. E.g.:
527 # tail end, and so this ensures proper spacing as well. E.g.:
528 #
528 #
529 # In [1]: print x
529 # In [1]: print x
530 # 5
530 # 5
531 #
531 #
532 # In [2]: x = 5
532 # In [2]: x = 5
533 #
533 #
534 # When in verbatim mode, `processed_output` is empty (because
534 # When in verbatim mode, `processed_output` is empty (because
535 # nothing was passed to IP. Sometimes the submitted code block has
535 # nothing was passed to IP. Sometimes the submitted code block has
536 # an Out[] portion and sometimes it does not. When it does not, we
536 # an Out[] portion and sometimes it does not. When it does not, we
537 # need to ensure proper spacing, so we have to add '' to `ret`.
537 # need to ensure proper spacing, so we have to add '' to `ret`.
538 # However, if there is an Out[] in the submitted code, then we do
538 # However, if there is an Out[] in the submitted code, then we do
539 # not want to add a newline as `process_output` has stuff to add.
539 # not want to add a newline as `process_output` has stuff to add.
540 # The difficulty is that `process_input` doesn't know if
540 # The difficulty is that `process_input` doesn't know if
541 # `process_output` will be called---so it doesn't know if there is
541 # `process_output` will be called---so it doesn't know if there is
542 # Out[] in the code block. The requires that we include a hack in
542 # Out[] in the code block. The requires that we include a hack in
543 # `process_block`. See the comments there.
543 # `process_block`. See the comments there.
544 #
544 #
545 ret.append(processed_output)
545 ret.append(processed_output)
546 elif is_semicolon:
546 elif is_semicolon:
547 # Make sure there is a newline after the semicolon.
547 # Make sure there is a newline after the semicolon.
548 ret.append('')
548 ret.append('')
549
549
550 # context information
550 # context information
551 filename = "Unknown"
551 filename = "Unknown"
552 lineno = 0
552 lineno = 0
553 if self.directive.state:
553 if self.directive.state:
554 filename = self.directive.state.document.current_source
554 filename = self.directive.state.document.current_source
555 lineno = self.directive.state.document.current_line
555 lineno = self.directive.state.document.current_line
556
556
557 # output any exceptions raised during execution to stdout
557 # output any exceptions raised during execution to stdout
558 # unless :okexcept: has been specified.
558 # unless :okexcept: has been specified.
559 if not is_okexcept and (("Traceback" in processed_output) or ("SyntaxError" in processed_output)):
559 if not is_okexcept and (("Traceback" in processed_output) or ("SyntaxError" in processed_output)):
560 s = "\nException in %s at block ending on line %s\n" % (filename, lineno)
560 s = "\nException in %s at block ending on line %s\n" % (filename, lineno)
561 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
561 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
562 sys.stdout.write('\n\n>>>' + ('-' * 73))
562 sys.stdout.write('\n\n>>>' + ('-' * 73))
563 sys.stdout.write(s)
563 sys.stdout.write(s)
564 sys.stdout.write(processed_output)
564 sys.stdout.write(processed_output)
565 sys.stdout.write('<<<' + ('-' * 73) + '\n\n')
565 sys.stdout.write('<<<' + ('-' * 73) + '\n\n')
566 if self.warning_is_error:
566 if self.warning_is_error:
567 raise RuntimeError('Non Expected exception in `{}` line {}'.format(filename, lineno))
567 raise RuntimeError('Non Expected exception in `{}` line {}'.format(filename, lineno))
568
568
569 # output any warning raised during execution to stdout
569 # output any warning raised during execution to stdout
570 # unless :okwarning: has been specified.
570 # unless :okwarning: has been specified.
571 if not is_okwarning:
571 if not is_okwarning:
572 for w in ws:
572 for w in ws:
573 s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno)
573 s = "\nWarning in %s at block ending on line %s\n" % (filename, lineno)
574 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
574 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
575 sys.stdout.write('\n\n>>>' + ('-' * 73))
575 sys.stdout.write('\n\n>>>' + ('-' * 73))
576 sys.stdout.write(s)
576 sys.stdout.write(s)
577 sys.stdout.write(('-' * 76) + '\n')
577 sys.stdout.write(('-' * 76) + '\n')
578 s=warnings.formatwarning(w.message, w.category,
578 s=warnings.formatwarning(w.message, w.category,
579 w.filename, w.lineno, w.line)
579 w.filename, w.lineno, w.line)
580 sys.stdout.write(s)
580 sys.stdout.write(s)
581 sys.stdout.write('<<<' + ('-' * 73) + '\n')
581 sys.stdout.write('<<<' + ('-' * 73) + '\n')
582 if self.warning_is_error:
582 if self.warning_is_error:
583 raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno))
583 raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno))
584
584
585 self.cout.truncate(0)
585 self.cout.truncate(0)
586 return (ret, input_lines, processed_output,
586 return (ret, input_lines, processed_output,
587 is_doctest, decorator, image_file, image_directive)
587 is_doctest, decorator, image_file, image_directive)
588
588
589
589
590 def process_output(self, data, output_prompt, input_lines, output,
590 def process_output(self, data, output_prompt, input_lines, output,
591 is_doctest, decorator, image_file):
591 is_doctest, decorator, image_file):
592 """
592 """
593 Process data block for OUTPUT token.
593 Process data block for OUTPUT token.
594
594
595 """
595 """
596 # Recall: `data` is the submitted output, and `output` is the processed
596 # Recall: `data` is the submitted output, and `output` is the processed
597 # output from `input_lines`.
597 # output from `input_lines`.
598
598
599 TAB = ' ' * 4
599 TAB = ' ' * 4
600
600
601 if is_doctest and output is not None:
601 if is_doctest and output is not None:
602
602
603 found = output # This is the processed output
603 found = output # This is the processed output
604 found = found.strip()
604 found = found.strip()
605 submitted = data.strip()
605 submitted = data.strip()
606
606
607 if self.directive is None:
607 if self.directive is None:
608 source = 'Unavailable'
608 source = 'Unavailable'
609 content = 'Unavailable'
609 content = 'Unavailable'
610 else:
610 else:
611 source = self.directive.state.document.current_source
611 source = self.directive.state.document.current_source
612 content = self.directive.content
612 content = self.directive.content
613 # Add tabs and join into a single string.
613 # Add tabs and join into a single string.
614 content = '\n'.join([TAB + line for line in content])
614 content = '\n'.join([TAB + line for line in content])
615
615
616 # Make sure the output contains the output prompt.
616 # Make sure the output contains the output prompt.
617 ind = found.find(output_prompt)
617 ind = found.find(output_prompt)
618 if ind < 0:
618 if ind < 0:
619 e = ('output does not contain output prompt\n\n'
619 e = ('output does not contain output prompt\n\n'
620 'Document source: {0}\n\n'
620 'Document source: {0}\n\n'
621 'Raw content: \n{1}\n\n'
621 'Raw content: \n{1}\n\n'
622 'Input line(s):\n{TAB}{2}\n\n'
622 'Input line(s):\n{TAB}{2}\n\n'
623 'Output line(s):\n{TAB}{3}\n\n')
623 'Output line(s):\n{TAB}{3}\n\n')
624 e = e.format(source, content, '\n'.join(input_lines),
624 e = e.format(source, content, '\n'.join(input_lines),
625 repr(found), TAB=TAB)
625 repr(found), TAB=TAB)
626 raise RuntimeError(e)
626 raise RuntimeError(e)
627 found = found[len(output_prompt):].strip()
627 found = found[len(output_prompt):].strip()
628
628
629 # Handle the actual doctest comparison.
629 # Handle the actual doctest comparison.
630 if decorator.strip() == '@doctest':
630 if decorator.strip() == '@doctest':
631 # Standard doctest
631 # Standard doctest
632 if found != submitted:
632 if found != submitted:
633 e = ('doctest failure\n\n'
633 e = ('doctest failure\n\n'
634 'Document source: {0}\n\n'
634 'Document source: {0}\n\n'
635 'Raw content: \n{1}\n\n'
635 'Raw content: \n{1}\n\n'
636 'On input line(s):\n{TAB}{2}\n\n'
636 'On input line(s):\n{TAB}{2}\n\n'
637 'we found output:\n{TAB}{3}\n\n'
637 'we found output:\n{TAB}{3}\n\n'
638 'instead of the expected:\n{TAB}{4}\n\n')
638 'instead of the expected:\n{TAB}{4}\n\n')
639 e = e.format(source, content, '\n'.join(input_lines),
639 e = e.format(source, content, '\n'.join(input_lines),
640 repr(found), repr(submitted), TAB=TAB)
640 repr(found), repr(submitted), TAB=TAB)
641 raise RuntimeError(e)
641 raise RuntimeError(e)
642 else:
642 else:
643 self.custom_doctest(decorator, input_lines, found, submitted)
643 self.custom_doctest(decorator, input_lines, found, submitted)
644
644
645 # When in verbatim mode, this holds additional submitted output
645 # When in verbatim mode, this holds additional submitted output
646 # to be written in the final Sphinx output.
646 # to be written in the final Sphinx output.
647 # https://github.com/ipython/ipython/issues/5776
647 # https://github.com/ipython/ipython/issues/5776
648 out_data = []
648 out_data = []
649
649
650 is_verbatim = decorator=='@verbatim' or self.is_verbatim
650 is_verbatim = decorator=='@verbatim' or self.is_verbatim
651 if is_verbatim and data.strip():
651 if is_verbatim and data.strip():
652 # Note that `ret` in `process_block` has '' as its last element if
652 # Note that `ret` in `process_block` has '' as its last element if
653 # the code block was in verbatim mode. So if there is no submitted
653 # the code block was in verbatim mode. So if there is no submitted
654 # output, then we will have proper spacing only if we do not add
654 # output, then we will have proper spacing only if we do not add
655 # an additional '' to `out_data`. This is why we condition on
655 # an additional '' to `out_data`. This is why we condition on
656 # `and data.strip()`.
656 # `and data.strip()`.
657
657
658 # The submitted output has no output prompt. If we want the
658 # The submitted output has no output prompt. If we want the
659 # prompt and the code to appear, we need to join them now
659 # prompt and the code to appear, we need to join them now
660 # instead of adding them separately---as this would create an
660 # instead of adding them separately---as this would create an
661 # undesired newline. How we do this ultimately depends on the
661 # undesired newline. How we do this ultimately depends on the
662 # format of the output regex. I'll do what works for the default
662 # format of the output regex. I'll do what works for the default
663 # prompt for now, and we might have to adjust if it doesn't work
663 # prompt for now, and we might have to adjust if it doesn't work
664 # in other cases. Finally, the submitted output does not have
664 # in other cases. Finally, the submitted output does not have
665 # a trailing newline, so we must add it manually.
665 # a trailing newline, so we must add it manually.
666 out_data.append("{0} {1}\n".format(output_prompt, data))
666 out_data.append("{0} {1}\n".format(output_prompt, data))
667
667
668 return out_data
668 return out_data
669
669
670 def process_comment(self, data):
670 def process_comment(self, data):
671 """Process data fPblock for COMMENT token."""
671 """Process data fPblock for COMMENT token."""
672 if not self.is_suppress:
672 if not self.is_suppress:
673 return [data]
673 return [data]
674
674
675 def save_image(self, image_file):
675 def save_image(self, image_file):
676 """
676 """
677 Saves the image file to disk.
677 Saves the image file to disk.
678 """
678 """
679 self.ensure_pyplot()
679 self.ensure_pyplot()
680 command = 'plt.gcf().savefig("%s")'%image_file
680 command = 'plt.gcf().savefig("%s")'%image_file
681 #print 'SAVEFIG', command # dbg
681 #print 'SAVEFIG', command # dbg
682 self.process_input_line('bookmark ipy_thisdir', store_history=False)
682 self.process_input_line('bookmark ipy_thisdir', store_history=False)
683 self.process_input_line('cd -b ipy_savedir', store_history=False)
683 self.process_input_line('cd -b ipy_savedir', store_history=False)
684 self.process_input_line(command, store_history=False)
684 self.process_input_line(command, store_history=False)
685 self.process_input_line('cd -b ipy_thisdir', store_history=False)
685 self.process_input_line('cd -b ipy_thisdir', store_history=False)
686 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
686 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
687 self.clear_cout()
687 self.clear_cout()
688
688
689 def process_block(self, block):
689 def process_block(self, block):
690 """
690 """
691 process block from the block_parser and return a list of processed lines
691 process block from the block_parser and return a list of processed lines
692 """
692 """
693 ret = []
693 ret = []
694 output = None
694 output = None
695 input_lines = None
695 input_lines = None
696 lineno = self.IP.execution_count
696 lineno = self.IP.execution_count
697
697
698 input_prompt = self.promptin % lineno
698 input_prompt = self.promptin % lineno
699 output_prompt = self.promptout % lineno
699 output_prompt = self.promptout % lineno
700 image_file = None
700 image_file = None
701 image_directive = None
701 image_directive = None
702
702
703 found_input = False
703 found_input = False
704 for token, data in block:
704 for token, data in block:
705 if token == COMMENT:
705 if token == COMMENT:
706 out_data = self.process_comment(data)
706 out_data = self.process_comment(data)
707 elif token == INPUT:
707 elif token == INPUT:
708 found_input = True
708 found_input = True
709 (out_data, input_lines, output, is_doctest,
709 (out_data, input_lines, output, is_doctest,
710 decorator, image_file, image_directive) = \
710 decorator, image_file, image_directive) = \
711 self.process_input(data, input_prompt, lineno)
711 self.process_input(data, input_prompt, lineno)
712 elif token == OUTPUT:
712 elif token == OUTPUT:
713 if not found_input:
713 if not found_input:
714
714
715 TAB = ' ' * 4
715 TAB = ' ' * 4
716 linenumber = 0
716 linenumber = 0
717 source = 'Unavailable'
717 source = 'Unavailable'
718 content = 'Unavailable'
718 content = 'Unavailable'
719 if self.directive:
719 if self.directive:
720 linenumber = self.directive.state.document.current_line
720 linenumber = self.directive.state.document.current_line
721 source = self.directive.state.document.current_source
721 source = self.directive.state.document.current_source
722 content = self.directive.content
722 content = self.directive.content
723 # Add tabs and join into a single string.
723 # Add tabs and join into a single string.
724 content = '\n'.join([TAB + line for line in content])
724 content = '\n'.join([TAB + line for line in content])
725
725
726 e = ('\n\nInvalid block: Block contains an output prompt '
726 e = ('\n\nInvalid block: Block contains an output prompt '
727 'without an input prompt.\n\n'
727 'without an input prompt.\n\n'
728 'Document source: {0}\n\n'
728 'Document source: {0}\n\n'
729 'Content begins at line {1}: \n\n{2}\n\n'
729 'Content begins at line {1}: \n\n{2}\n\n'
730 'Problematic block within content: \n\n{TAB}{3}\n\n')
730 'Problematic block within content: \n\n{TAB}{3}\n\n')
731 e = e.format(source, linenumber, content, block, TAB=TAB)
731 e = e.format(source, linenumber, content, block, TAB=TAB)
732
732
733 # Write, rather than include in exception, since Sphinx
733 # Write, rather than include in exception, since Sphinx
734 # will truncate tracebacks.
734 # will truncate tracebacks.
735 sys.stdout.write(e)
735 sys.stdout.write(e)
736 raise RuntimeError('An invalid block was detected.')
736 raise RuntimeError('An invalid block was detected.')
737 out_data = \
737 out_data = \
738 self.process_output(data, output_prompt, input_lines,
738 self.process_output(data, output_prompt, input_lines,
739 output, is_doctest, decorator,
739 output, is_doctest, decorator,
740 image_file)
740 image_file)
741 if out_data:
741 if out_data:
742 # Then there was user submitted output in verbatim mode.
742 # Then there was user submitted output in verbatim mode.
743 # We need to remove the last element of `ret` that was
743 # We need to remove the last element of `ret` that was
744 # added in `process_input`, as it is '' and would introduce
744 # added in `process_input`, as it is '' and would introduce
745 # an undesirable newline.
745 # an undesirable newline.
746 assert(ret[-1] == '')
746 assert(ret[-1] == '')
747 del ret[-1]
747 del ret[-1]
748
748
749 if out_data:
749 if out_data:
750 ret.extend(out_data)
750 ret.extend(out_data)
751
751
752 # save the image files
752 # save the image files
753 if image_file is not None:
753 if image_file is not None:
754 self.save_image(image_file)
754 self.save_image(image_file)
755
755
756 return ret, image_directive
756 return ret, image_directive
757
757
758 def ensure_pyplot(self):
758 def ensure_pyplot(self):
759 """
759 """
760 Ensures that pyplot has been imported into the embedded IPython shell.
760 Ensures that pyplot has been imported into the embedded IPython shell.
761
761
762 Also, makes sure to set the backend appropriately if not set already.
762 Also, makes sure to set the backend appropriately if not set already.
763
763
764 """
764 """
765 # We are here if the @figure pseudo decorator was used. Thus, it's
765 # We are here if the @figure pseudo decorator was used. Thus, it's
766 # possible that we could be here even if python_mplbackend were set to
766 # possible that we could be here even if python_mplbackend were set to
767 # `None`. That's also strange and perhaps worthy of raising an
767 # `None`. That's also strange and perhaps worthy of raising an
768 # exception, but for now, we just set the backend to 'agg'.
768 # exception, but for now, we just set the backend to 'agg'.
769
769
770 if not self._pyplot_imported:
770 if not self._pyplot_imported:
771 if 'matplotlib.backends' not in sys.modules:
771 if 'matplotlib.backends' not in sys.modules:
772 # Then ipython_matplotlib was set to None but there was a
772 # Then ipython_matplotlib was set to None but there was a
773 # call to the @figure decorator (and ipython_execlines did
773 # call to the @figure decorator (and ipython_execlines did
774 # not set a backend).
774 # not set a backend).
775 #raise Exception("No backend was set, but @figure was used!")
775 #raise Exception("No backend was set, but @figure was used!")
776 import matplotlib
776 import matplotlib
777 matplotlib.use('agg')
777 matplotlib.use('agg')
778
778
779 # Always import pyplot into embedded shell.
779 # Always import pyplot into embedded shell.
780 self.process_input_line('import matplotlib.pyplot as plt',
780 self.process_input_line('import matplotlib.pyplot as plt',
781 store_history=False)
781 store_history=False)
782 self._pyplot_imported = True
782 self._pyplot_imported = True
783
783
784 def process_pure_python(self, content):
784 def process_pure_python(self, content):
785 """
785 """
786 content is a list of strings. it is unedited directive content
786 content is a list of strings. it is unedited directive content
787
787
788 This runs it line by line in the InteractiveShell, prepends
788 This runs it line by line in the InteractiveShell, prepends
789 prompts as needed capturing stderr and stdout, then returns
789 prompts as needed capturing stderr and stdout, then returns
790 the content as a list as if it were ipython code
790 the content as a list as if it were ipython code
791 """
791 """
792 output = []
792 output = []
793 savefig = False # keep up with this to clear figure
793 savefig = False # keep up with this to clear figure
794 multiline = False # to handle line continuation
794 multiline = False # to handle line continuation
795 multiline_start = None
795 multiline_start = None
796 fmtin = self.promptin
796 fmtin = self.promptin
797
797
798 ct = 0
798 ct = 0
799
799
800 for lineno, line in enumerate(content):
800 for lineno, line in enumerate(content):
801
801
802 line_stripped = line.strip()
802 line_stripped = line.strip()
803 if not len(line):
803 if not len(line):
804 output.append(line)
804 output.append(line)
805 continue
805 continue
806
806
807 # handle decorators
807 # handle decorators
808 if line_stripped.startswith('@'):
808 if line_stripped.startswith('@'):
809 output.extend([line])
809 output.extend([line])
810 if 'savefig' in line:
810 if 'savefig' in line:
811 savefig = True # and need to clear figure
811 savefig = True # and need to clear figure
812 continue
812 continue
813
813
814 # handle comments
814 # handle comments
815 if line_stripped.startswith('#'):
815 if line_stripped.startswith('#'):
816 output.extend([line])
816 output.extend([line])
817 continue
817 continue
818
818
819 # deal with lines checking for multiline
819 # deal with lines checking for multiline
820 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
820 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
821 if not multiline:
821 if not multiline:
822 modified = u"%s %s" % (fmtin % ct, line_stripped)
822 modified = u"%s %s" % (fmtin % ct, line_stripped)
823 output.append(modified)
823 output.append(modified)
824 ct += 1
824 ct += 1
825 try:
825 try:
826 ast.parse(line_stripped)
826 ast.parse(line_stripped)
827 output.append(u'')
827 output.append(u'')
828 except Exception: # on a multiline
828 except Exception: # on a multiline
829 multiline = True
829 multiline = True
830 multiline_start = lineno
830 multiline_start = lineno
831 else: # still on a multiline
831 else: # still on a multiline
832 modified = u'%s %s' % (continuation, line)
832 modified = u'%s %s' % (continuation, line)
833 output.append(modified)
833 output.append(modified)
834
834
835 # if the next line is indented, it should be part of multiline
835 # if the next line is indented, it should be part of multiline
836 if len(content) > lineno + 1:
836 if len(content) > lineno + 1:
837 nextline = content[lineno + 1]
837 nextline = content[lineno + 1]
838 if len(nextline) - len(nextline.lstrip()) > 3:
838 if len(nextline) - len(nextline.lstrip()) > 3:
839 continue
839 continue
840 try:
840 try:
841 mod = ast.parse(
841 mod = ast.parse(
842 '\n'.join(content[multiline_start:lineno+1]))
842 '\n'.join(content[multiline_start:lineno+1]))
843 if isinstance(mod.body[0], ast.FunctionDef):
843 if isinstance(mod.body[0], ast.FunctionDef):
844 # check to see if we have the whole function
844 # check to see if we have the whole function
845 for element in mod.body[0].body:
845 for element in mod.body[0].body:
846 if isinstance(element, ast.Return):
846 if isinstance(element, ast.Return):
847 multiline = False
847 multiline = False
848 else:
848 else:
849 output.append(u'')
849 output.append(u'')
850 multiline = False
850 multiline = False
851 except Exception:
851 except Exception:
852 pass
852 pass
853
853
854 if savefig: # clear figure if plotted
854 if savefig: # clear figure if plotted
855 self.ensure_pyplot()
855 self.ensure_pyplot()
856 self.process_input_line('plt.clf()', store_history=False)
856 self.process_input_line('plt.clf()', store_history=False)
857 self.clear_cout()
857 self.clear_cout()
858 savefig = False
858 savefig = False
859
859
860 return output
860 return output
861
861
862 def custom_doctest(self, decorator, input_lines, found, submitted):
862 def custom_doctest(self, decorator, input_lines, found, submitted):
863 """
863 """
864 Perform a specialized doctest.
864 Perform a specialized doctest.
865
865
866 """
866 """
867 from .custom_doctests import doctests
867 from .custom_doctests import doctests
868
868
869 args = decorator.split()
869 args = decorator.split()
870 doctest_type = args[1]
870 doctest_type = args[1]
871 if doctest_type in doctests:
871 if doctest_type in doctests:
872 doctests[doctest_type](self, args, input_lines, found, submitted)
872 doctests[doctest_type](self, args, input_lines, found, submitted)
873 else:
873 else:
874 e = "Invalid option to @doctest: {0}".format(doctest_type)
874 e = "Invalid option to @doctest: {0}".format(doctest_type)
875 raise Exception(e)
875 raise Exception(e)
876
876
877
877
878 class IPythonDirective(Directive):
878 class IPythonDirective(Directive):
879
879
880 has_content = True
880 has_content = True
881 required_arguments = 0
881 required_arguments = 0
882 optional_arguments = 4 # python, suppress, verbatim, doctest
882 optional_arguments = 4 # python, suppress, verbatim, doctest
883 final_argumuent_whitespace = True
883 final_argumuent_whitespace = True
884 option_spec = { 'python': directives.unchanged,
884 option_spec = { 'python': directives.unchanged,
885 'suppress' : directives.flag,
885 'suppress' : directives.flag,
886 'verbatim' : directives.flag,
886 'verbatim' : directives.flag,
887 'doctest' : directives.flag,
887 'doctest' : directives.flag,
888 'okexcept': directives.flag,
888 'okexcept': directives.flag,
889 'okwarning': directives.flag
889 'okwarning': directives.flag
890 }
890 }
891
891
892 shell = None
892 shell = None
893
893
894 seen_docs = set()
894 seen_docs = set()
895
895
896 def get_config_options(self):
896 def get_config_options(self):
897 # contains sphinx configuration variables
897 # contains sphinx configuration variables
898 config = self.state.document.settings.env.config
898 config = self.state.document.settings.env.config
899
899
900 # get config variables to set figure output directory
900 # get config variables to set figure output directory
901 savefig_dir = config.ipython_savefig_dir
901 savefig_dir = config.ipython_savefig_dir
902 source_dir = self.state.document.settings.env.srcdir
902 source_dir = self.state.document.settings.env.srcdir
903 savefig_dir = os.path.join(source_dir, savefig_dir)
903 savefig_dir = os.path.join(source_dir, savefig_dir)
904
904
905 # get regex and prompt stuff
905 # get regex and prompt stuff
906 rgxin = config.ipython_rgxin
906 rgxin = config.ipython_rgxin
907 rgxout = config.ipython_rgxout
907 rgxout = config.ipython_rgxout
908 warning_is_error= config.ipython_warning_is_error
908 warning_is_error= config.ipython_warning_is_error
909 promptin = config.ipython_promptin
909 promptin = config.ipython_promptin
910 promptout = config.ipython_promptout
910 promptout = config.ipython_promptout
911 mplbackend = config.ipython_mplbackend
911 mplbackend = config.ipython_mplbackend
912 exec_lines = config.ipython_execlines
912 exec_lines = config.ipython_execlines
913 hold_count = config.ipython_holdcount
913 hold_count = config.ipython_holdcount
914
914
915 return (savefig_dir, source_dir, rgxin, rgxout,
915 return (savefig_dir, source_dir, rgxin, rgxout,
916 promptin, promptout, mplbackend, exec_lines, hold_count, warning_is_error)
916 promptin, promptout, mplbackend, exec_lines, hold_count, warning_is_error)
917
917
918 def setup(self):
918 def setup(self):
919 # Get configuration values.
919 # Get configuration values.
920 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
920 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
921 mplbackend, exec_lines, hold_count, warning_is_error) = self.get_config_options()
921 mplbackend, exec_lines, hold_count, warning_is_error) = self.get_config_options()
922
922
923 try:
923 try:
924 os.makedirs(savefig_dir)
924 os.makedirs(savefig_dir)
925 except OSError as e:
925 except OSError as e:
926 if e.errno != errno.EEXIST:
926 if e.errno != errno.EEXIST:
927 raise
927 raise
928
928
929 if self.shell is None:
929 if self.shell is None:
930 # We will be here many times. However, when the
930 # We will be here many times. However, when the
931 # EmbeddedSphinxShell is created, its interactive shell member
931 # EmbeddedSphinxShell is created, its interactive shell member
932 # is the same for each instance.
932 # is the same for each instance.
933
933
934 if mplbackend and 'matplotlib.backends' not in sys.modules and use_matpltolib:
934 if mplbackend and 'matplotlib.backends' not in sys.modules and use_matpltolib:
935 import matplotlib
935 import matplotlib
936 matplotlib.use(mplbackend)
936 matplotlib.use(mplbackend)
937
937
938 # Must be called after (potentially) importing matplotlib and
938 # Must be called after (potentially) importing matplotlib and
939 # setting its backend since exec_lines might import pylab.
939 # setting its backend since exec_lines might import pylab.
940 self.shell = EmbeddedSphinxShell(exec_lines)
940 self.shell = EmbeddedSphinxShell(exec_lines)
941
941
942 # Store IPython directive to enable better error messages
942 # Store IPython directive to enable better error messages
943 self.shell.directive = self
943 self.shell.directive = self
944
944
945 # reset the execution count if we haven't processed this doc
945 # reset the execution count if we haven't processed this doc
946 #NOTE: this may be borked if there are multiple seen_doc tmp files
946 #NOTE: this may be borked if there are multiple seen_doc tmp files
947 #check time stamp?
947 #check time stamp?
948 if not self.state.document.current_source in self.seen_docs:
948 if not self.state.document.current_source in self.seen_docs:
949 self.shell.IP.history_manager.reset()
949 self.shell.IP.history_manager.reset()
950 self.shell.IP.execution_count = 1
950 self.shell.IP.execution_count = 1
951 self.seen_docs.add(self.state.document.current_source)
951 self.seen_docs.add(self.state.document.current_source)
952
952
953 # and attach to shell so we don't have to pass them around
953 # and attach to shell so we don't have to pass them around
954 self.shell.rgxin = rgxin
954 self.shell.rgxin = rgxin
955 self.shell.rgxout = rgxout
955 self.shell.rgxout = rgxout
956 self.shell.promptin = promptin
956 self.shell.promptin = promptin
957 self.shell.promptout = promptout
957 self.shell.promptout = promptout
958 self.shell.savefig_dir = savefig_dir
958 self.shell.savefig_dir = savefig_dir
959 self.shell.source_dir = source_dir
959 self.shell.source_dir = source_dir
960 self.shell.hold_count = hold_count
960 self.shell.hold_count = hold_count
961 self.shell.warning_is_error = warning_is_error
961 self.shell.warning_is_error = warning_is_error
962
962
963 # setup bookmark for saving figures directory
963 # setup bookmark for saving figures directory
964 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
964 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
965 store_history=False)
965 store_history=False)
966 self.shell.clear_cout()
966 self.shell.clear_cout()
967
967
968 return rgxin, rgxout, promptin, promptout
968 return rgxin, rgxout, promptin, promptout
969
969
970 def teardown(self):
970 def teardown(self):
971 # delete last bookmark
971 # delete last bookmark
972 self.shell.process_input_line('bookmark -d ipy_savedir',
972 self.shell.process_input_line('bookmark -d ipy_savedir',
973 store_history=False)
973 store_history=False)
974 self.shell.clear_cout()
974 self.shell.clear_cout()
975
975
976 def run(self):
976 def run(self):
977 debug = False
977 debug = False
978
978
979 #TODO, any reason block_parser can't be a method of embeddable shell
979 #TODO, any reason block_parser can't be a method of embeddable shell
980 # then we wouldn't have to carry these around
980 # then we wouldn't have to carry these around
981 rgxin, rgxout, promptin, promptout = self.setup()
981 rgxin, rgxout, promptin, promptout = self.setup()
982
982
983 options = self.options
983 options = self.options
984 self.shell.is_suppress = 'suppress' in options
984 self.shell.is_suppress = 'suppress' in options
985 self.shell.is_doctest = 'doctest' in options
985 self.shell.is_doctest = 'doctest' in options
986 self.shell.is_verbatim = 'verbatim' in options
986 self.shell.is_verbatim = 'verbatim' in options
987 self.shell.is_okexcept = 'okexcept' in options
987 self.shell.is_okexcept = 'okexcept' in options
988 self.shell.is_okwarning = 'okwarning' in options
988 self.shell.is_okwarning = 'okwarning' in options
989
989
990 # handle pure python code
990 # handle pure python code
991 if 'python' in self.arguments:
991 if 'python' in self.arguments:
992 content = self.content
992 content = self.content
993 self.content = self.shell.process_pure_python(content)
993 self.content = self.shell.process_pure_python(content)
994
994
995 # parts consists of all text within the ipython-block.
995 # parts consists of all text within the ipython-block.
996 # Each part is an input/output block.
996 # Each part is an input/output block.
997 parts = '\n'.join(self.content).split('\n\n')
997 parts = '\n'.join(self.content).split('\n\n')
998
998
999 lines = ['.. code-block:: ipython', '']
999 lines = ['.. code-block:: ipython', '']
1000 figures = []
1000 figures = []
1001
1001
1002 for part in parts:
1002 for part in parts:
1003 block = block_parser(part, rgxin, rgxout, promptin, promptout)
1003 block = block_parser(part, rgxin, rgxout, promptin, promptout)
1004 if len(block):
1004 if len(block):
1005 rows, figure = self.shell.process_block(block)
1005 rows, figure = self.shell.process_block(block)
1006 for row in rows:
1006 for row in rows:
1007 lines.extend([' {0}'.format(line)
1007 lines.extend([' {0}'.format(line)
1008 for line in row.split('\n')])
1008 for line in row.split('\n')])
1009
1009
1010 if figure is not None:
1010 if figure is not None:
1011 figures.append(figure)
1011 figures.append(figure)
1012 else:
1012 else:
1013 message = 'Code input with no code at {}, line {}'\
1013 message = 'Code input with no code at {}, line {}'\
1014 .format(
1014 .format(
1015 self.state.document.current_source,
1015 self.state.document.current_source,
1016 self.state.document.current_line)
1016 self.state.document.current_line)
1017 if self.shell.warning_is_error:
1017 if self.shell.warning_is_error:
1018 raise RuntimeError(message)
1018 raise RuntimeError(message)
1019 else:
1019 else:
1020 warnings.warn(message)
1020 warnings.warn(message)
1021
1021
1022 for figure in figures:
1022 for figure in figures:
1023 lines.append('')
1023 lines.append('')
1024 lines.extend(figure.split('\n'))
1024 lines.extend(figure.split('\n'))
1025 lines.append('')
1025 lines.append('')
1026
1026
1027 if len(lines) > 2:
1027 if len(lines) > 2:
1028 if debug:
1028 if debug:
1029 print('\n'.join(lines))
1029 print('\n'.join(lines))
1030 else:
1030 else:
1031 # This has to do with input, not output. But if we comment
1031 # This has to do with input, not output. But if we comment
1032 # these lines out, then no IPython code will appear in the
1032 # these lines out, then no IPython code will appear in the
1033 # final output.
1033 # final output.
1034 self.state_machine.insert_input(
1034 self.state_machine.insert_input(
1035 lines, self.state_machine.input_lines.source(0))
1035 lines, self.state_machine.input_lines.source(0))
1036
1036
1037 # cleanup
1037 # cleanup
1038 self.teardown()
1038 self.teardown()
1039
1039
1040 return []
1040 return []
1041
1041
1042 # Enable as a proper Sphinx directive
1042 # Enable as a proper Sphinx directive
1043 def setup(app):
1043 def setup(app):
1044 setup.app = app
1044 setup.app = app
1045
1045
1046 app.add_directive('ipython', IPythonDirective)
1046 app.add_directive('ipython', IPythonDirective)
1047 app.add_config_value('ipython_savefig_dir', 'savefig', 'env')
1047 app.add_config_value('ipython_savefig_dir', 'savefig', 'env')
1048 app.add_config_value('ipython_warning_is_error', True, 'env')
1048 app.add_config_value('ipython_warning_is_error', True, 'env')
1049 app.add_config_value('ipython_rgxin',
1049 app.add_config_value('ipython_rgxin',
1050 re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env')
1050 re.compile(r'In \[(\d+)\]:\s?(.*)\s*'), 'env')
1051 app.add_config_value('ipython_rgxout',
1051 app.add_config_value('ipython_rgxout',
1052 re.compile('Out\[(\d+)\]:\s?(.*)\s*'), 'env')
1052 re.compile(r'Out\[(\d+)\]:\s?(.*)\s*'), 'env')
1053 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
1053 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
1054 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
1054 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
1055
1055
1056 # We could just let matplotlib pick whatever is specified as the default
1056 # We could just let matplotlib pick whatever is specified as the default
1057 # backend in the matplotlibrc file, but this would cause issues if the
1057 # backend in the matplotlibrc file, but this would cause issues if the
1058 # backend didn't work in headless environments. For this reason, 'agg'
1058 # backend didn't work in headless environments. For this reason, 'agg'
1059 # is a good default backend choice.
1059 # is a good default backend choice.
1060 app.add_config_value('ipython_mplbackend', 'agg', 'env')
1060 app.add_config_value('ipython_mplbackend', 'agg', 'env')
1061
1061
1062 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
1062 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
1063 # __init__ method will treat it as [].
1063 # __init__ method will treat it as [].
1064 execlines = ['import numpy as np']
1064 execlines = ['import numpy as np']
1065 if use_matpltolib:
1065 if use_matpltolib:
1066 execlines.append('import matplotlib.pyplot as plt')
1066 execlines.append('import matplotlib.pyplot as plt')
1067 app.add_config_value('ipython_execlines', execlines, 'env')
1067 app.add_config_value('ipython_execlines', execlines, 'env')
1068
1068
1069 app.add_config_value('ipython_holdcount', True, 'env')
1069 app.add_config_value('ipython_holdcount', True, 'env')
1070
1070
1071 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
1071 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
1072 return metadata
1072 return metadata
1073
1073
1074 # Simple smoke test, needs to be converted to a proper automatic test.
1074 # Simple smoke test, needs to be converted to a proper automatic test.
1075 def test():
1075 def test():
1076
1076
1077 examples = [
1077 examples = [
1078 r"""
1078 r"""
1079 In [9]: pwd
1079 In [9]: pwd
1080 Out[9]: '/home/jdhunter/py4science/book'
1080 Out[9]: '/home/jdhunter/py4science/book'
1081
1081
1082 In [10]: cd bookdata/
1082 In [10]: cd bookdata/
1083 /home/jdhunter/py4science/book/bookdata
1083 /home/jdhunter/py4science/book/bookdata
1084
1084
1085 In [2]: from pylab import *
1085 In [2]: from pylab import *
1086
1086
1087 In [2]: ion()
1087 In [2]: ion()
1088
1088
1089 In [3]: im = imread('stinkbug.png')
1089 In [3]: im = imread('stinkbug.png')
1090
1090
1091 @savefig mystinkbug.png width=4in
1091 @savefig mystinkbug.png width=4in
1092 In [4]: imshow(im)
1092 In [4]: imshow(im)
1093 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
1093 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
1094
1094
1095 """,
1095 """,
1096 r"""
1096 r"""
1097
1097
1098 In [1]: x = 'hello world'
1098 In [1]: x = 'hello world'
1099
1099
1100 # string methods can be
1100 # string methods can be
1101 # used to alter the string
1101 # used to alter the string
1102 @doctest
1102 @doctest
1103 In [2]: x.upper()
1103 In [2]: x.upper()
1104 Out[2]: 'HELLO WORLD'
1104 Out[2]: 'HELLO WORLD'
1105
1105
1106 @verbatim
1106 @verbatim
1107 In [3]: x.st<TAB>
1107 In [3]: x.st<TAB>
1108 x.startswith x.strip
1108 x.startswith x.strip
1109 """,
1109 """,
1110 r"""
1110 r"""
1111
1111
1112 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
1112 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
1113 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
1113 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
1114
1114
1115 In [131]: print url.split('&')
1115 In [131]: print url.split('&')
1116 ['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']
1116 ['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']
1117
1117
1118 In [60]: import urllib
1118 In [60]: import urllib
1119
1119
1120 """,
1120 """,
1121 r"""\
1121 r"""\
1122
1122
1123 In [133]: import numpy.random
1123 In [133]: import numpy.random
1124
1124
1125 @suppress
1125 @suppress
1126 In [134]: numpy.random.seed(2358)
1126 In [134]: numpy.random.seed(2358)
1127
1127
1128 @doctest
1128 @doctest
1129 In [135]: numpy.random.rand(10,2)
1129 In [135]: numpy.random.rand(10,2)
1130 Out[135]:
1130 Out[135]:
1131 array([[ 0.64524308, 0.59943846],
1131 array([[ 0.64524308, 0.59943846],
1132 [ 0.47102322, 0.8715456 ],
1132 [ 0.47102322, 0.8715456 ],
1133 [ 0.29370834, 0.74776844],
1133 [ 0.29370834, 0.74776844],
1134 [ 0.99539577, 0.1313423 ],
1134 [ 0.99539577, 0.1313423 ],
1135 [ 0.16250302, 0.21103583],
1135 [ 0.16250302, 0.21103583],
1136 [ 0.81626524, 0.1312433 ],
1136 [ 0.81626524, 0.1312433 ],
1137 [ 0.67338089, 0.72302393],
1137 [ 0.67338089, 0.72302393],
1138 [ 0.7566368 , 0.07033696],
1138 [ 0.7566368 , 0.07033696],
1139 [ 0.22591016, 0.77731835],
1139 [ 0.22591016, 0.77731835],
1140 [ 0.0072729 , 0.34273127]])
1140 [ 0.0072729 , 0.34273127]])
1141
1141
1142 """,
1142 """,
1143
1143
1144 r"""
1144 r"""
1145 In [106]: print x
1145 In [106]: print x
1146 jdh
1146 jdh
1147
1147
1148 In [109]: for i in range(10):
1148 In [109]: for i in range(10):
1149 .....: print i
1149 .....: print i
1150 .....:
1150 .....:
1151 .....:
1151 .....:
1152 0
1152 0
1153 1
1153 1
1154 2
1154 2
1155 3
1155 3
1156 4
1156 4
1157 5
1157 5
1158 6
1158 6
1159 7
1159 7
1160 8
1160 8
1161 9
1161 9
1162 """,
1162 """,
1163
1163
1164 r"""
1164 r"""
1165
1165
1166 In [144]: from pylab import *
1166 In [144]: from pylab import *
1167
1167
1168 In [145]: ion()
1168 In [145]: ion()
1169
1169
1170 # use a semicolon to suppress the output
1170 # use a semicolon to suppress the output
1171 @savefig test_hist.png width=4in
1171 @savefig test_hist.png width=4in
1172 In [151]: hist(np.random.randn(10000), 100);
1172 In [151]: hist(np.random.randn(10000), 100);
1173
1173
1174
1174
1175 @savefig test_plot.png width=4in
1175 @savefig test_plot.png width=4in
1176 In [151]: plot(np.random.randn(10000), 'o');
1176 In [151]: plot(np.random.randn(10000), 'o');
1177 """,
1177 """,
1178
1178
1179 r"""
1179 r"""
1180 # use a semicolon to suppress the output
1180 # use a semicolon to suppress the output
1181 In [151]: plt.clf()
1181 In [151]: plt.clf()
1182
1182
1183 @savefig plot_simple.png width=4in
1183 @savefig plot_simple.png width=4in
1184 In [151]: plot([1,2,3])
1184 In [151]: plot([1,2,3])
1185
1185
1186 @savefig hist_simple.png width=4in
1186 @savefig hist_simple.png width=4in
1187 In [151]: hist(np.random.randn(10000), 100);
1187 In [151]: hist(np.random.randn(10000), 100);
1188
1188
1189 """,
1189 """,
1190 r"""
1190 r"""
1191 # update the current fig
1191 # update the current fig
1192 In [151]: ylabel('number')
1192 In [151]: ylabel('number')
1193
1193
1194 In [152]: title('normal distribution')
1194 In [152]: title('normal distribution')
1195
1195
1196
1196
1197 @savefig hist_with_text.png
1197 @savefig hist_with_text.png
1198 In [153]: grid(True)
1198 In [153]: grid(True)
1199
1199
1200 @doctest float
1200 @doctest float
1201 In [154]: 0.1 + 0.2
1201 In [154]: 0.1 + 0.2
1202 Out[154]: 0.3
1202 Out[154]: 0.3
1203
1203
1204 @doctest float
1204 @doctest float
1205 In [155]: np.arange(16).reshape(4,4)
1205 In [155]: np.arange(16).reshape(4,4)
1206 Out[155]:
1206 Out[155]:
1207 array([[ 0, 1, 2, 3],
1207 array([[ 0, 1, 2, 3],
1208 [ 4, 5, 6, 7],
1208 [ 4, 5, 6, 7],
1209 [ 8, 9, 10, 11],
1209 [ 8, 9, 10, 11],
1210 [12, 13, 14, 15]])
1210 [12, 13, 14, 15]])
1211
1211
1212 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1212 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1213
1213
1214 In [2]: x[0,0] = np.inf
1214 In [2]: x[0,0] = np.inf
1215
1215
1216 In [3]: x[0,1] = np.nan
1216 In [3]: x[0,1] = np.nan
1217
1217
1218 @doctest float
1218 @doctest float
1219 In [4]: x
1219 In [4]: x
1220 Out[4]:
1220 Out[4]:
1221 array([[ inf, nan, 2., 3.],
1221 array([[ inf, nan, 2., 3.],
1222 [ 4., 5., 6., 7.],
1222 [ 4., 5., 6., 7.],
1223 [ 8., 9., 10., 11.],
1223 [ 8., 9., 10., 11.],
1224 [ 12., 13., 14., 15.]])
1224 [ 12., 13., 14., 15.]])
1225
1225
1226
1226
1227 """,
1227 """,
1228 ]
1228 ]
1229 # skip local-file depending first example:
1229 # skip local-file depending first example:
1230 examples = examples[1:]
1230 examples = examples[1:]
1231
1231
1232 #ipython_directive.DEBUG = True # dbg
1232 #ipython_directive.DEBUG = True # dbg
1233 #options = dict(suppress=True) # dbg
1233 #options = dict(suppress=True) # dbg
1234 options = {}
1234 options = {}
1235 for example in examples:
1235 for example in examples:
1236 content = example.split('\n')
1236 content = example.split('\n')
1237 IPythonDirective('debug', arguments=None, options=options,
1237 IPythonDirective('debug', arguments=None, options=options,
1238 content=content, lineno=0,
1238 content=content, lineno=0,
1239 content_offset=None, block_text=None,
1239 content_offset=None, block_text=None,
1240 state=None, state_machine=None,
1240 state=None, state_machine=None,
1241 )
1241 )
1242
1242
1243 # Run test suite as a script
1243 # Run test suite as a script
1244 if __name__=='__main__':
1244 if __name__=='__main__':
1245 if not os.path.isdir('_static'):
1245 if not os.path.isdir('_static'):
1246 os.mkdir('_static')
1246 os.mkdir('_static')
1247 test()
1247 test()
1248 print('All OK? Check figures in _static/')
1248 print('All OK? Check figures in _static/')
@@ -1,74 +1,74 b''
1 """Test embedding of IPython"""
1 """Test embedding of IPython"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import sys
15 import sys
16 from IPython.testing.decorators import skip_win32
16 from IPython.testing.decorators import skip_win32
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Tests
19 # Tests
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 @skip_win32
22 @skip_win32
23 def test_debug_magic_passes_through_generators():
23 def test_debug_magic_passes_through_generators():
24 """
24 """
25 This test that we can correctly pass through frames of a generator post-mortem.
25 This test that we can correctly pass through frames of a generator post-mortem.
26 """
26 """
27 import pexpect
27 import pexpect
28 import re
28 import re
29 in_prompt = re.compile(b'In ?\[\\d+\]:')
29 in_prompt = re.compile(br'In ?\[\d+\]:')
30 ipdb_prompt = 'ipdb>'
30 ipdb_prompt = 'ipdb>'
31 env = os.environ.copy()
31 env = os.environ.copy()
32 child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor', '--simple-prompt'],
32 child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor', '--simple-prompt'],
33 env=env)
33 env=env)
34 child.timeout = 2
34 child.timeout = 2
35
35
36 child.expect(in_prompt)
36 child.expect(in_prompt)
37 child.sendline("def f(x):")
37 child.sendline("def f(x):")
38 child.sendline(" raise Exception")
38 child.sendline(" raise Exception")
39 child.sendline("")
39 child.sendline("")
40
40
41 child.expect(in_prompt)
41 child.expect(in_prompt)
42 child.sendline("gen = (f(x) for x in [0])")
42 child.sendline("gen = (f(x) for x in [0])")
43 child.sendline("")
43 child.sendline("")
44
44
45 child.expect(in_prompt)
45 child.expect(in_prompt)
46 child.sendline("for x in gen:")
46 child.sendline("for x in gen:")
47 child.sendline(" pass")
47 child.sendline(" pass")
48 child.sendline("")
48 child.sendline("")
49
49
50 child.expect('Exception:')
50 child.expect('Exception:')
51
51
52 child.expect(in_prompt)
52 child.expect(in_prompt)
53 child.sendline(r'%debug')
53 child.sendline(r'%debug')
54 child.expect('----> 2 raise Exception')
54 child.expect('----> 2 raise Exception')
55
55
56 child.expect(ipdb_prompt)
56 child.expect(ipdb_prompt)
57 child.sendline('u')
57 child.sendline('u')
58 child.expect_exact(r'----> 1 gen = (f(x) for x in [0])')
58 child.expect_exact(r'----> 1 gen = (f(x) for x in [0])')
59
59
60 child.expect(ipdb_prompt)
60 child.expect(ipdb_prompt)
61 child.sendline('u')
61 child.sendline('u')
62 child.expect_exact('----> 1 for x in gen:')
62 child.expect_exact('----> 1 for x in gen:')
63
63
64 child.expect(ipdb_prompt)
64 child.expect(ipdb_prompt)
65 child.sendline('u')
65 child.sendline('u')
66 child.expect_exact('*** Oldest frame')
66 child.expect_exact('*** Oldest frame')
67
67
68 child.expect(ipdb_prompt)
68 child.expect(ipdb_prompt)
69 child.sendline('exit')
69 child.sendline('exit')
70
70
71 child.expect(in_prompt)
71 child.expect(in_prompt)
72 child.sendline('exit')
72 child.sendline('exit')
73
73
74 child.close()
74 child.close()
@@ -1,453 +1,453 b''
1 """Attempt to generate templates for module reference with Sphinx
1 """Attempt to generate templates for module reference with Sphinx
2
2
3 XXX - we exclude extension modules
3 XXX - we exclude extension modules
4
4
5 To include extension modules, first identify them as valid in the
5 To include extension modules, first identify them as valid in the
6 ``_uri2path`` method, then handle them in the ``_parse_module`` script.
6 ``_uri2path`` method, then handle them in the ``_parse_module`` script.
7
7
8 We get functions and classes by parsing the text of .py files.
8 We get functions and classes by parsing the text of .py files.
9 Alternatively we could import the modules for discovery, and we'd have
9 Alternatively we could import the modules for discovery, and we'd have
10 to do that for extension modules. This would involve changing the
10 to do that for extension modules. This would involve changing the
11 ``_parse_module`` method to work via import and introspection, and
11 ``_parse_module`` method to work via import and introspection, and
12 might involve changing ``discover_modules`` (which determines which
12 might involve changing ``discover_modules`` (which determines which
13 files are modules, and therefore which module URIs will be passed to
13 files are modules, and therefore which module URIs will be passed to
14 ``_parse_module``).
14 ``_parse_module``).
15
15
16 NOTE: this is a modified version of a script originally shipped with the
16 NOTE: this is a modified version of a script originally shipped with the
17 PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed
17 PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed
18 project."""
18 project."""
19
19
20
20
21 # Stdlib imports
21 # Stdlib imports
22 import ast
22 import ast
23 import inspect
23 import inspect
24 import os
24 import os
25 import re
25 import re
26 from importlib import import_module
26 from importlib import import_module
27
27
28
28
29 class Obj(object):
29 class Obj(object):
30 '''Namespace to hold arbitrary information.'''
30 '''Namespace to hold arbitrary information.'''
31 def __init__(self, **kwargs):
31 def __init__(self, **kwargs):
32 for k, v in kwargs.items():
32 for k, v in kwargs.items():
33 setattr(self, k, v)
33 setattr(self, k, v)
34
34
35 class FuncClsScanner(ast.NodeVisitor):
35 class FuncClsScanner(ast.NodeVisitor):
36 """Scan a module for top-level functions and classes.
36 """Scan a module for top-level functions and classes.
37
37
38 Skips objects with an @undoc decorator, or a name starting with '_'.
38 Skips objects with an @undoc decorator, or a name starting with '_'.
39 """
39 """
40 def __init__(self):
40 def __init__(self):
41 ast.NodeVisitor.__init__(self)
41 ast.NodeVisitor.__init__(self)
42 self.classes = []
42 self.classes = []
43 self.classes_seen = set()
43 self.classes_seen = set()
44 self.functions = []
44 self.functions = []
45
45
46 @staticmethod
46 @staticmethod
47 def has_undoc_decorator(node):
47 def has_undoc_decorator(node):
48 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
48 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
49 for d in node.decorator_list)
49 for d in node.decorator_list)
50
50
51 def visit_If(self, node):
51 def visit_If(self, node):
52 if isinstance(node.test, ast.Compare) \
52 if isinstance(node.test, ast.Compare) \
53 and isinstance(node.test.left, ast.Name) \
53 and isinstance(node.test.left, ast.Name) \
54 and node.test.left.id == '__name__':
54 and node.test.left.id == '__name__':
55 return # Ignore classes defined in "if __name__ == '__main__':"
55 return # Ignore classes defined in "if __name__ == '__main__':"
56
56
57 self.generic_visit(node)
57 self.generic_visit(node)
58
58
59 def visit_FunctionDef(self, node):
59 def visit_FunctionDef(self, node):
60 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
60 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
61 and node.name not in self.functions:
61 and node.name not in self.functions:
62 self.functions.append(node.name)
62 self.functions.append(node.name)
63
63
64 def visit_ClassDef(self, node):
64 def visit_ClassDef(self, node):
65 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
65 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
66 and node.name not in self.classes_seen:
66 and node.name not in self.classes_seen:
67 cls = Obj(name=node.name)
67 cls = Obj(name=node.name)
68 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
68 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
69 n.name=='__init__' for n in node.body)
69 n.name=='__init__' for n in node.body)
70 self.classes.append(cls)
70 self.classes.append(cls)
71 self.classes_seen.add(node.name)
71 self.classes_seen.add(node.name)
72
72
73 def scan(self, mod):
73 def scan(self, mod):
74 self.visit(mod)
74 self.visit(mod)
75 return self.functions, self.classes
75 return self.functions, self.classes
76
76
77 # Functions and classes
77 # Functions and classes
78 class ApiDocWriter(object):
78 class ApiDocWriter(object):
79 ''' Class for automatic detection and parsing of API docs
79 ''' Class for automatic detection and parsing of API docs
80 to Sphinx-parsable reST format'''
80 to Sphinx-parsable reST format'''
81
81
82 # only separating first two levels
82 # only separating first two levels
83 rst_section_levels = ['*', '=', '-', '~', '^']
83 rst_section_levels = ['*', '=', '-', '~', '^']
84
84
85 def __init__(self,
85 def __init__(self,
86 package_name,
86 package_name,
87 rst_extension='.rst',
87 rst_extension='.rst',
88 package_skip_patterns=None,
88 package_skip_patterns=None,
89 module_skip_patterns=None,
89 module_skip_patterns=None,
90 names_from__all__=None,
90 names_from__all__=None,
91 ):
91 ):
92 ''' Initialize package for parsing
92 ''' Initialize package for parsing
93
93
94 Parameters
94 Parameters
95 ----------
95 ----------
96 package_name : string
96 package_name : string
97 Name of the top-level package. *package_name* must be the
97 Name of the top-level package. *package_name* must be the
98 name of an importable package
98 name of an importable package
99 rst_extension : string, optional
99 rst_extension : string, optional
100 Extension for reST files, default '.rst'
100 Extension for reST files, default '.rst'
101 package_skip_patterns : None or sequence of {strings, regexps}
101 package_skip_patterns : None or sequence of {strings, regexps}
102 Sequence of strings giving URIs of packages to be excluded
102 Sequence of strings giving URIs of packages to be excluded
103 Operates on the package path, starting at (including) the
103 Operates on the package path, starting at (including) the
104 first dot in the package path, after *package_name* - so,
104 first dot in the package path, after *package_name* - so,
105 if *package_name* is ``sphinx``, then ``sphinx.util`` will
105 if *package_name* is ``sphinx``, then ``sphinx.util`` will
106 result in ``.util`` being passed for earching by these
106 result in ``.util`` being passed for earching by these
107 regexps. If is None, gives default. Default is:
107 regexps. If is None, gives default. Default is:
108 ['\.tests$']
108 ['\\.tests$']
109 module_skip_patterns : None or sequence
109 module_skip_patterns : None or sequence
110 Sequence of strings giving URIs of modules to be excluded
110 Sequence of strings giving URIs of modules to be excluded
111 Operates on the module name including preceding URI path,
111 Operates on the module name including preceding URI path,
112 back to the first dot after *package_name*. For example
112 back to the first dot after *package_name*. For example
113 ``sphinx.util.console`` results in the string to search of
113 ``sphinx.util.console`` results in the string to search of
114 ``.util.console``
114 ``.util.console``
115 If is None, gives default. Default is:
115 If is None, gives default. Default is:
116 ['\.setup$', '\._']
116 ['\\.setup$', '\\._']
117 names_from__all__ : set, optional
117 names_from__all__ : set, optional
118 Modules listed in here will be scanned by doing ``from mod import *``,
118 Modules listed in here will be scanned by doing ``from mod import *``,
119 rather than finding function and class definitions by scanning the
119 rather than finding function and class definitions by scanning the
120 AST. This is intended for API modules which expose things defined in
120 AST. This is intended for API modules which expose things defined in
121 other files. Modules listed here must define ``__all__`` to avoid
121 other files. Modules listed here must define ``__all__`` to avoid
122 exposing everything they import.
122 exposing everything they import.
123 '''
123 '''
124 if package_skip_patterns is None:
124 if package_skip_patterns is None:
125 package_skip_patterns = ['\\.tests$']
125 package_skip_patterns = ['\\.tests$']
126 if module_skip_patterns is None:
126 if module_skip_patterns is None:
127 module_skip_patterns = ['\\.setup$', '\\._']
127 module_skip_patterns = ['\\.setup$', '\\._']
128 self.package_name = package_name
128 self.package_name = package_name
129 self.rst_extension = rst_extension
129 self.rst_extension = rst_extension
130 self.package_skip_patterns = package_skip_patterns
130 self.package_skip_patterns = package_skip_patterns
131 self.module_skip_patterns = module_skip_patterns
131 self.module_skip_patterns = module_skip_patterns
132 self.names_from__all__ = names_from__all__ or set()
132 self.names_from__all__ = names_from__all__ or set()
133
133
134 def get_package_name(self):
134 def get_package_name(self):
135 return self._package_name
135 return self._package_name
136
136
137 def set_package_name(self, package_name):
137 def set_package_name(self, package_name):
138 ''' Set package_name
138 ''' Set package_name
139
139
140 >>> docwriter = ApiDocWriter('sphinx')
140 >>> docwriter = ApiDocWriter('sphinx')
141 >>> import sphinx
141 >>> import sphinx
142 >>> docwriter.root_path == sphinx.__path__[0]
142 >>> docwriter.root_path == sphinx.__path__[0]
143 True
143 True
144 >>> docwriter.package_name = 'docutils'
144 >>> docwriter.package_name = 'docutils'
145 >>> import docutils
145 >>> import docutils
146 >>> docwriter.root_path == docutils.__path__[0]
146 >>> docwriter.root_path == docutils.__path__[0]
147 True
147 True
148 '''
148 '''
149 # It's also possible to imagine caching the module parsing here
149 # It's also possible to imagine caching the module parsing here
150 self._package_name = package_name
150 self._package_name = package_name
151 self.root_module = import_module(package_name)
151 self.root_module = import_module(package_name)
152 self.root_path = self.root_module.__path__[0]
152 self.root_path = self.root_module.__path__[0]
153 self.written_modules = None
153 self.written_modules = None
154
154
155 package_name = property(get_package_name, set_package_name, None,
155 package_name = property(get_package_name, set_package_name, None,
156 'get/set package_name')
156 'get/set package_name')
157
157
158 def _uri2path(self, uri):
158 def _uri2path(self, uri):
159 ''' Convert uri to absolute filepath
159 ''' Convert uri to absolute filepath
160
160
161 Parameters
161 Parameters
162 ----------
162 ----------
163 uri : string
163 uri : string
164 URI of python module to return path for
164 URI of python module to return path for
165
165
166 Returns
166 Returns
167 -------
167 -------
168 path : None or string
168 path : None or string
169 Returns None if there is no valid path for this URI
169 Returns None if there is no valid path for this URI
170 Otherwise returns absolute file system path for URI
170 Otherwise returns absolute file system path for URI
171
171
172 Examples
172 Examples
173 --------
173 --------
174 >>> docwriter = ApiDocWriter('sphinx')
174 >>> docwriter = ApiDocWriter('sphinx')
175 >>> import sphinx
175 >>> import sphinx
176 >>> modpath = sphinx.__path__[0]
176 >>> modpath = sphinx.__path__[0]
177 >>> res = docwriter._uri2path('sphinx.builder')
177 >>> res = docwriter._uri2path('sphinx.builder')
178 >>> res == os.path.join(modpath, 'builder.py')
178 >>> res == os.path.join(modpath, 'builder.py')
179 True
179 True
180 >>> res = docwriter._uri2path('sphinx')
180 >>> res = docwriter._uri2path('sphinx')
181 >>> res == os.path.join(modpath, '__init__.py')
181 >>> res == os.path.join(modpath, '__init__.py')
182 True
182 True
183 >>> docwriter._uri2path('sphinx.does_not_exist')
183 >>> docwriter._uri2path('sphinx.does_not_exist')
184
184
185 '''
185 '''
186 if uri == self.package_name:
186 if uri == self.package_name:
187 return os.path.join(self.root_path, '__init__.py')
187 return os.path.join(self.root_path, '__init__.py')
188 path = uri.replace('.', os.path.sep)
188 path = uri.replace('.', os.path.sep)
189 path = path.replace(self.package_name + os.path.sep, '')
189 path = path.replace(self.package_name + os.path.sep, '')
190 path = os.path.join(self.root_path, path)
190 path = os.path.join(self.root_path, path)
191 # XXX maybe check for extensions as well?
191 # XXX maybe check for extensions as well?
192 if os.path.exists(path + '.py'): # file
192 if os.path.exists(path + '.py'): # file
193 path += '.py'
193 path += '.py'
194 elif os.path.exists(os.path.join(path, '__init__.py')):
194 elif os.path.exists(os.path.join(path, '__init__.py')):
195 path = os.path.join(path, '__init__.py')
195 path = os.path.join(path, '__init__.py')
196 else:
196 else:
197 return None
197 return None
198 return path
198 return path
199
199
200 def _path2uri(self, dirpath):
200 def _path2uri(self, dirpath):
201 ''' Convert directory path to uri '''
201 ''' Convert directory path to uri '''
202 relpath = dirpath.replace(self.root_path, self.package_name)
202 relpath = dirpath.replace(self.root_path, self.package_name)
203 if relpath.startswith(os.path.sep):
203 if relpath.startswith(os.path.sep):
204 relpath = relpath[1:]
204 relpath = relpath[1:]
205 return relpath.replace(os.path.sep, '.')
205 return relpath.replace(os.path.sep, '.')
206
206
207 def _parse_module(self, uri):
207 def _parse_module(self, uri):
208 ''' Parse module defined in *uri* '''
208 ''' Parse module defined in *uri* '''
209 filename = self._uri2path(uri)
209 filename = self._uri2path(uri)
210 if filename is None:
210 if filename is None:
211 # nothing that we could handle here.
211 # nothing that we could handle here.
212 return ([],[])
212 return ([],[])
213 with open(filename, 'rb') as f:
213 with open(filename, 'rb') as f:
214 mod = ast.parse(f.read())
214 mod = ast.parse(f.read())
215 return FuncClsScanner().scan(mod)
215 return FuncClsScanner().scan(mod)
216
216
217 def _import_funcs_classes(self, uri):
217 def _import_funcs_classes(self, uri):
218 """Import * from uri, and separate out functions and classes."""
218 """Import * from uri, and separate out functions and classes."""
219 ns = {}
219 ns = {}
220 exec('from %s import *' % uri, ns)
220 exec('from %s import *' % uri, ns)
221 funcs, classes = [], []
221 funcs, classes = [], []
222 for name, obj in ns.items():
222 for name, obj in ns.items():
223 if inspect.isclass(obj):
223 if inspect.isclass(obj):
224 cls = Obj(name=name, has_init='__init__' in obj.__dict__)
224 cls = Obj(name=name, has_init='__init__' in obj.__dict__)
225 classes.append(cls)
225 classes.append(cls)
226 elif inspect.isfunction(obj):
226 elif inspect.isfunction(obj):
227 funcs.append(name)
227 funcs.append(name)
228
228
229 return sorted(funcs), sorted(classes, key=lambda x: x.name)
229 return sorted(funcs), sorted(classes, key=lambda x: x.name)
230
230
231 def find_funcs_classes(self, uri):
231 def find_funcs_classes(self, uri):
232 """Find the functions and classes defined in the module ``uri``"""
232 """Find the functions and classes defined in the module ``uri``"""
233 if uri in self.names_from__all__:
233 if uri in self.names_from__all__:
234 # For API modules which expose things defined elsewhere, import them
234 # For API modules which expose things defined elsewhere, import them
235 return self._import_funcs_classes(uri)
235 return self._import_funcs_classes(uri)
236 else:
236 else:
237 # For other modules, scan their AST to see what they define
237 # For other modules, scan their AST to see what they define
238 return self._parse_module(uri)
238 return self._parse_module(uri)
239
239
240 def generate_api_doc(self, uri):
240 def generate_api_doc(self, uri):
241 '''Make autodoc documentation template string for a module
241 '''Make autodoc documentation template string for a module
242
242
243 Parameters
243 Parameters
244 ----------
244 ----------
245 uri : string
245 uri : string
246 python location of module - e.g 'sphinx.builder'
246 python location of module - e.g 'sphinx.builder'
247
247
248 Returns
248 Returns
249 -------
249 -------
250 S : string
250 S : string
251 Contents of API doc
251 Contents of API doc
252 '''
252 '''
253 # get the names of all classes and functions
253 # get the names of all classes and functions
254 functions, classes = self.find_funcs_classes(uri)
254 functions, classes = self.find_funcs_classes(uri)
255 if not len(functions) and not len(classes):
255 if not len(functions) and not len(classes):
256 #print ('WARNING: Empty -', uri) # dbg
256 #print ('WARNING: Empty -', uri) # dbg
257 return ''
257 return ''
258
258
259 # Make a shorter version of the uri that omits the package name for
259 # Make a shorter version of the uri that omits the package name for
260 # titles
260 # titles
261 uri_short = re.sub(r'^%s\.' % self.package_name,'',uri)
261 uri_short = re.sub(r'^%s\.' % self.package_name,'',uri)
262
262
263 ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n'
263 ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n'
264
264
265 # Set the chapter title to read 'Module:' for all modules except for the
265 # Set the chapter title to read 'Module:' for all modules except for the
266 # main packages
266 # main packages
267 if '.' in uri:
267 if '.' in uri:
268 chap_title = 'Module: :mod:`' + uri_short + '`'
268 chap_title = 'Module: :mod:`' + uri_short + '`'
269 else:
269 else:
270 chap_title = ':mod:`' + uri_short + '`'
270 chap_title = ':mod:`' + uri_short + '`'
271 ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title)
271 ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title)
272
272
273 ad += '\n.. automodule:: ' + uri + '\n'
273 ad += '\n.. automodule:: ' + uri + '\n'
274 ad += '\n.. currentmodule:: ' + uri + '\n'
274 ad += '\n.. currentmodule:: ' + uri + '\n'
275
275
276 if classes:
276 if classes:
277 subhead = str(len(classes)) + (' Classes' if len(classes) > 1 else ' Class')
277 subhead = str(len(classes)) + (' Classes' if len(classes) > 1 else ' Class')
278 ad += '\n'+ subhead + '\n' + \
278 ad += '\n'+ subhead + '\n' + \
279 self.rst_section_levels[2] * len(subhead) + '\n'
279 self.rst_section_levels[2] * len(subhead) + '\n'
280
280
281 for c in classes:
281 for c in classes:
282 ad += '\n.. autoclass:: ' + c.name + '\n'
282 ad += '\n.. autoclass:: ' + c.name + '\n'
283 # must NOT exclude from index to keep cross-refs working
283 # must NOT exclude from index to keep cross-refs working
284 ad += ' :members:\n' \
284 ad += ' :members:\n' \
285 ' :show-inheritance:\n'
285 ' :show-inheritance:\n'
286 if c.has_init:
286 if c.has_init:
287 ad += '\n .. automethod:: __init__\n'
287 ad += '\n .. automethod:: __init__\n'
288
288
289 if functions:
289 if functions:
290 subhead = str(len(functions)) + (' Functions' if len(functions) > 1 else ' Function')
290 subhead = str(len(functions)) + (' Functions' if len(functions) > 1 else ' Function')
291 ad += '\n'+ subhead + '\n' + \
291 ad += '\n'+ subhead + '\n' + \
292 self.rst_section_levels[2] * len(subhead) + '\n'
292 self.rst_section_levels[2] * len(subhead) + '\n'
293 for f in functions:
293 for f in functions:
294 # must NOT exclude from index to keep cross-refs working
294 # must NOT exclude from index to keep cross-refs working
295 ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n'
295 ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n'
296 return ad
296 return ad
297
297
298 def _survives_exclude(self, matchstr, match_type):
298 def _survives_exclude(self, matchstr, match_type):
299 ''' Returns True if *matchstr* does not match patterns
299 ''' Returns True if *matchstr* does not match patterns
300
300
301 ``self.package_name`` removed from front of string if present
301 ``self.package_name`` removed from front of string if present
302
302
303 Examples
303 Examples
304 --------
304 --------
305 >>> dw = ApiDocWriter('sphinx')
305 >>> dw = ApiDocWriter('sphinx')
306 >>> dw._survives_exclude('sphinx.okpkg', 'package')
306 >>> dw._survives_exclude('sphinx.okpkg', 'package')
307 True
307 True
308 >>> dw.package_skip_patterns.append('^\\.badpkg$')
308 >>> dw.package_skip_patterns.append('^\\.badpkg$')
309 >>> dw._survives_exclude('sphinx.badpkg', 'package')
309 >>> dw._survives_exclude('sphinx.badpkg', 'package')
310 False
310 False
311 >>> dw._survives_exclude('sphinx.badpkg', 'module')
311 >>> dw._survives_exclude('sphinx.badpkg', 'module')
312 True
312 True
313 >>> dw._survives_exclude('sphinx.badmod', 'module')
313 >>> dw._survives_exclude('sphinx.badmod', 'module')
314 True
314 True
315 >>> dw.module_skip_patterns.append('^\\.badmod$')
315 >>> dw.module_skip_patterns.append('^\\.badmod$')
316 >>> dw._survives_exclude('sphinx.badmod', 'module')
316 >>> dw._survives_exclude('sphinx.badmod', 'module')
317 False
317 False
318 '''
318 '''
319 if match_type == 'module':
319 if match_type == 'module':
320 patterns = self.module_skip_patterns
320 patterns = self.module_skip_patterns
321 elif match_type == 'package':
321 elif match_type == 'package':
322 patterns = self.package_skip_patterns
322 patterns = self.package_skip_patterns
323 else:
323 else:
324 raise ValueError('Cannot interpret match type "%s"'
324 raise ValueError('Cannot interpret match type "%s"'
325 % match_type)
325 % match_type)
326 # Match to URI without package name
326 # Match to URI without package name
327 L = len(self.package_name)
327 L = len(self.package_name)
328 if matchstr[:L] == self.package_name:
328 if matchstr[:L] == self.package_name:
329 matchstr = matchstr[L:]
329 matchstr = matchstr[L:]
330 for pat in patterns:
330 for pat in patterns:
331 try:
331 try:
332 pat.search
332 pat.search
333 except AttributeError:
333 except AttributeError:
334 pat = re.compile(pat)
334 pat = re.compile(pat)
335 if pat.search(matchstr):
335 if pat.search(matchstr):
336 return False
336 return False
337 return True
337 return True
338
338
339 def discover_modules(self):
339 def discover_modules(self):
340 ''' Return module sequence discovered from ``self.package_name``
340 ''' Return module sequence discovered from ``self.package_name``
341
341
342
342
343 Parameters
343 Parameters
344 ----------
344 ----------
345 None
345 None
346
346
347 Returns
347 Returns
348 -------
348 -------
349 mods : sequence
349 mods : sequence
350 Sequence of module names within ``self.package_name``
350 Sequence of module names within ``self.package_name``
351
351
352 Examples
352 Examples
353 --------
353 --------
354 >>> dw = ApiDocWriter('sphinx')
354 >>> dw = ApiDocWriter('sphinx')
355 >>> mods = dw.discover_modules()
355 >>> mods = dw.discover_modules()
356 >>> 'sphinx.util' in mods
356 >>> 'sphinx.util' in mods
357 True
357 True
358 >>> dw.package_skip_patterns.append('\.util$')
358 >>> dw.package_skip_patterns.append('\\.util$')
359 >>> 'sphinx.util' in dw.discover_modules()
359 >>> 'sphinx.util' in dw.discover_modules()
360 False
360 False
361 >>>
361 >>>
362 '''
362 '''
363 modules = [self.package_name]
363 modules = [self.package_name]
364 # raw directory parsing
364 # raw directory parsing
365 for dirpath, dirnames, filenames in os.walk(self.root_path):
365 for dirpath, dirnames, filenames in os.walk(self.root_path):
366 # Check directory names for packages
366 # Check directory names for packages
367 root_uri = self._path2uri(os.path.join(self.root_path,
367 root_uri = self._path2uri(os.path.join(self.root_path,
368 dirpath))
368 dirpath))
369 for dirname in dirnames[:]: # copy list - we modify inplace
369 for dirname in dirnames[:]: # copy list - we modify inplace
370 package_uri = '.'.join((root_uri, dirname))
370 package_uri = '.'.join((root_uri, dirname))
371 if (self._uri2path(package_uri) and
371 if (self._uri2path(package_uri) and
372 self._survives_exclude(package_uri, 'package')):
372 self._survives_exclude(package_uri, 'package')):
373 modules.append(package_uri)
373 modules.append(package_uri)
374 else:
374 else:
375 dirnames.remove(dirname)
375 dirnames.remove(dirname)
376 # Check filenames for modules
376 # Check filenames for modules
377 for filename in filenames:
377 for filename in filenames:
378 module_name = filename[:-3]
378 module_name = filename[:-3]
379 module_uri = '.'.join((root_uri, module_name))
379 module_uri = '.'.join((root_uri, module_name))
380 if (self._uri2path(module_uri) and
380 if (self._uri2path(module_uri) and
381 self._survives_exclude(module_uri, 'module')):
381 self._survives_exclude(module_uri, 'module')):
382 modules.append(module_uri)
382 modules.append(module_uri)
383 return sorted(modules)
383 return sorted(modules)
384
384
385 def write_modules_api(self, modules,outdir):
385 def write_modules_api(self, modules,outdir):
386 # write the list
386 # write the list
387 written_modules = []
387 written_modules = []
388 for m in modules:
388 for m in modules:
389 api_str = self.generate_api_doc(m)
389 api_str = self.generate_api_doc(m)
390 if not api_str:
390 if not api_str:
391 continue
391 continue
392 # write out to file
392 # write out to file
393 outfile = os.path.join(outdir,
393 outfile = os.path.join(outdir,
394 m + self.rst_extension)
394 m + self.rst_extension)
395 with open(outfile, 'wt') as fileobj:
395 with open(outfile, 'wt') as fileobj:
396 fileobj.write(api_str)
396 fileobj.write(api_str)
397 written_modules.append(m)
397 written_modules.append(m)
398 self.written_modules = written_modules
398 self.written_modules = written_modules
399
399
400 def write_api_docs(self, outdir):
400 def write_api_docs(self, outdir):
401 """Generate API reST files.
401 """Generate API reST files.
402
402
403 Parameters
403 Parameters
404 ----------
404 ----------
405 outdir : string
405 outdir : string
406 Directory name in which to store files
406 Directory name in which to store files
407 We create automatic filenames for each module
407 We create automatic filenames for each module
408
408
409 Returns
409 Returns
410 -------
410 -------
411 None
411 None
412
412
413 Notes
413 Notes
414 -----
414 -----
415 Sets self.written_modules to list of written modules
415 Sets self.written_modules to list of written modules
416 """
416 """
417 if not os.path.exists(outdir):
417 if not os.path.exists(outdir):
418 os.mkdir(outdir)
418 os.mkdir(outdir)
419 # compose list of modules
419 # compose list of modules
420 modules = self.discover_modules()
420 modules = self.discover_modules()
421 self.write_modules_api(modules,outdir)
421 self.write_modules_api(modules,outdir)
422
422
423 def write_index(self, outdir, path='gen.rst', relative_to=None):
423 def write_index(self, outdir, path='gen.rst', relative_to=None):
424 """Make a reST API index file from written files
424 """Make a reST API index file from written files
425
425
426 Parameters
426 Parameters
427 ----------
427 ----------
428 outdir : string
428 outdir : string
429 Directory to which to write generated index file
429 Directory to which to write generated index file
430 path : string
430 path : string
431 Filename to write index to
431 Filename to write index to
432 relative_to : string
432 relative_to : string
433 path to which written filenames are relative. This
433 path to which written filenames are relative. This
434 component of the written file path will be removed from
434 component of the written file path will be removed from
435 outdir, in the generated index. Default is None, meaning,
435 outdir, in the generated index. Default is None, meaning,
436 leave path as it is.
436 leave path as it is.
437 """
437 """
438 if self.written_modules is None:
438 if self.written_modules is None:
439 raise ValueError('No modules written')
439 raise ValueError('No modules written')
440 # Get full filename path
440 # Get full filename path
441 path = os.path.join(outdir, path)
441 path = os.path.join(outdir, path)
442 # Path written into index is relative to rootpath
442 # Path written into index is relative to rootpath
443 if relative_to is not None:
443 if relative_to is not None:
444 relpath = outdir.replace(relative_to + os.path.sep, '')
444 relpath = outdir.replace(relative_to + os.path.sep, '')
445 else:
445 else:
446 relpath = outdir
446 relpath = outdir
447 with open(path,'wt') as idx:
447 with open(path,'wt') as idx:
448 w = idx.write
448 w = idx.write
449 w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
449 w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
450 w('.. autosummary::\n'
450 w('.. autosummary::\n'
451 ' :toctree: %s\n\n' % relpath)
451 ' :toctree: %s\n\n' % relpath)
452 for mod in self.written_modules:
452 for mod in self.written_modules:
453 w(' %s\n' % mod)
453 w(' %s\n' % mod)
@@ -1,403 +1,403 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15
15
16 import re
16 import re
17 import os
17 import os
18 import sys
18 import sys
19
19
20 from distutils import log
20 from distutils import log
21 from distutils.command.build_py import build_py
21 from distutils.command.build_py import build_py
22 from distutils.command.build_scripts import build_scripts
22 from distutils.command.build_scripts import build_scripts
23 from distutils.command.install import install
23 from distutils.command.install import install
24 from distutils.command.install_scripts import install_scripts
24 from distutils.command.install_scripts import install_scripts
25 from distutils.cmd import Command
25 from distutils.cmd import Command
26 from glob import glob
26 from glob import glob
27
27
28 from setupext import install_data_ext
28 from setupext import install_data_ext
29
29
30 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
31 # Useful globals and utility functions
31 # Useful globals and utility functions
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33
33
34 # A few handy globals
34 # A few handy globals
35 isfile = os.path.isfile
35 isfile = os.path.isfile
36 pjoin = os.path.join
36 pjoin = os.path.join
37 repo_root = os.path.dirname(os.path.abspath(__file__))
37 repo_root = os.path.dirname(os.path.abspath(__file__))
38
38
39 def execfile(fname, globs, locs=None):
39 def execfile(fname, globs, locs=None):
40 locs = locs or globs
40 locs = locs or globs
41 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
41 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
42
42
43 # A little utility we'll need below, since glob() does NOT allow you to do
43 # A little utility we'll need below, since glob() does NOT allow you to do
44 # exclusion on multiple endings!
44 # exclusion on multiple endings!
45 def file_doesnt_endwith(test,endings):
45 def file_doesnt_endwith(test,endings):
46 """Return true if test is a file and its name does NOT end with any
46 """Return true if test is a file and its name does NOT end with any
47 of the strings listed in endings."""
47 of the strings listed in endings."""
48 if not isfile(test):
48 if not isfile(test):
49 return False
49 return False
50 for e in endings:
50 for e in endings:
51 if test.endswith(e):
51 if test.endswith(e):
52 return False
52 return False
53 return True
53 return True
54
54
55 #---------------------------------------------------------------------------
55 #---------------------------------------------------------------------------
56 # Basic project information
56 # Basic project information
57 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
58
58
59 # release.py contains version, authors, license, url, keywords, etc.
59 # release.py contains version, authors, license, url, keywords, etc.
60 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
60 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
61
61
62 # Create a dict with the basic information
62 # Create a dict with the basic information
63 # This dict is eventually passed to setup after additional keys are added.
63 # This dict is eventually passed to setup after additional keys are added.
64 setup_args = dict(
64 setup_args = dict(
65 name = name,
65 name = name,
66 version = version,
66 version = version,
67 description = description,
67 description = description,
68 long_description = long_description,
68 long_description = long_description,
69 author = author,
69 author = author,
70 author_email = author_email,
70 author_email = author_email,
71 url = url,
71 url = url,
72 license = license,
72 license = license,
73 platforms = platforms,
73 platforms = platforms,
74 keywords = keywords,
74 keywords = keywords,
75 classifiers = classifiers,
75 classifiers = classifiers,
76 cmdclass = {'install_data': install_data_ext},
76 cmdclass = {'install_data': install_data_ext},
77 project_urls={
77 project_urls={
78 'Documentation': 'https://ipython.readthedocs.io/',
78 'Documentation': 'https://ipython.readthedocs.io/',
79 'Funding' : 'https://numfocus.org/',
79 'Funding' : 'https://numfocus.org/',
80 'Source' : 'https://github.com/ipython/ipython',
80 'Source' : 'https://github.com/ipython/ipython',
81 'Tracker' : 'https://github.com/ipython/ipython/issues',
81 'Tracker' : 'https://github.com/ipython/ipython/issues',
82 }
82 }
83 )
83 )
84
84
85
85
86 #---------------------------------------------------------------------------
86 #---------------------------------------------------------------------------
87 # Find packages
87 # Find packages
88 #---------------------------------------------------------------------------
88 #---------------------------------------------------------------------------
89
89
90 def find_packages():
90 def find_packages():
91 """
91 """
92 Find all of IPython's packages.
92 Find all of IPython's packages.
93 """
93 """
94 excludes = ['deathrow', 'quarantine']
94 excludes = ['deathrow', 'quarantine']
95 packages = []
95 packages = []
96 for dir,subdirs,files in os.walk('IPython'):
96 for dir,subdirs,files in os.walk('IPython'):
97 package = dir.replace(os.path.sep, '.')
97 package = dir.replace(os.path.sep, '.')
98 if any(package.startswith('IPython.'+exc) for exc in excludes):
98 if any(package.startswith('IPython.'+exc) for exc in excludes):
99 # package is to be excluded (e.g. deathrow)
99 # package is to be excluded (e.g. deathrow)
100 continue
100 continue
101 if '__init__.py' not in files:
101 if '__init__.py' not in files:
102 # not a package
102 # not a package
103 continue
103 continue
104 packages.append(package)
104 packages.append(package)
105 return packages
105 return packages
106
106
107 #---------------------------------------------------------------------------
107 #---------------------------------------------------------------------------
108 # Find package data
108 # Find package data
109 #---------------------------------------------------------------------------
109 #---------------------------------------------------------------------------
110
110
111 def find_package_data():
111 def find_package_data():
112 """
112 """
113 Find IPython's package_data.
113 Find IPython's package_data.
114 """
114 """
115 # This is not enough for these things to appear in an sdist.
115 # This is not enough for these things to appear in an sdist.
116 # We need to muck with the MANIFEST to get this to work
116 # We need to muck with the MANIFEST to get this to work
117
117
118 package_data = {
118 package_data = {
119 'IPython.core' : ['profile/README*'],
119 'IPython.core' : ['profile/README*'],
120 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
120 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
121 'IPython.lib.tests' : ['*.wav'],
121 'IPython.lib.tests' : ['*.wav'],
122 'IPython.testing.plugin' : ['*.txt'],
122 'IPython.testing.plugin' : ['*.txt'],
123 }
123 }
124
124
125 return package_data
125 return package_data
126
126
127
127
128 def check_package_data(package_data):
128 def check_package_data(package_data):
129 """verify that package_data globs make sense"""
129 """verify that package_data globs make sense"""
130 print("checking package data")
130 print("checking package data")
131 for pkg, data in package_data.items():
131 for pkg, data in package_data.items():
132 pkg_root = pjoin(*pkg.split('.'))
132 pkg_root = pjoin(*pkg.split('.'))
133 for d in data:
133 for d in data:
134 path = pjoin(pkg_root, d)
134 path = pjoin(pkg_root, d)
135 if '*' in path:
135 if '*' in path:
136 assert len(glob(path)) > 0, "No files match pattern %s" % path
136 assert len(glob(path)) > 0, "No files match pattern %s" % path
137 else:
137 else:
138 assert os.path.exists(path), "Missing package data: %s" % path
138 assert os.path.exists(path), "Missing package data: %s" % path
139
139
140
140
141 def check_package_data_first(command):
141 def check_package_data_first(command):
142 """decorator for checking package_data before running a given command
142 """decorator for checking package_data before running a given command
143
143
144 Probably only needs to wrap build_py
144 Probably only needs to wrap build_py
145 """
145 """
146 class DecoratedCommand(command):
146 class DecoratedCommand(command):
147 def run(self):
147 def run(self):
148 check_package_data(self.package_data)
148 check_package_data(self.package_data)
149 command.run(self)
149 command.run(self)
150 return DecoratedCommand
150 return DecoratedCommand
151
151
152
152
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154 # Find data files
154 # Find data files
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156
156
157 def find_data_files():
157 def find_data_files():
158 """
158 """
159 Find IPython's data_files.
159 Find IPython's data_files.
160
160
161 Just man pages at this point.
161 Just man pages at this point.
162 """
162 """
163
163
164 manpagebase = pjoin('share', 'man', 'man1')
164 manpagebase = pjoin('share', 'man', 'man1')
165
165
166 # Simple file lists can be made by hand
166 # Simple file lists can be made by hand
167 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
167 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
168 if not manpages:
168 if not manpages:
169 # When running from a source tree, the manpages aren't gzipped
169 # When running from a source tree, the manpages aren't gzipped
170 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
170 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
171
171
172 # And assemble the entire output list
172 # And assemble the entire output list
173 data_files = [ (manpagebase, manpages) ]
173 data_files = [ (manpagebase, manpages) ]
174
174
175 return data_files
175 return data_files
176
176
177
177
178 # The two functions below are copied from IPython.utils.path, so we don't need
178 # The two functions below are copied from IPython.utils.path, so we don't need
179 # to import IPython during setup, which fails on Python 3.
179 # to import IPython during setup, which fails on Python 3.
180
180
181 def target_outdated(target,deps):
181 def target_outdated(target,deps):
182 """Determine whether a target is out of date.
182 """Determine whether a target is out of date.
183
183
184 target_outdated(target,deps) -> 1/0
184 target_outdated(target,deps) -> 1/0
185
185
186 deps: list of filenames which MUST exist.
186 deps: list of filenames which MUST exist.
187 target: single filename which may or may not exist.
187 target: single filename which may or may not exist.
188
188
189 If target doesn't exist or is older than any file listed in deps, return
189 If target doesn't exist or is older than any file listed in deps, return
190 true, otherwise return false.
190 true, otherwise return false.
191 """
191 """
192 try:
192 try:
193 target_time = os.path.getmtime(target)
193 target_time = os.path.getmtime(target)
194 except os.error:
194 except os.error:
195 return 1
195 return 1
196 for dep in deps:
196 for dep in deps:
197 dep_time = os.path.getmtime(dep)
197 dep_time = os.path.getmtime(dep)
198 if dep_time > target_time:
198 if dep_time > target_time:
199 #print "For target",target,"Dep failed:",dep # dbg
199 #print "For target",target,"Dep failed:",dep # dbg
200 #print "times (dep,tar):",dep_time,target_time # dbg
200 #print "times (dep,tar):",dep_time,target_time # dbg
201 return 1
201 return 1
202 return 0
202 return 0
203
203
204
204
205 def target_update(target,deps,cmd):
205 def target_update(target,deps,cmd):
206 """Update a target with a given command given a list of dependencies.
206 """Update a target with a given command given a list of dependencies.
207
207
208 target_update(target,deps,cmd) -> runs cmd if target is outdated.
208 target_update(target,deps,cmd) -> runs cmd if target is outdated.
209
209
210 This is just a wrapper around target_outdated() which calls the given
210 This is just a wrapper around target_outdated() which calls the given
211 command if target is outdated."""
211 command if target is outdated."""
212
212
213 if target_outdated(target,deps):
213 if target_outdated(target,deps):
214 os.system(cmd)
214 os.system(cmd)
215
215
216 #---------------------------------------------------------------------------
216 #---------------------------------------------------------------------------
217 # Find scripts
217 # Find scripts
218 #---------------------------------------------------------------------------
218 #---------------------------------------------------------------------------
219
219
220 def find_entry_points():
220 def find_entry_points():
221 """Defines the command line entry points for IPython
221 """Defines the command line entry points for IPython
222
222
223 This always uses setuptools-style entry points. When setuptools is not in
223 This always uses setuptools-style entry points. When setuptools is not in
224 use, our own build_scripts_entrypt class below parses these and builds
224 use, our own build_scripts_entrypt class below parses these and builds
225 command line scripts.
225 command line scripts.
226
226
227 Each of our entry points gets both a plain name, e.g. ipython, and one
227 Each of our entry points gets both a plain name, e.g. ipython, and one
228 suffixed with the Python major version number, e.g. ipython3.
228 suffixed with the Python major version number, e.g. ipython3.
229 """
229 """
230 ep = [
230 ep = [
231 'ipython%s = IPython:start_ipython',
231 'ipython%s = IPython:start_ipython',
232 'iptest%s = IPython.testing.iptestcontroller:main',
232 'iptest%s = IPython.testing.iptestcontroller:main',
233 ]
233 ]
234 suffix = str(sys.version_info[0])
234 suffix = str(sys.version_info[0])
235 return [e % '' for e in ep] + [e % suffix for e in ep]
235 return [e % '' for e in ep] + [e % suffix for e in ep]
236
236
237 script_src = """#!{executable}
237 script_src = """#!{executable}
238 # This script was automatically generated by setup.py
238 # This script was automatically generated by setup.py
239 if __name__ == '__main__':
239 if __name__ == '__main__':
240 from {mod} import {func}
240 from {mod} import {func}
241 {func}()
241 {func}()
242 """
242 """
243
243
244 class build_scripts_entrypt(build_scripts):
244 class build_scripts_entrypt(build_scripts):
245 """Build the command line scripts
245 """Build the command line scripts
246
246
247 Parse setuptools style entry points and write simple scripts to run the
247 Parse setuptools style entry points and write simple scripts to run the
248 target functions.
248 target functions.
249
249
250 On Windows, this also creates .cmd wrappers for the scripts so that you can
250 On Windows, this also creates .cmd wrappers for the scripts so that you can
251 easily launch them from a command line.
251 easily launch them from a command line.
252 """
252 """
253 def run(self):
253 def run(self):
254 self.mkpath(self.build_dir)
254 self.mkpath(self.build_dir)
255 outfiles = []
255 outfiles = []
256 for script in find_entry_points():
256 for script in find_entry_points():
257 name, entrypt = script.split('=')
257 name, entrypt = script.split('=')
258 name = name.strip()
258 name = name.strip()
259 entrypt = entrypt.strip()
259 entrypt = entrypt.strip()
260 outfile = os.path.join(self.build_dir, name)
260 outfile = os.path.join(self.build_dir, name)
261 outfiles.append(outfile)
261 outfiles.append(outfile)
262 print('Writing script to', outfile)
262 print('Writing script to', outfile)
263
263
264 mod, func = entrypt.split(':')
264 mod, func = entrypt.split(':')
265 with open(outfile, 'w') as f:
265 with open(outfile, 'w') as f:
266 f.write(script_src.format(executable=sys.executable,
266 f.write(script_src.format(executable=sys.executable,
267 mod=mod, func=func))
267 mod=mod, func=func))
268
268
269 if sys.platform == 'win32':
269 if sys.platform == 'win32':
270 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
270 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
271 # command line
271 # command line
272 cmd_file = os.path.join(self.build_dir, name + '.cmd')
272 cmd_file = os.path.join(self.build_dir, name + '.cmd')
273 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
273 cmd = r'@"{python}" "%~dp0\{script}" %*\r\n'.format(
274 python=sys.executable, script=name)
274 python=sys.executable, script=name)
275 log.info("Writing %s wrapper script" % cmd_file)
275 log.info("Writing %s wrapper script" % cmd_file)
276 with open(cmd_file, 'w') as f:
276 with open(cmd_file, 'w') as f:
277 f.write(cmd)
277 f.write(cmd)
278
278
279 return outfiles, outfiles
279 return outfiles, outfiles
280
280
281 class install_lib_symlink(Command):
281 class install_lib_symlink(Command):
282 user_options = [
282 user_options = [
283 ('install-dir=', 'd', "directory to install to"),
283 ('install-dir=', 'd', "directory to install to"),
284 ]
284 ]
285
285
286 def initialize_options(self):
286 def initialize_options(self):
287 self.install_dir = None
287 self.install_dir = None
288
288
289 def finalize_options(self):
289 def finalize_options(self):
290 self.set_undefined_options('symlink',
290 self.set_undefined_options('symlink',
291 ('install_lib', 'install_dir'),
291 ('install_lib', 'install_dir'),
292 )
292 )
293
293
294 def run(self):
294 def run(self):
295 if sys.platform == 'win32':
295 if sys.platform == 'win32':
296 raise Exception("This doesn't work on Windows.")
296 raise Exception("This doesn't work on Windows.")
297 pkg = os.path.join(os.getcwd(), 'IPython')
297 pkg = os.path.join(os.getcwd(), 'IPython')
298 dest = os.path.join(self.install_dir, 'IPython')
298 dest = os.path.join(self.install_dir, 'IPython')
299 if os.path.islink(dest):
299 if os.path.islink(dest):
300 print('removing existing symlink at %s' % dest)
300 print('removing existing symlink at %s' % dest)
301 os.unlink(dest)
301 os.unlink(dest)
302 print('symlinking %s -> %s' % (pkg, dest))
302 print('symlinking %s -> %s' % (pkg, dest))
303 os.symlink(pkg, dest)
303 os.symlink(pkg, dest)
304
304
305 class unsymlink(install):
305 class unsymlink(install):
306 def run(self):
306 def run(self):
307 dest = os.path.join(self.install_lib, 'IPython')
307 dest = os.path.join(self.install_lib, 'IPython')
308 if os.path.islink(dest):
308 if os.path.islink(dest):
309 print('removing symlink at %s' % dest)
309 print('removing symlink at %s' % dest)
310 os.unlink(dest)
310 os.unlink(dest)
311 else:
311 else:
312 print('No symlink exists at %s' % dest)
312 print('No symlink exists at %s' % dest)
313
313
314 class install_symlinked(install):
314 class install_symlinked(install):
315 def run(self):
315 def run(self):
316 if sys.platform == 'win32':
316 if sys.platform == 'win32':
317 raise Exception("This doesn't work on Windows.")
317 raise Exception("This doesn't work on Windows.")
318
318
319 # Run all sub-commands (at least those that need to be run)
319 # Run all sub-commands (at least those that need to be run)
320 for cmd_name in self.get_sub_commands():
320 for cmd_name in self.get_sub_commands():
321 self.run_command(cmd_name)
321 self.run_command(cmd_name)
322
322
323 # 'sub_commands': a list of commands this command might have to run to
323 # 'sub_commands': a list of commands this command might have to run to
324 # get its work done. See cmd.py for more info.
324 # get its work done. See cmd.py for more info.
325 sub_commands = [('install_lib_symlink', lambda self:True),
325 sub_commands = [('install_lib_symlink', lambda self:True),
326 ('install_scripts_sym', lambda self:True),
326 ('install_scripts_sym', lambda self:True),
327 ]
327 ]
328
328
329 class install_scripts_for_symlink(install_scripts):
329 class install_scripts_for_symlink(install_scripts):
330 """Redefined to get options from 'symlink' instead of 'install'.
330 """Redefined to get options from 'symlink' instead of 'install'.
331
331
332 I love distutils almost as much as I love setuptools.
332 I love distutils almost as much as I love setuptools.
333 """
333 """
334 def finalize_options(self):
334 def finalize_options(self):
335 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
335 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
336 self.set_undefined_options('symlink',
336 self.set_undefined_options('symlink',
337 ('install_scripts', 'install_dir'),
337 ('install_scripts', 'install_dir'),
338 ('force', 'force'),
338 ('force', 'force'),
339 ('skip_build', 'skip_build'),
339 ('skip_build', 'skip_build'),
340 )
340 )
341
341
342
342
343 #---------------------------------------------------------------------------
343 #---------------------------------------------------------------------------
344 # VCS related
344 # VCS related
345 #---------------------------------------------------------------------------
345 #---------------------------------------------------------------------------
346
346
347
347
348 def git_prebuild(pkg_dir, build_cmd=build_py):
348 def git_prebuild(pkg_dir, build_cmd=build_py):
349 """Return extended build or sdist command class for recording commit
349 """Return extended build or sdist command class for recording commit
350
350
351 records git commit in IPython.utils._sysinfo.commit
351 records git commit in IPython.utils._sysinfo.commit
352
352
353 for use in IPython.utils.sysinfo.sys_info() calls after installation.
353 for use in IPython.utils.sysinfo.sys_info() calls after installation.
354 """
354 """
355
355
356 class MyBuildPy(build_cmd):
356 class MyBuildPy(build_cmd):
357 ''' Subclass to write commit data into installation tree '''
357 ''' Subclass to write commit data into installation tree '''
358 def run(self):
358 def run(self):
359 # loose as `.dev` is suppose to be invalid
359 # loose as `.dev` is suppose to be invalid
360 print("check version number")
360 print("check version number")
361 loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
361 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
362 if not loose_pep440re.match(version):
362 if not loose_pep440re.match(version):
363 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
363 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
364
364
365
365
366 build_cmd.run(self)
366 build_cmd.run(self)
367 # this one will only fire for build commands
367 # this one will only fire for build commands
368 if hasattr(self, 'build_lib'):
368 if hasattr(self, 'build_lib'):
369 self._record_commit(self.build_lib)
369 self._record_commit(self.build_lib)
370
370
371 def make_release_tree(self, base_dir, files):
371 def make_release_tree(self, base_dir, files):
372 # this one will fire for sdist
372 # this one will fire for sdist
373 build_cmd.make_release_tree(self, base_dir, files)
373 build_cmd.make_release_tree(self, base_dir, files)
374 self._record_commit(base_dir)
374 self._record_commit(base_dir)
375
375
376 def _record_commit(self, base_dir):
376 def _record_commit(self, base_dir):
377 import subprocess
377 import subprocess
378 proc = subprocess.Popen('git rev-parse --short HEAD',
378 proc = subprocess.Popen('git rev-parse --short HEAD',
379 stdout=subprocess.PIPE,
379 stdout=subprocess.PIPE,
380 stderr=subprocess.PIPE,
380 stderr=subprocess.PIPE,
381 shell=True)
381 shell=True)
382 repo_commit, _ = proc.communicate()
382 repo_commit, _ = proc.communicate()
383 repo_commit = repo_commit.strip().decode("ascii")
383 repo_commit = repo_commit.strip().decode("ascii")
384
384
385 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
385 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
386 if os.path.isfile(out_pth) and not repo_commit:
386 if os.path.isfile(out_pth) and not repo_commit:
387 # nothing to write, don't clobber
387 # nothing to write, don't clobber
388 return
388 return
389
389
390 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
390 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
391
391
392 # remove to avoid overwriting original via hard link
392 # remove to avoid overwriting original via hard link
393 try:
393 try:
394 os.remove(out_pth)
394 os.remove(out_pth)
395 except (IOError, OSError):
395 except (IOError, OSError):
396 pass
396 pass
397 with open(out_pth, 'w') as out_file:
397 with open(out_pth, 'w') as out_file:
398 out_file.writelines([
398 out_file.writelines([
399 '# GENERATED BY setup.py\n',
399 '# GENERATED BY setup.py\n',
400 'commit = u"%s"\n' % repo_commit,
400 'commit = u"%s"\n' % repo_commit,
401 ])
401 ])
402 return MyBuildPy
402 return MyBuildPy
403
403
General Comments 0
You need to be logged in to leave comments. Login now