##// END OF EJS Templates
Update python2 code example to python3, print '' -> print('')
TenzinRabgy -
Show More
@@ -1,672 +1,672 b''
1 """Module for interactive demos using IPython.
1 """Module for interactive demos using IPython.
2
2
3 This module implements a few classes for running Python scripts interactively
3 This module implements a few classes for running Python scripts interactively
4 in IPython for demonstrations. With very simple markup (a few tags in
4 in IPython for demonstrations. With very simple markup (a few tags in
5 comments), you can control points where the script stops executing and returns
5 comments), you can control points where the script stops executing and returns
6 control to IPython.
6 control to IPython.
7
7
8
8
9 Provided classes
9 Provided classes
10 ----------------
10 ----------------
11
11
12 The classes are (see their docstrings for further details):
12 The classes are (see their docstrings for further details):
13
13
14 - Demo: pure python demos
14 - Demo: pure python demos
15
15
16 - IPythonDemo: demos with input to be processed by IPython as if it had been
16 - IPythonDemo: demos with input to be processed by IPython as if it had been
17 typed interactively (so magics work, as well as any other special syntax you
17 typed interactively (so magics work, as well as any other special syntax you
18 may have added via input prefilters).
18 may have added via input prefilters).
19
19
20 - LineDemo: single-line version of the Demo class. These demos are executed
20 - LineDemo: single-line version of the Demo class. These demos are executed
21 one line at a time, and require no markup.
21 one line at a time, and require no markup.
22
22
23 - IPythonLineDemo: IPython version of the LineDemo class (the demo is
23 - IPythonLineDemo: IPython version of the LineDemo class (the demo is
24 executed a line at a time, but processed via IPython).
24 executed a line at a time, but processed via IPython).
25
25
26 - ClearMixin: mixin to make Demo classes with less visual clutter. It
26 - ClearMixin: mixin to make Demo classes with less visual clutter. It
27 declares an empty marquee and a pre_cmd that clears the screen before each
27 declares an empty marquee and a pre_cmd that clears the screen before each
28 block (see Subclassing below).
28 block (see Subclassing below).
29
29
30 - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo
30 - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo
31 classes.
31 classes.
32
32
33 Inheritance diagram:
33 Inheritance diagram:
34
34
35 .. inheritance-diagram:: IPython.lib.demo
35 .. inheritance-diagram:: IPython.lib.demo
36 :parts: 3
36 :parts: 3
37
37
38 Subclassing
38 Subclassing
39 -----------
39 -----------
40
40
41 The classes here all include a few methods meant to make customization by
41 The classes here all include a few methods meant to make customization by
42 subclassing more convenient. Their docstrings below have some more details:
42 subclassing more convenient. Their docstrings below have some more details:
43
43
44 - highlight(): format every block and optionally highlight comments and
44 - highlight(): format every block and optionally highlight comments and
45 docstring content.
45 docstring content.
46
46
47 - marquee(): generates a marquee to provide visible on-screen markers at each
47 - marquee(): generates a marquee to provide visible on-screen markers at each
48 block start and end.
48 block start and end.
49
49
50 - pre_cmd(): run right before the execution of each block.
50 - pre_cmd(): run right before the execution of each block.
51
51
52 - post_cmd(): run right after the execution of each block. If the block
52 - post_cmd(): run right after the execution of each block. If the block
53 raises an exception, this is NOT called.
53 raises an exception, this is NOT called.
54
54
55
55
56 Operation
56 Operation
57 ---------
57 ---------
58
58
59 The file is run in its own empty namespace (though you can pass it a string of
59 The file is run in its own empty namespace (though you can pass it a string of
60 arguments as if in a command line environment, and it will see those as
60 arguments as if in a command line environment, and it will see those as
61 sys.argv). But at each stop, the global IPython namespace is updated with the
61 sys.argv). But at each stop, the global IPython namespace is updated with the
62 current internal demo namespace, so you can work interactively with the data
62 current internal demo namespace, so you can work interactively with the data
63 accumulated so far.
63 accumulated so far.
64
64
65 By default, each block of code is printed (with syntax highlighting) before
65 By default, each block of code is printed (with syntax highlighting) before
66 executing it and you have to confirm execution. This is intended to show the
66 executing it and you have to confirm execution. This is intended to show the
67 code to an audience first so you can discuss it, and only proceed with
67 code to an audience first so you can discuss it, and only proceed with
68 execution once you agree. There are a few tags which allow you to modify this
68 execution once you agree. There are a few tags which allow you to modify this
69 behavior.
69 behavior.
70
70
71 The supported tags are:
71 The supported tags are:
72
72
73 # <demo> stop
73 # <demo> stop
74
74
75 Defines block boundaries, the points where IPython stops execution of the
75 Defines block boundaries, the points where IPython stops execution of the
76 file and returns to the interactive prompt.
76 file and returns to the interactive prompt.
77
77
78 You can optionally mark the stop tag with extra dashes before and after the
78 You can optionally mark the stop tag with extra dashes before and after the
79 word 'stop', to help visually distinguish the blocks in a text editor:
79 word 'stop', to help visually distinguish the blocks in a text editor:
80
80
81 # <demo> --- stop ---
81 # <demo> --- stop ---
82
82
83
83
84 # <demo> silent
84 # <demo> silent
85
85
86 Make a block execute silently (and hence automatically). Typically used in
86 Make a block execute silently (and hence automatically). Typically used in
87 cases where you have some boilerplate or initialization code which you need
87 cases where you have some boilerplate or initialization code which you need
88 executed but do not want to be seen in the demo.
88 executed but do not want to be seen in the demo.
89
89
90 # <demo> auto
90 # <demo> auto
91
91
92 Make a block execute automatically, but still being printed. Useful for
92 Make a block execute automatically, but still being printed. Useful for
93 simple code which does not warrant discussion, since it avoids the extra
93 simple code which does not warrant discussion, since it avoids the extra
94 manual confirmation.
94 manual confirmation.
95
95
96 # <demo> auto_all
96 # <demo> auto_all
97
97
98 This tag can _only_ be in the first block, and if given it overrides the
98 This tag can _only_ be in the first block, and if given it overrides the
99 individual auto tags to make the whole demo fully automatic (no block asks
99 individual auto tags to make the whole demo fully automatic (no block asks
100 for confirmation). It can also be given at creation time (or the attribute
100 for confirmation). It can also be given at creation time (or the attribute
101 set later) to override what's in the file.
101 set later) to override what's in the file.
102
102
103 While _any_ python file can be run as a Demo instance, if there are no stop
103 While _any_ python file can be run as a Demo instance, if there are no stop
104 tags the whole file will run in a single block (no different that calling
104 tags the whole file will run in a single block (no different that calling
105 first %pycat and then %run). The minimal markup to make this useful is to
105 first %pycat and then %run). The minimal markup to make this useful is to
106 place a set of stop tags; the other tags are only there to let you fine-tune
106 place a set of stop tags; the other tags are only there to let you fine-tune
107 the execution.
107 the execution.
108
108
109 This is probably best explained with the simple example file below. You can
109 This is probably best explained with the simple example file below. You can
110 copy this into a file named ex_demo.py, and try running it via::
110 copy this into a file named ex_demo.py, and try running it via::
111
111
112 from IPython.lib.demo import Demo
112 from IPython.lib.demo import Demo
113 d = Demo('ex_demo.py')
113 d = Demo('ex_demo.py')
114 d()
114 d()
115
115
116 Each time you call the demo object, it runs the next block. The demo object
116 Each time you call the demo object, it runs the next block. The demo object
117 has a few useful methods for navigation, like again(), edit(), jump(), seek()
117 has a few useful methods for navigation, like again(), edit(), jump(), seek()
118 and back(). It can be reset for a new run via reset() or reloaded from disk
118 and back(). It can be reset for a new run via reset() or reloaded from disk
119 (in case you've edited the source) via reload(). See their docstrings below.
119 (in case you've edited the source) via reload(). See their docstrings below.
120
120
121 Note: To make this simpler to explore, a file called "demo-exercizer.py" has
121 Note: To make this simpler to explore, a file called "demo-exercizer.py" has
122 been added to the "docs/examples/core" directory. Just cd to this directory in
122 been added to the "docs/examples/core" directory. Just cd to this directory in
123 an IPython session, and type::
123 an IPython session, and type::
124
124
125 %run demo-exercizer.py
125 %run demo-exercizer.py
126
126
127 and then follow the directions.
127 and then follow the directions.
128
128
129 Example
129 Example
130 -------
130 -------
131
131
132 The following is a very simple example of a valid demo file.
132 The following is a very simple example of a valid demo file.
133
133
134 ::
134 ::
135
135
136 #################### EXAMPLE DEMO <ex_demo.py> ###############################
136 #################### EXAMPLE DEMO <ex_demo.py> ###############################
137 '''A simple interactive demo to illustrate the use of IPython's Demo class.'''
137 '''A simple interactive demo to illustrate the use of IPython's Demo class.'''
138
138
139 print 'Hello, welcome to an interactive IPython demo.'
139 print('Hello, welcome to an interactive IPython demo.')
140
140
141 # The mark below defines a block boundary, which is a point where IPython will
141 # The mark below defines a block boundary, which is a point where IPython will
142 # stop execution and return to the interactive prompt. The dashes are actually
142 # stop execution and return to the interactive prompt. The dashes are actually
143 # optional and used only as a visual aid to clearly separate blocks while
143 # optional and used only as a visual aid to clearly separate blocks while
144 # editing the demo code.
144 # editing the demo code.
145 # <demo> stop
145 # <demo> stop
146
146
147 x = 1
147 x = 1
148 y = 2
148 y = 2
149
149
150 # <demo> stop
150 # <demo> stop
151
151
152 # the mark below makes this block as silent
152 # the mark below makes this block as silent
153 # <demo> silent
153 # <demo> silent
154
154
155 print 'This is a silent block, which gets executed but not printed.'
155 print('This is a silent block, which gets executed but not printed.')
156
156
157 # <demo> stop
157 # <demo> stop
158 # <demo> auto
158 # <demo> auto
159 print 'This is an automatic block.'
159 print('This is an automatic block.')
160 print 'It is executed without asking for confirmation, but printed.'
160 print('It is executed without asking for confirmation, but printed.')
161 z = x+y
161 z = x + y
162
162
163 print 'z=',x
163 print('z =', x)
164
164
165 # <demo> stop
165 # <demo> stop
166 # This is just another normal block.
166 # This is just another normal block.
167 print 'z is now:', z
167 print('z is now:', z)
168
168
169 print 'bye!'
169 print('bye!')
170 ################### END EXAMPLE DEMO <ex_demo.py> ############################
170 ################### END EXAMPLE DEMO <ex_demo.py> ############################
171 """
171 """
172
172
173
173
174 #*****************************************************************************
174 #*****************************************************************************
175 # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu>
175 # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu>
176 #
176 #
177 # Distributed under the terms of the BSD License. The full license is in
177 # Distributed under the terms of the BSD License. The full license is in
178 # the file COPYING, distributed as part of this software.
178 # the file COPYING, distributed as part of this software.
179 #
179 #
180 #*****************************************************************************
180 #*****************************************************************************
181
181
182 import os
182 import os
183 import re
183 import re
184 import shlex
184 import shlex
185 import sys
185 import sys
186 import pygments
186 import pygments
187 from pathlib import Path
187 from pathlib import Path
188
188
189 from IPython.utils.text import marquee
189 from IPython.utils.text import marquee
190 from IPython.utils import openpy
190 from IPython.utils import openpy
191 from IPython.utils import py3compat
191 from IPython.utils import py3compat
192 __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError']
192 __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError']
193
193
194 class DemoError(Exception): pass
194 class DemoError(Exception): pass
195
195
196 def re_mark(mark):
196 def re_mark(mark):
197 return re.compile(r'^\s*#\s+<demo>\s+%s\s*$' % mark,re.MULTILINE)
197 return re.compile(r'^\s*#\s+<demo>\s+%s\s*$' % mark,re.MULTILINE)
198
198
199 class Demo(object):
199 class Demo(object):
200
200
201 re_stop = re_mark(r'-*\s?stop\s?-*')
201 re_stop = re_mark(r'-*\s?stop\s?-*')
202 re_silent = re_mark('silent')
202 re_silent = re_mark('silent')
203 re_auto = re_mark('auto')
203 re_auto = re_mark('auto')
204 re_auto_all = re_mark('auto_all')
204 re_auto_all = re_mark('auto_all')
205
205
206 def __init__(self,src,title='',arg_str='',auto_all=None, format_rst=False,
206 def __init__(self,src,title='',arg_str='',auto_all=None, format_rst=False,
207 formatter='terminal', style='default'):
207 formatter='terminal', style='default'):
208 """Make a new demo object. To run the demo, simply call the object.
208 """Make a new demo object. To run the demo, simply call the object.
209
209
210 See the module docstring for full details and an example (you can use
210 See the module docstring for full details and an example (you can use
211 IPython.Demo? in IPython to see it).
211 IPython.Demo? in IPython to see it).
212
212
213 Inputs:
213 Inputs:
214
214
215 - src is either a file, or file-like object, or a
215 - src is either a file, or file-like object, or a
216 string that can be resolved to a filename.
216 string that can be resolved to a filename.
217
217
218 Optional inputs:
218 Optional inputs:
219
219
220 - title: a string to use as the demo name. Of most use when the demo
220 - title: a string to use as the demo name. Of most use when the demo
221 you are making comes from an object that has no filename, or if you
221 you are making comes from an object that has no filename, or if you
222 want an alternate denotation distinct from the filename.
222 want an alternate denotation distinct from the filename.
223
223
224 - arg_str(''): a string of arguments, internally converted to a list
224 - arg_str(''): a string of arguments, internally converted to a list
225 just like sys.argv, so the demo script can see a similar
225 just like sys.argv, so the demo script can see a similar
226 environment.
226 environment.
227
227
228 - auto_all(None): global flag to run all blocks automatically without
228 - auto_all(None): global flag to run all blocks automatically without
229 confirmation. This attribute overrides the block-level tags and
229 confirmation. This attribute overrides the block-level tags and
230 applies to the whole demo. It is an attribute of the object, and
230 applies to the whole demo. It is an attribute of the object, and
231 can be changed at runtime simply by reassigning it to a boolean
231 can be changed at runtime simply by reassigning it to a boolean
232 value.
232 value.
233
233
234 - format_rst(False): a bool to enable comments and doc strings
234 - format_rst(False): a bool to enable comments and doc strings
235 formatting with pygments rst lexer
235 formatting with pygments rst lexer
236
236
237 - formatter('terminal'): a string of pygments formatter name to be
237 - formatter('terminal'): a string of pygments formatter name to be
238 used. Useful values for terminals: terminal, terminal256,
238 used. Useful values for terminals: terminal, terminal256,
239 terminal16m
239 terminal16m
240
240
241 - style('default'): a string of pygments style name to be used.
241 - style('default'): a string of pygments style name to be used.
242 """
242 """
243 if hasattr(src, "read"):
243 if hasattr(src, "read"):
244 # It seems to be a file or a file-like object
244 # It seems to be a file or a file-like object
245 self.fname = "from a file-like object"
245 self.fname = "from a file-like object"
246 if title == '':
246 if title == '':
247 self.title = "from a file-like object"
247 self.title = "from a file-like object"
248 else:
248 else:
249 self.title = title
249 self.title = title
250 else:
250 else:
251 # Assume it's a string or something that can be converted to one
251 # Assume it's a string or something that can be converted to one
252 self.fname = src
252 self.fname = src
253 if title == '':
253 if title == '':
254 (filepath, filename) = os.path.split(src)
254 (filepath, filename) = os.path.split(src)
255 self.title = filename
255 self.title = filename
256 else:
256 else:
257 self.title = title
257 self.title = title
258 self.sys_argv = [src] + shlex.split(arg_str)
258 self.sys_argv = [src] + shlex.split(arg_str)
259 self.auto_all = auto_all
259 self.auto_all = auto_all
260 self.src = src
260 self.src = src
261
261
262 try:
262 try:
263 ip = get_ipython() # this is in builtins whenever IPython is running
263 ip = get_ipython() # this is in builtins whenever IPython is running
264 self.inside_ipython = True
264 self.inside_ipython = True
265 except NameError:
265 except NameError:
266 self.inside_ipython = False
266 self.inside_ipython = False
267
267
268 if self.inside_ipython:
268 if self.inside_ipython:
269 # get a few things from ipython. While it's a bit ugly design-wise,
269 # get a few things from ipython. While it's a bit ugly design-wise,
270 # it ensures that things like color scheme and the like are always in
270 # it ensures that things like color scheme and the like are always in
271 # sync with the ipython mode being used. This class is only meant to
271 # sync with the ipython mode being used. This class is only meant to
272 # be used inside ipython anyways, so it's OK.
272 # be used inside ipython anyways, so it's OK.
273 self.ip_ns = ip.user_ns
273 self.ip_ns = ip.user_ns
274 self.ip_colorize = ip.pycolorize
274 self.ip_colorize = ip.pycolorize
275 self.ip_showtb = ip.showtraceback
275 self.ip_showtb = ip.showtraceback
276 self.ip_run_cell = ip.run_cell
276 self.ip_run_cell = ip.run_cell
277 self.shell = ip
277 self.shell = ip
278
278
279 self.formatter = pygments.formatters.get_formatter_by_name(formatter,
279 self.formatter = pygments.formatters.get_formatter_by_name(formatter,
280 style=style)
280 style=style)
281 self.python_lexer = pygments.lexers.get_lexer_by_name("py3")
281 self.python_lexer = pygments.lexers.get_lexer_by_name("py3")
282 self.format_rst = format_rst
282 self.format_rst = format_rst
283 if format_rst:
283 if format_rst:
284 self.rst_lexer = pygments.lexers.get_lexer_by_name("rst")
284 self.rst_lexer = pygments.lexers.get_lexer_by_name("rst")
285
285
286 # load user data and initialize data structures
286 # load user data and initialize data structures
287 self.reload()
287 self.reload()
288
288
289 def fload(self):
289 def fload(self):
290 """Load file object."""
290 """Load file object."""
291 # read data and parse into blocks
291 # read data and parse into blocks
292 if hasattr(self, 'fobj') and self.fobj is not None:
292 if hasattr(self, 'fobj') and self.fobj is not None:
293 self.fobj.close()
293 self.fobj.close()
294 if hasattr(self.src, "read"):
294 if hasattr(self.src, "read"):
295 # It seems to be a file or a file-like object
295 # It seems to be a file or a file-like object
296 self.fobj = self.src
296 self.fobj = self.src
297 else:
297 else:
298 # Assume it's a string or something that can be converted to one
298 # Assume it's a string or something that can be converted to one
299 self.fobj = openpy.open(self.fname)
299 self.fobj = openpy.open(self.fname)
300
300
301 def reload(self):
301 def reload(self):
302 """Reload source from disk and initialize state."""
302 """Reload source from disk and initialize state."""
303 self.fload()
303 self.fload()
304
304
305 self.src = "".join(openpy.strip_encoding_cookie(self.fobj))
305 self.src = "".join(openpy.strip_encoding_cookie(self.fobj))
306 src_b = [b.strip() for b in self.re_stop.split(self.src) if b]
306 src_b = [b.strip() for b in self.re_stop.split(self.src) if b]
307 self._silent = [bool(self.re_silent.findall(b)) for b in src_b]
307 self._silent = [bool(self.re_silent.findall(b)) for b in src_b]
308 self._auto = [bool(self.re_auto.findall(b)) for b in src_b]
308 self._auto = [bool(self.re_auto.findall(b)) for b in src_b]
309
309
310 # if auto_all is not given (def. None), we read it from the file
310 # if auto_all is not given (def. None), we read it from the file
311 if self.auto_all is None:
311 if self.auto_all is None:
312 self.auto_all = bool(self.re_auto_all.findall(src_b[0]))
312 self.auto_all = bool(self.re_auto_all.findall(src_b[0]))
313 else:
313 else:
314 self.auto_all = bool(self.auto_all)
314 self.auto_all = bool(self.auto_all)
315
315
316 # Clean the sources from all markup so it doesn't get displayed when
316 # Clean the sources from all markup so it doesn't get displayed when
317 # running the demo
317 # running the demo
318 src_blocks = []
318 src_blocks = []
319 auto_strip = lambda s: self.re_auto.sub('',s)
319 auto_strip = lambda s: self.re_auto.sub('',s)
320 for i,b in enumerate(src_b):
320 for i,b in enumerate(src_b):
321 if self._auto[i]:
321 if self._auto[i]:
322 src_blocks.append(auto_strip(b))
322 src_blocks.append(auto_strip(b))
323 else:
323 else:
324 src_blocks.append(b)
324 src_blocks.append(b)
325 # remove the auto_all marker
325 # remove the auto_all marker
326 src_blocks[0] = self.re_auto_all.sub('',src_blocks[0])
326 src_blocks[0] = self.re_auto_all.sub('',src_blocks[0])
327
327
328 self.nblocks = len(src_blocks)
328 self.nblocks = len(src_blocks)
329 self.src_blocks = src_blocks
329 self.src_blocks = src_blocks
330
330
331 # also build syntax-highlighted source
331 # also build syntax-highlighted source
332 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
332 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
333
333
334 # ensure clean namespace and seek offset
334 # ensure clean namespace and seek offset
335 self.reset()
335 self.reset()
336
336
337 def reset(self):
337 def reset(self):
338 """Reset the namespace and seek pointer to restart the demo"""
338 """Reset the namespace and seek pointer to restart the demo"""
339 self.user_ns = {}
339 self.user_ns = {}
340 self.finished = False
340 self.finished = False
341 self.block_index = 0
341 self.block_index = 0
342
342
343 def _validate_index(self,index):
343 def _validate_index(self,index):
344 if index<0 or index>=self.nblocks:
344 if index<0 or index>=self.nblocks:
345 raise ValueError('invalid block index %s' % index)
345 raise ValueError('invalid block index %s' % index)
346
346
347 def _get_index(self,index):
347 def _get_index(self,index):
348 """Get the current block index, validating and checking status.
348 """Get the current block index, validating and checking status.
349
349
350 Returns None if the demo is finished"""
350 Returns None if the demo is finished"""
351
351
352 if index is None:
352 if index is None:
353 if self.finished:
353 if self.finished:
354 print('Demo finished. Use <demo_name>.reset() if you want to rerun it.')
354 print('Demo finished. Use <demo_name>.reset() if you want to rerun it.')
355 return None
355 return None
356 index = self.block_index
356 index = self.block_index
357 else:
357 else:
358 self._validate_index(index)
358 self._validate_index(index)
359 return index
359 return index
360
360
361 def seek(self,index):
361 def seek(self,index):
362 """Move the current seek pointer to the given block.
362 """Move the current seek pointer to the given block.
363
363
364 You can use negative indices to seek from the end, with identical
364 You can use negative indices to seek from the end, with identical
365 semantics to those of Python lists."""
365 semantics to those of Python lists."""
366 if index<0:
366 if index<0:
367 index = self.nblocks + index
367 index = self.nblocks + index
368 self._validate_index(index)
368 self._validate_index(index)
369 self.block_index = index
369 self.block_index = index
370 self.finished = False
370 self.finished = False
371
371
372 def back(self,num=1):
372 def back(self,num=1):
373 """Move the seek pointer back num blocks (default is 1)."""
373 """Move the seek pointer back num blocks (default is 1)."""
374 self.seek(self.block_index-num)
374 self.seek(self.block_index-num)
375
375
376 def jump(self,num=1):
376 def jump(self,num=1):
377 """Jump a given number of blocks relative to the current one.
377 """Jump a given number of blocks relative to the current one.
378
378
379 The offset can be positive or negative, defaults to 1."""
379 The offset can be positive or negative, defaults to 1."""
380 self.seek(self.block_index+num)
380 self.seek(self.block_index+num)
381
381
382 def again(self):
382 def again(self):
383 """Move the seek pointer back one block and re-execute."""
383 """Move the seek pointer back one block and re-execute."""
384 self.back(1)
384 self.back(1)
385 self()
385 self()
386
386
387 def edit(self,index=None):
387 def edit(self,index=None):
388 """Edit a block.
388 """Edit a block.
389
389
390 If no number is given, use the last block executed.
390 If no number is given, use the last block executed.
391
391
392 This edits the in-memory copy of the demo, it does NOT modify the
392 This edits the in-memory copy of the demo, it does NOT modify the
393 original source file. If you want to do that, simply open the file in
393 original source file. If you want to do that, simply open the file in
394 an editor and use reload() when you make changes to the file. This
394 an editor and use reload() when you make changes to the file. This
395 method is meant to let you change a block during a demonstration for
395 method is meant to let you change a block during a demonstration for
396 explanatory purposes, without damaging your original script."""
396 explanatory purposes, without damaging your original script."""
397
397
398 index = self._get_index(index)
398 index = self._get_index(index)
399 if index is None:
399 if index is None:
400 return
400 return
401 # decrease the index by one (unless we're at the very beginning), so
401 # decrease the index by one (unless we're at the very beginning), so
402 # that the default demo.edit() call opens up the sblock we've last run
402 # that the default demo.edit() call opens up the sblock we've last run
403 if index>0:
403 if index>0:
404 index -= 1
404 index -= 1
405
405
406 filename = self.shell.mktempfile(self.src_blocks[index])
406 filename = self.shell.mktempfile(self.src_blocks[index])
407 self.shell.hooks.editor(filename, 1)
407 self.shell.hooks.editor(filename, 1)
408 with open(Path(filename), "r", encoding="utf-8") as f:
408 with open(Path(filename), "r", encoding="utf-8") as f:
409 new_block = f.read()
409 new_block = f.read()
410 # update the source and colored block
410 # update the source and colored block
411 self.src_blocks[index] = new_block
411 self.src_blocks[index] = new_block
412 self.src_blocks_colored[index] = self.highlight(new_block)
412 self.src_blocks_colored[index] = self.highlight(new_block)
413 self.block_index = index
413 self.block_index = index
414 # call to run with the newly edited index
414 # call to run with the newly edited index
415 self()
415 self()
416
416
417 def show(self,index=None):
417 def show(self,index=None):
418 """Show a single block on screen"""
418 """Show a single block on screen"""
419
419
420 index = self._get_index(index)
420 index = self._get_index(index)
421 if index is None:
421 if index is None:
422 return
422 return
423
423
424 print(self.marquee('<%s> block # %s (%s remaining)' %
424 print(self.marquee('<%s> block # %s (%s remaining)' %
425 (self.title,index,self.nblocks-index-1)))
425 (self.title,index,self.nblocks-index-1)))
426 print(self.src_blocks_colored[index])
426 print(self.src_blocks_colored[index])
427 sys.stdout.flush()
427 sys.stdout.flush()
428
428
429 def show_all(self):
429 def show_all(self):
430 """Show entire demo on screen, block by block"""
430 """Show entire demo on screen, block by block"""
431
431
432 fname = self.title
432 fname = self.title
433 title = self.title
433 title = self.title
434 nblocks = self.nblocks
434 nblocks = self.nblocks
435 silent = self._silent
435 silent = self._silent
436 marquee = self.marquee
436 marquee = self.marquee
437 for index,block in enumerate(self.src_blocks_colored):
437 for index,block in enumerate(self.src_blocks_colored):
438 if silent[index]:
438 if silent[index]:
439 print(marquee('<%s> SILENT block # %s (%s remaining)' %
439 print(marquee('<%s> SILENT block # %s (%s remaining)' %
440 (title,index,nblocks-index-1)))
440 (title,index,nblocks-index-1)))
441 else:
441 else:
442 print(marquee('<%s> block # %s (%s remaining)' %
442 print(marquee('<%s> block # %s (%s remaining)' %
443 (title,index,nblocks-index-1)))
443 (title,index,nblocks-index-1)))
444 print(block, end=' ')
444 print(block, end=' ')
445 sys.stdout.flush()
445 sys.stdout.flush()
446
446
447 def run_cell(self,source):
447 def run_cell(self,source):
448 """Execute a string with one or more lines of code"""
448 """Execute a string with one or more lines of code"""
449
449
450 exec(source, self.user_ns)
450 exec(source, self.user_ns)
451
451
452 def __call__(self,index=None):
452 def __call__(self,index=None):
453 """run a block of the demo.
453 """run a block of the demo.
454
454
455 If index is given, it should be an integer >=1 and <= nblocks. This
455 If index is given, it should be an integer >=1 and <= nblocks. This
456 means that the calling convention is one off from typical Python
456 means that the calling convention is one off from typical Python
457 lists. The reason for the inconsistency is that the demo always
457 lists. The reason for the inconsistency is that the demo always
458 prints 'Block n/N, and N is the total, so it would be very odd to use
458 prints 'Block n/N, and N is the total, so it would be very odd to use
459 zero-indexing here."""
459 zero-indexing here."""
460
460
461 index = self._get_index(index)
461 index = self._get_index(index)
462 if index is None:
462 if index is None:
463 return
463 return
464 try:
464 try:
465 marquee = self.marquee
465 marquee = self.marquee
466 next_block = self.src_blocks[index]
466 next_block = self.src_blocks[index]
467 self.block_index += 1
467 self.block_index += 1
468 if self._silent[index]:
468 if self._silent[index]:
469 print(marquee('Executing silent block # %s (%s remaining)' %
469 print(marquee('Executing silent block # %s (%s remaining)' %
470 (index,self.nblocks-index-1)))
470 (index,self.nblocks-index-1)))
471 else:
471 else:
472 self.pre_cmd()
472 self.pre_cmd()
473 self.show(index)
473 self.show(index)
474 if self.auto_all or self._auto[index]:
474 if self.auto_all or self._auto[index]:
475 print(marquee('output:'))
475 print(marquee('output:'))
476 else:
476 else:
477 print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ')
477 print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ')
478 ans = py3compat.input().strip()
478 ans = py3compat.input().strip()
479 if ans:
479 if ans:
480 print(marquee('Block NOT executed'))
480 print(marquee('Block NOT executed'))
481 return
481 return
482 try:
482 try:
483 save_argv = sys.argv
483 save_argv = sys.argv
484 sys.argv = self.sys_argv
484 sys.argv = self.sys_argv
485 self.run_cell(next_block)
485 self.run_cell(next_block)
486 self.post_cmd()
486 self.post_cmd()
487 finally:
487 finally:
488 sys.argv = save_argv
488 sys.argv = save_argv
489
489
490 except:
490 except:
491 if self.inside_ipython:
491 if self.inside_ipython:
492 self.ip_showtb(filename=self.fname)
492 self.ip_showtb(filename=self.fname)
493 else:
493 else:
494 if self.inside_ipython:
494 if self.inside_ipython:
495 self.ip_ns.update(self.user_ns)
495 self.ip_ns.update(self.user_ns)
496
496
497 if self.block_index == self.nblocks:
497 if self.block_index == self.nblocks:
498 mq1 = self.marquee('END OF DEMO')
498 mq1 = self.marquee('END OF DEMO')
499 if mq1:
499 if mq1:
500 # avoid spurious print if empty marquees are used
500 # avoid spurious print if empty marquees are used
501 print()
501 print()
502 print(mq1)
502 print(mq1)
503 print(self.marquee('Use <demo_name>.reset() if you want to rerun it.'))
503 print(self.marquee('Use <demo_name>.reset() if you want to rerun it.'))
504 self.finished = True
504 self.finished = True
505
505
506 # These methods are meant to be overridden by subclasses who may wish to
506 # These methods are meant to be overridden by subclasses who may wish to
507 # customize the behavior of of their demos.
507 # customize the behavior of of their demos.
508 def marquee(self,txt='',width=78,mark='*'):
508 def marquee(self,txt='',width=78,mark='*'):
509 """Return the input string centered in a 'marquee'."""
509 """Return the input string centered in a 'marquee'."""
510 return marquee(txt,width,mark)
510 return marquee(txt,width,mark)
511
511
512 def pre_cmd(self):
512 def pre_cmd(self):
513 """Method called before executing each block."""
513 """Method called before executing each block."""
514 pass
514 pass
515
515
516 def post_cmd(self):
516 def post_cmd(self):
517 """Method called after executing each block."""
517 """Method called after executing each block."""
518 pass
518 pass
519
519
520 def highlight(self, block):
520 def highlight(self, block):
521 """Method called on each block to highlight it content"""
521 """Method called on each block to highlight it content"""
522 tokens = pygments.lex(block, self.python_lexer)
522 tokens = pygments.lex(block, self.python_lexer)
523 if self.format_rst:
523 if self.format_rst:
524 from pygments.token import Token
524 from pygments.token import Token
525 toks = []
525 toks = []
526 for token in tokens:
526 for token in tokens:
527 if token[0] == Token.String.Doc and len(token[1]) > 6:
527 if token[0] == Token.String.Doc and len(token[1]) > 6:
528 toks += pygments.lex(token[1][:3], self.python_lexer)
528 toks += pygments.lex(token[1][:3], self.python_lexer)
529 # parse doc string content by rst lexer
529 # parse doc string content by rst lexer
530 toks += pygments.lex(token[1][3:-3], self.rst_lexer)
530 toks += pygments.lex(token[1][3:-3], self.rst_lexer)
531 toks += pygments.lex(token[1][-3:], self.python_lexer)
531 toks += pygments.lex(token[1][-3:], self.python_lexer)
532 elif token[0] == Token.Comment.Single:
532 elif token[0] == Token.Comment.Single:
533 toks.append((Token.Comment.Single, token[1][0]))
533 toks.append((Token.Comment.Single, token[1][0]))
534 # parse comment content by rst lexer
534 # parse comment content by rst lexer
535 # remove the extra newline added by rst lexer
535 # remove the extra newline added by rst lexer
536 toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1]
536 toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1]
537 else:
537 else:
538 toks.append(token)
538 toks.append(token)
539 tokens = toks
539 tokens = toks
540 return pygments.format(tokens, self.formatter)
540 return pygments.format(tokens, self.formatter)
541
541
542
542
543 class IPythonDemo(Demo):
543 class IPythonDemo(Demo):
544 """Class for interactive demos with IPython's input processing applied.
544 """Class for interactive demos with IPython's input processing applied.
545
545
546 This subclasses Demo, but instead of executing each block by the Python
546 This subclasses Demo, but instead of executing each block by the Python
547 interpreter (via exec), it actually calls IPython on it, so that any input
547 interpreter (via exec), it actually calls IPython on it, so that any input
548 filters which may be in place are applied to the input block.
548 filters which may be in place are applied to the input block.
549
549
550 If you have an interactive environment which exposes special input
550 If you have an interactive environment which exposes special input
551 processing, you can use this class instead to write demo scripts which
551 processing, you can use this class instead to write demo scripts which
552 operate exactly as if you had typed them interactively. The default Demo
552 operate exactly as if you had typed them interactively. The default Demo
553 class requires the input to be valid, pure Python code.
553 class requires the input to be valid, pure Python code.
554 """
554 """
555
555
556 def run_cell(self,source):
556 def run_cell(self,source):
557 """Execute a string with one or more lines of code"""
557 """Execute a string with one or more lines of code"""
558
558
559 self.shell.run_cell(source)
559 self.shell.run_cell(source)
560
560
561 class LineDemo(Demo):
561 class LineDemo(Demo):
562 """Demo where each line is executed as a separate block.
562 """Demo where each line is executed as a separate block.
563
563
564 The input script should be valid Python code.
564 The input script should be valid Python code.
565
565
566 This class doesn't require any markup at all, and it's meant for simple
566 This class doesn't require any markup at all, and it's meant for simple
567 scripts (with no nesting or any kind of indentation) which consist of
567 scripts (with no nesting or any kind of indentation) which consist of
568 multiple lines of input to be executed, one at a time, as if they had been
568 multiple lines of input to be executed, one at a time, as if they had been
569 typed in the interactive prompt.
569 typed in the interactive prompt.
570
570
571 Note: the input can not have *any* indentation, which means that only
571 Note: the input can not have *any* indentation, which means that only
572 single-lines of input are accepted, not even function definitions are
572 single-lines of input are accepted, not even function definitions are
573 valid."""
573 valid."""
574
574
575 def reload(self):
575 def reload(self):
576 """Reload source from disk and initialize state."""
576 """Reload source from disk and initialize state."""
577 # read data and parse into blocks
577 # read data and parse into blocks
578 self.fload()
578 self.fload()
579 lines = self.fobj.readlines()
579 lines = self.fobj.readlines()
580 src_b = [l for l in lines if l.strip()]
580 src_b = [l for l in lines if l.strip()]
581 nblocks = len(src_b)
581 nblocks = len(src_b)
582 self.src = ''.join(lines)
582 self.src = ''.join(lines)
583 self._silent = [False]*nblocks
583 self._silent = [False]*nblocks
584 self._auto = [True]*nblocks
584 self._auto = [True]*nblocks
585 self.auto_all = True
585 self.auto_all = True
586 self.nblocks = nblocks
586 self.nblocks = nblocks
587 self.src_blocks = src_b
587 self.src_blocks = src_b
588
588
589 # also build syntax-highlighted source
589 # also build syntax-highlighted source
590 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
590 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
591
591
592 # ensure clean namespace and seek offset
592 # ensure clean namespace and seek offset
593 self.reset()
593 self.reset()
594
594
595
595
596 class IPythonLineDemo(IPythonDemo,LineDemo):
596 class IPythonLineDemo(IPythonDemo,LineDemo):
597 """Variant of the LineDemo class whose input is processed by IPython."""
597 """Variant of the LineDemo class whose input is processed by IPython."""
598 pass
598 pass
599
599
600
600
601 class ClearMixin(object):
601 class ClearMixin(object):
602 """Use this mixin to make Demo classes with less visual clutter.
602 """Use this mixin to make Demo classes with less visual clutter.
603
603
604 Demos using this mixin will clear the screen before every block and use
604 Demos using this mixin will clear the screen before every block and use
605 blank marquees.
605 blank marquees.
606
606
607 Note that in order for the methods defined here to actually override those
607 Note that in order for the methods defined here to actually override those
608 of the classes it's mixed with, it must go /first/ in the inheritance
608 of the classes it's mixed with, it must go /first/ in the inheritance
609 tree. For example:
609 tree. For example:
610
610
611 class ClearIPDemo(ClearMixin,IPythonDemo): pass
611 class ClearIPDemo(ClearMixin,IPythonDemo): pass
612
612
613 will provide an IPythonDemo class with the mixin's features.
613 will provide an IPythonDemo class with the mixin's features.
614 """
614 """
615
615
616 def marquee(self,txt='',width=78,mark='*'):
616 def marquee(self,txt='',width=78,mark='*'):
617 """Blank marquee that returns '' no matter what the input."""
617 """Blank marquee that returns '' no matter what the input."""
618 return ''
618 return ''
619
619
620 def pre_cmd(self):
620 def pre_cmd(self):
621 """Method called before executing each block.
621 """Method called before executing each block.
622
622
623 This one simply clears the screen."""
623 This one simply clears the screen."""
624 from IPython.utils.terminal import _term_clear
624 from IPython.utils.terminal import _term_clear
625 _term_clear()
625 _term_clear()
626
626
627 class ClearDemo(ClearMixin,Demo):
627 class ClearDemo(ClearMixin,Demo):
628 pass
628 pass
629
629
630
630
631 class ClearIPDemo(ClearMixin,IPythonDemo):
631 class ClearIPDemo(ClearMixin,IPythonDemo):
632 pass
632 pass
633
633
634
634
635 def slide(file_path, noclear=False, format_rst=True, formatter="terminal",
635 def slide(file_path, noclear=False, format_rst=True, formatter="terminal",
636 style="native", auto_all=False, delimiter='...'):
636 style="native", auto_all=False, delimiter='...'):
637 if noclear:
637 if noclear:
638 demo_class = Demo
638 demo_class = Demo
639 else:
639 else:
640 demo_class = ClearDemo
640 demo_class = ClearDemo
641 demo = demo_class(file_path, format_rst=format_rst, formatter=formatter,
641 demo = demo_class(file_path, format_rst=format_rst, formatter=formatter,
642 style=style, auto_all=auto_all)
642 style=style, auto_all=auto_all)
643 while not demo.finished:
643 while not demo.finished:
644 demo()
644 demo()
645 try:
645 try:
646 py3compat.input('\n' + delimiter)
646 py3compat.input('\n' + delimiter)
647 except KeyboardInterrupt:
647 except KeyboardInterrupt:
648 exit(1)
648 exit(1)
649
649
650 if __name__ == '__main__':
650 if __name__ == '__main__':
651 import argparse
651 import argparse
652 parser = argparse.ArgumentParser(description='Run python demos')
652 parser = argparse.ArgumentParser(description='Run python demos')
653 parser.add_argument('--noclear', '-C', action='store_true',
653 parser.add_argument('--noclear', '-C', action='store_true',
654 help='Do not clear terminal on each slide')
654 help='Do not clear terminal on each slide')
655 parser.add_argument('--rst', '-r', action='store_true',
655 parser.add_argument('--rst', '-r', action='store_true',
656 help='Highlight comments and dostrings as rst')
656 help='Highlight comments and dostrings as rst')
657 parser.add_argument('--formatter', '-f', default='terminal',
657 parser.add_argument('--formatter', '-f', default='terminal',
658 help='pygments formatter name could be: terminal, '
658 help='pygments formatter name could be: terminal, '
659 'terminal256, terminal16m')
659 'terminal256, terminal16m')
660 parser.add_argument('--style', '-s', default='default',
660 parser.add_argument('--style', '-s', default='default',
661 help='pygments style name')
661 help='pygments style name')
662 parser.add_argument('--auto', '-a', action='store_true',
662 parser.add_argument('--auto', '-a', action='store_true',
663 help='Run all blocks automatically without'
663 help='Run all blocks automatically without'
664 'confirmation')
664 'confirmation')
665 parser.add_argument('--delimiter', '-d', default='...',
665 parser.add_argument('--delimiter', '-d', default='...',
666 help='slides delimiter added after each slide run')
666 help='slides delimiter added after each slide run')
667 parser.add_argument('file', nargs=1,
667 parser.add_argument('file', nargs=1,
668 help='python demo file')
668 help='python demo file')
669 args = parser.parse_args()
669 args = parser.parse_args()
670 slide(args.file[0], noclear=args.noclear, format_rst=args.rst,
670 slide(args.file[0], noclear=args.noclear, format_rst=args.rst,
671 formatter=args.formatter, style=args.style, auto_all=args.auto,
671 formatter=args.formatter, style=args.style, auto_all=args.auto,
672 delimiter=args.delimiter)
672 delimiter=args.delimiter)
General Comments 0
You need to be logged in to leave comments. Login now