##// END OF EJS Templates
use pathlib.Path in demo.py
Samreen Zarroug -
Show More
@@ -1,671 +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
188
188 from IPython.utils.text import marquee
189 from IPython.utils.text import marquee
189 from IPython.utils import openpy
190 from IPython.utils import openpy
190 from IPython.utils import py3compat
191 from IPython.utils import py3compat
191 __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError']
192 __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError']
192
193
193 class DemoError(Exception): pass
194 class DemoError(Exception): pass
194
195
195 def re_mark(mark):
196 def re_mark(mark):
196 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)
197
198
198 class Demo(object):
199 class Demo(object):
199
200
200 re_stop = re_mark(r'-*\s?stop\s?-*')
201 re_stop = re_mark(r'-*\s?stop\s?-*')
201 re_silent = re_mark('silent')
202 re_silent = re_mark('silent')
202 re_auto = re_mark('auto')
203 re_auto = re_mark('auto')
203 re_auto_all = re_mark('auto_all')
204 re_auto_all = re_mark('auto_all')
204
205
205 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,
206 formatter='terminal', style='default'):
207 formatter='terminal', style='default'):
207 """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.
208
209
209 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
210 IPython.Demo? in IPython to see it).
211 IPython.Demo? in IPython to see it).
211
212
212 Inputs:
213 Inputs:
213
214
214 - src is either a file, or file-like object, or a
215 - src is either a file, or file-like object, or a
215 string that can be resolved to a filename.
216 string that can be resolved to a filename.
216
217
217 Optional inputs:
218 Optional inputs:
218
219
219 - 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
220 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
221 want an alternate denotation distinct from the filename.
222 want an alternate denotation distinct from the filename.
222
223
223 - arg_str(''): a string of arguments, internally converted to a list
224 - arg_str(''): a string of arguments, internally converted to a list
224 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
225 environment.
226 environment.
226
227
227 - auto_all(None): global flag to run all blocks automatically without
228 - auto_all(None): global flag to run all blocks automatically without
228 confirmation. This attribute overrides the block-level tags and
229 confirmation. This attribute overrides the block-level tags and
229 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
230 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
231 value.
232 value.
232
233
233 - format_rst(False): a bool to enable comments and doc strings
234 - format_rst(False): a bool to enable comments and doc strings
234 formatting with pygments rst lexer
235 formatting with pygments rst lexer
235
236
236 - formatter('terminal'): a string of pygments formatter name to be
237 - formatter('terminal'): a string of pygments formatter name to be
237 used. Useful values for terminals: terminal, terminal256,
238 used. Useful values for terminals: terminal, terminal256,
238 terminal16m
239 terminal16m
239
240
240 - style('default'): a string of pygments style name to be used.
241 - style('default'): a string of pygments style name to be used.
241 """
242 """
242 if hasattr(src, "read"):
243 if hasattr(src, "read"):
243 # It seems to be a file or a file-like object
244 # It seems to be a file or a file-like object
244 self.fname = "from a file-like object"
245 self.fname = "from a file-like object"
245 if title == '':
246 if title == '':
246 self.title = "from a file-like object"
247 self.title = "from a file-like object"
247 else:
248 else:
248 self.title = title
249 self.title = title
249 else:
250 else:
250 # 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
251 self.fname = src
252 self.fname = src
252 if title == '':
253 if title == '':
253 (filepath, filename) = os.path.split(src)
254 (filepath, filename) = os.path.split(src)
254 self.title = filename
255 self.title = filename
255 else:
256 else:
256 self.title = title
257 self.title = title
257 self.sys_argv = [src] + shlex.split(arg_str)
258 self.sys_argv = [src] + shlex.split(arg_str)
258 self.auto_all = auto_all
259 self.auto_all = auto_all
259 self.src = src
260 self.src = src
260
261
261 try:
262 try:
262 ip = get_ipython() # this is in builtins whenever IPython is running
263 ip = get_ipython() # this is in builtins whenever IPython is running
263 self.inside_ipython = True
264 self.inside_ipython = True
264 except NameError:
265 except NameError:
265 self.inside_ipython = False
266 self.inside_ipython = False
266
267
267 if self.inside_ipython:
268 if self.inside_ipython:
268 # 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,
269 # 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
270 # 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
271 # be used inside ipython anyways, so it's OK.
272 # be used inside ipython anyways, so it's OK.
272 self.ip_ns = ip.user_ns
273 self.ip_ns = ip.user_ns
273 self.ip_colorize = ip.pycolorize
274 self.ip_colorize = ip.pycolorize
274 self.ip_showtb = ip.showtraceback
275 self.ip_showtb = ip.showtraceback
275 self.ip_run_cell = ip.run_cell
276 self.ip_run_cell = ip.run_cell
276 self.shell = ip
277 self.shell = ip
277
278
278 self.formatter = pygments.formatters.get_formatter_by_name(formatter,
279 self.formatter = pygments.formatters.get_formatter_by_name(formatter,
279 style=style)
280 style=style)
280 self.python_lexer = pygments.lexers.get_lexer_by_name("py3")
281 self.python_lexer = pygments.lexers.get_lexer_by_name("py3")
281 self.format_rst = format_rst
282 self.format_rst = format_rst
282 if format_rst:
283 if format_rst:
283 self.rst_lexer = pygments.lexers.get_lexer_by_name("rst")
284 self.rst_lexer = pygments.lexers.get_lexer_by_name("rst")
284
285
285 # load user data and initialize data structures
286 # load user data and initialize data structures
286 self.reload()
287 self.reload()
287
288
288 def fload(self):
289 def fload(self):
289 """Load file object."""
290 """Load file object."""
290 # read data and parse into blocks
291 # read data and parse into blocks
291 if hasattr(self, 'fobj') and self.fobj is not None:
292 if hasattr(self, 'fobj') and self.fobj is not None:
292 self.fobj.close()
293 self.fobj.close()
293 if hasattr(self.src, "read"):
294 if hasattr(self.src, "read"):
294 # It seems to be a file or a file-like object
295 # It seems to be a file or a file-like object
295 self.fobj = self.src
296 self.fobj = self.src
296 else:
297 else:
297 # 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
298 self.fobj = openpy.open(self.fname)
299 self.fobj = openpy.open(self.fname)
299
300
300 def reload(self):
301 def reload(self):
301 """Reload source from disk and initialize state."""
302 """Reload source from disk and initialize state."""
302 self.fload()
303 self.fload()
303
304
304 self.src = "".join(openpy.strip_encoding_cookie(self.fobj))
305 self.src = "".join(openpy.strip_encoding_cookie(self.fobj))
305 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]
306 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]
307 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]
308
309
309 # 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
310 if self.auto_all is None:
311 if self.auto_all is None:
311 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]))
312 else:
313 else:
313 self.auto_all = bool(self.auto_all)
314 self.auto_all = bool(self.auto_all)
314
315
315 # 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
316 # running the demo
317 # running the demo
317 src_blocks = []
318 src_blocks = []
318 auto_strip = lambda s: self.re_auto.sub('',s)
319 auto_strip = lambda s: self.re_auto.sub('',s)
319 for i,b in enumerate(src_b):
320 for i,b in enumerate(src_b):
320 if self._auto[i]:
321 if self._auto[i]:
321 src_blocks.append(auto_strip(b))
322 src_blocks.append(auto_strip(b))
322 else:
323 else:
323 src_blocks.append(b)
324 src_blocks.append(b)
324 # remove the auto_all marker
325 # remove the auto_all marker
325 src_blocks[0] = self.re_auto_all.sub('',src_blocks[0])
326 src_blocks[0] = self.re_auto_all.sub('',src_blocks[0])
326
327
327 self.nblocks = len(src_blocks)
328 self.nblocks = len(src_blocks)
328 self.src_blocks = src_blocks
329 self.src_blocks = src_blocks
329
330
330 # also build syntax-highlighted source
331 # also build syntax-highlighted source
331 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
332 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
332
333
333 # ensure clean namespace and seek offset
334 # ensure clean namespace and seek offset
334 self.reset()
335 self.reset()
335
336
336 def reset(self):
337 def reset(self):
337 """Reset the namespace and seek pointer to restart the demo"""
338 """Reset the namespace and seek pointer to restart the demo"""
338 self.user_ns = {}
339 self.user_ns = {}
339 self.finished = False
340 self.finished = False
340 self.block_index = 0
341 self.block_index = 0
341
342
342 def _validate_index(self,index):
343 def _validate_index(self,index):
343 if index<0 or index>=self.nblocks:
344 if index<0 or index>=self.nblocks:
344 raise ValueError('invalid block index %s' % index)
345 raise ValueError('invalid block index %s' % index)
345
346
346 def _get_index(self,index):
347 def _get_index(self,index):
347 """Get the current block index, validating and checking status.
348 """Get the current block index, validating and checking status.
348
349
349 Returns None if the demo is finished"""
350 Returns None if the demo is finished"""
350
351
351 if index is None:
352 if index is None:
352 if self.finished:
353 if self.finished:
353 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.')
354 return None
355 return None
355 index = self.block_index
356 index = self.block_index
356 else:
357 else:
357 self._validate_index(index)
358 self._validate_index(index)
358 return index
359 return index
359
360
360 def seek(self,index):
361 def seek(self,index):
361 """Move the current seek pointer to the given block.
362 """Move the current seek pointer to the given block.
362
363
363 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
364 semantics to those of Python lists."""
365 semantics to those of Python lists."""
365 if index<0:
366 if index<0:
366 index = self.nblocks + index
367 index = self.nblocks + index
367 self._validate_index(index)
368 self._validate_index(index)
368 self.block_index = index
369 self.block_index = index
369 self.finished = False
370 self.finished = False
370
371
371 def back(self,num=1):
372 def back(self,num=1):
372 """Move the seek pointer back num blocks (default is 1)."""
373 """Move the seek pointer back num blocks (default is 1)."""
373 self.seek(self.block_index-num)
374 self.seek(self.block_index-num)
374
375
375 def jump(self,num=1):
376 def jump(self,num=1):
376 """Jump a given number of blocks relative to the current one.
377 """Jump a given number of blocks relative to the current one.
377
378
378 The offset can be positive or negative, defaults to 1."""
379 The offset can be positive or negative, defaults to 1."""
379 self.seek(self.block_index+num)
380 self.seek(self.block_index+num)
380
381
381 def again(self):
382 def again(self):
382 """Move the seek pointer back one block and re-execute."""
383 """Move the seek pointer back one block and re-execute."""
383 self.back(1)
384 self.back(1)
384 self()
385 self()
385
386
386 def edit(self,index=None):
387 def edit(self,index=None):
387 """Edit a block.
388 """Edit a block.
388
389
389 If no number is given, use the last block executed.
390 If no number is given, use the last block executed.
390
391
391 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
392 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
393 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
394 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
395 explanatory purposes, without damaging your original script."""
396 explanatory purposes, without damaging your original script."""
396
397
397 index = self._get_index(index)
398 index = self._get_index(index)
398 if index is None:
399 if index is None:
399 return
400 return
400 # 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
401 # 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
402 if index>0:
403 if index>0:
403 index -= 1
404 index -= 1
404
405
405 filename = self.shell.mktempfile(self.src_blocks[index])
406 filename = self.shell.mktempfile(self.src_blocks[index])
406 self.shell.hooks.editor(filename,1)
407 self.shell.hooks.editor(filename, 1)
407 with open(filename, 'r') as f:
408 with open(Path(filename), "r") as f:
408 new_block = f.read()
409 new_block = f.read()
409 # update the source and colored block
410 # update the source and colored block
410 self.src_blocks[index] = new_block
411 self.src_blocks[index] = new_block
411 self.src_blocks_colored[index] = self.highlight(new_block)
412 self.src_blocks_colored[index] = self.highlight(new_block)
412 self.block_index = index
413 self.block_index = index
413 # call to run with the newly edited index
414 # call to run with the newly edited index
414 self()
415 self()
415
416
416 def show(self,index=None):
417 def show(self,index=None):
417 """Show a single block on screen"""
418 """Show a single block on screen"""
418
419
419 index = self._get_index(index)
420 index = self._get_index(index)
420 if index is None:
421 if index is None:
421 return
422 return
422
423
423 print(self.marquee('<%s> block # %s (%s remaining)' %
424 print(self.marquee('<%s> block # %s (%s remaining)' %
424 (self.title,index,self.nblocks-index-1)))
425 (self.title,index,self.nblocks-index-1)))
425 print(self.src_blocks_colored[index])
426 print(self.src_blocks_colored[index])
426 sys.stdout.flush()
427 sys.stdout.flush()
427
428
428 def show_all(self):
429 def show_all(self):
429 """Show entire demo on screen, block by block"""
430 """Show entire demo on screen, block by block"""
430
431
431 fname = self.title
432 fname = self.title
432 title = self.title
433 title = self.title
433 nblocks = self.nblocks
434 nblocks = self.nblocks
434 silent = self._silent
435 silent = self._silent
435 marquee = self.marquee
436 marquee = self.marquee
436 for index,block in enumerate(self.src_blocks_colored):
437 for index,block in enumerate(self.src_blocks_colored):
437 if silent[index]:
438 if silent[index]:
438 print(marquee('<%s> SILENT block # %s (%s remaining)' %
439 print(marquee('<%s> SILENT block # %s (%s remaining)' %
439 (title,index,nblocks-index-1)))
440 (title,index,nblocks-index-1)))
440 else:
441 else:
441 print(marquee('<%s> block # %s (%s remaining)' %
442 print(marquee('<%s> block # %s (%s remaining)' %
442 (title,index,nblocks-index-1)))
443 (title,index,nblocks-index-1)))
443 print(block, end=' ')
444 print(block, end=' ')
444 sys.stdout.flush()
445 sys.stdout.flush()
445
446
446 def run_cell(self,source):
447 def run_cell(self,source):
447 """Execute a string with one or more lines of code"""
448 """Execute a string with one or more lines of code"""
448
449
449 exec(source, self.user_ns)
450 exec(source, self.user_ns)
450
451
451 def __call__(self,index=None):
452 def __call__(self,index=None):
452 """run a block of the demo.
453 """run a block of the demo.
453
454
454 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
455 means that the calling convention is one off from typical Python
456 means that the calling convention is one off from typical Python
456 lists. The reason for the inconsistency is that the demo always
457 lists. The reason for the inconsistency is that the demo always
457 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
458 zero-indexing here."""
459 zero-indexing here."""
459
460
460 index = self._get_index(index)
461 index = self._get_index(index)
461 if index is None:
462 if index is None:
462 return
463 return
463 try:
464 try:
464 marquee = self.marquee
465 marquee = self.marquee
465 next_block = self.src_blocks[index]
466 next_block = self.src_blocks[index]
466 self.block_index += 1
467 self.block_index += 1
467 if self._silent[index]:
468 if self._silent[index]:
468 print(marquee('Executing silent block # %s (%s remaining)' %
469 print(marquee('Executing silent block # %s (%s remaining)' %
469 (index,self.nblocks-index-1)))
470 (index,self.nblocks-index-1)))
470 else:
471 else:
471 self.pre_cmd()
472 self.pre_cmd()
472 self.show(index)
473 self.show(index)
473 if self.auto_all or self._auto[index]:
474 if self.auto_all or self._auto[index]:
474 print(marquee('output:'))
475 print(marquee('output:'))
475 else:
476 else:
476 print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ')
477 print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ')
477 ans = py3compat.input().strip()
478 ans = py3compat.input().strip()
478 if ans:
479 if ans:
479 print(marquee('Block NOT executed'))
480 print(marquee('Block NOT executed'))
480 return
481 return
481 try:
482 try:
482 save_argv = sys.argv
483 save_argv = sys.argv
483 sys.argv = self.sys_argv
484 sys.argv = self.sys_argv
484 self.run_cell(next_block)
485 self.run_cell(next_block)
485 self.post_cmd()
486 self.post_cmd()
486 finally:
487 finally:
487 sys.argv = save_argv
488 sys.argv = save_argv
488
489
489 except:
490 except:
490 if self.inside_ipython:
491 if self.inside_ipython:
491 self.ip_showtb(filename=self.fname)
492 self.ip_showtb(filename=self.fname)
492 else:
493 else:
493 if self.inside_ipython:
494 if self.inside_ipython:
494 self.ip_ns.update(self.user_ns)
495 self.ip_ns.update(self.user_ns)
495
496
496 if self.block_index == self.nblocks:
497 if self.block_index == self.nblocks:
497 mq1 = self.marquee('END OF DEMO')
498 mq1 = self.marquee('END OF DEMO')
498 if mq1:
499 if mq1:
499 # avoid spurious print if empty marquees are used
500 # avoid spurious print if empty marquees are used
500 print()
501 print()
501 print(mq1)
502 print(mq1)
502 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.'))
503 self.finished = True
504 self.finished = True
504
505
505 # 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
506 # customize the behavior of of their demos.
507 # customize the behavior of of their demos.
507 def marquee(self,txt='',width=78,mark='*'):
508 def marquee(self,txt='',width=78,mark='*'):
508 """Return the input string centered in a 'marquee'."""
509 """Return the input string centered in a 'marquee'."""
509 return marquee(txt,width,mark)
510 return marquee(txt,width,mark)
510
511
511 def pre_cmd(self):
512 def pre_cmd(self):
512 """Method called before executing each block."""
513 """Method called before executing each block."""
513 pass
514 pass
514
515
515 def post_cmd(self):
516 def post_cmd(self):
516 """Method called after executing each block."""
517 """Method called after executing each block."""
517 pass
518 pass
518
519
519 def highlight(self, block):
520 def highlight(self, block):
520 """Method called on each block to highlight it content"""
521 """Method called on each block to highlight it content"""
521 tokens = pygments.lex(block, self.python_lexer)
522 tokens = pygments.lex(block, self.python_lexer)
522 if self.format_rst:
523 if self.format_rst:
523 from pygments.token import Token
524 from pygments.token import Token
524 toks = []
525 toks = []
525 for token in tokens:
526 for token in tokens:
526 if token[0] == Token.String.Doc and len(token[1]) > 6:
527 if token[0] == Token.String.Doc and len(token[1]) > 6:
527 toks += pygments.lex(token[1][:3], self.python_lexer)
528 toks += pygments.lex(token[1][:3], self.python_lexer)
528 # parse doc string content by rst lexer
529 # parse doc string content by rst lexer
529 toks += pygments.lex(token[1][3:-3], self.rst_lexer)
530 toks += pygments.lex(token[1][3:-3], self.rst_lexer)
530 toks += pygments.lex(token[1][-3:], self.python_lexer)
531 toks += pygments.lex(token[1][-3:], self.python_lexer)
531 elif token[0] == Token.Comment.Single:
532 elif token[0] == Token.Comment.Single:
532 toks.append((Token.Comment.Single, token[1][0]))
533 toks.append((Token.Comment.Single, token[1][0]))
533 # parse comment content by rst lexer
534 # parse comment content by rst lexer
534 # remove the extrat newline added by rst lexer
535 # remove the extrat newline added by rst lexer
535 toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1]
536 toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1]
536 else:
537 else:
537 toks.append(token)
538 toks.append(token)
538 tokens = toks
539 tokens = toks
539 return pygments.format(tokens, self.formatter)
540 return pygments.format(tokens, self.formatter)
540
541
541
542
542 class IPythonDemo(Demo):
543 class IPythonDemo(Demo):
543 """Class for interactive demos with IPython's input processing applied.
544 """Class for interactive demos with IPython's input processing applied.
544
545
545 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
546 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
547 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.
548
549
549 If you have an interactive environment which exposes special input
550 If you have an interactive environment which exposes special input
550 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
551 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
552 class requires the input to be valid, pure Python code.
553 class requires the input to be valid, pure Python code.
553 """
554 """
554
555
555 def run_cell(self,source):
556 def run_cell(self,source):
556 """Execute a string with one or more lines of code"""
557 """Execute a string with one or more lines of code"""
557
558
558 self.shell.run_cell(source)
559 self.shell.run_cell(source)
559
560
560 class LineDemo(Demo):
561 class LineDemo(Demo):
561 """Demo where each line is executed as a separate block.
562 """Demo where each line is executed as a separate block.
562
563
563 The input script should be valid Python code.
564 The input script should be valid Python code.
564
565
565 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
566 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
567 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
568 typed in the interactive prompt.
569 typed in the interactive prompt.
569
570
570 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
571 single-lines of input are accepted, not even function definitions are
572 single-lines of input are accepted, not even function definitions are
572 valid."""
573 valid."""
573
574
574 def reload(self):
575 def reload(self):
575 """Reload source from disk and initialize state."""
576 """Reload source from disk and initialize state."""
576 # read data and parse into blocks
577 # read data and parse into blocks
577 self.fload()
578 self.fload()
578 lines = self.fobj.readlines()
579 lines = self.fobj.readlines()
579 src_b = [l for l in lines if l.strip()]
580 src_b = [l for l in lines if l.strip()]
580 nblocks = len(src_b)
581 nblocks = len(src_b)
581 self.src = ''.join(lines)
582 self.src = ''.join(lines)
582 self._silent = [False]*nblocks
583 self._silent = [False]*nblocks
583 self._auto = [True]*nblocks
584 self._auto = [True]*nblocks
584 self.auto_all = True
585 self.auto_all = True
585 self.nblocks = nblocks
586 self.nblocks = nblocks
586 self.src_blocks = src_b
587 self.src_blocks = src_b
587
588
588 # also build syntax-highlighted source
589 # also build syntax-highlighted source
589 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
590 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
590
591
591 # ensure clean namespace and seek offset
592 # ensure clean namespace and seek offset
592 self.reset()
593 self.reset()
593
594
594
595
595 class IPythonLineDemo(IPythonDemo,LineDemo):
596 class IPythonLineDemo(IPythonDemo,LineDemo):
596 """Variant of the LineDemo class whose input is processed by IPython."""
597 """Variant of the LineDemo class whose input is processed by IPython."""
597 pass
598 pass
598
599
599
600
600 class ClearMixin(object):
601 class ClearMixin(object):
601 """Use this mixin to make Demo classes with less visual clutter.
602 """Use this mixin to make Demo classes with less visual clutter.
602
603
603 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
604 blank marquees.
605 blank marquees.
605
606
606 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
607 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
608 tree. For example:
609 tree. For example:
609
610
610 class ClearIPDemo(ClearMixin,IPythonDemo): pass
611 class ClearIPDemo(ClearMixin,IPythonDemo): pass
611
612
612 will provide an IPythonDemo class with the mixin's features.
613 will provide an IPythonDemo class with the mixin's features.
613 """
614 """
614
615
615 def marquee(self,txt='',width=78,mark='*'):
616 def marquee(self,txt='',width=78,mark='*'):
616 """Blank marquee that returns '' no matter what the input."""
617 """Blank marquee that returns '' no matter what the input."""
617 return ''
618 return ''
618
619
619 def pre_cmd(self):
620 def pre_cmd(self):
620 """Method called before executing each block.
621 """Method called before executing each block.
621
622
622 This one simply clears the screen."""
623 This one simply clears the screen."""
623 from IPython.utils.terminal import _term_clear
624 from IPython.utils.terminal import _term_clear
624 _term_clear()
625 _term_clear()
625
626
626 class ClearDemo(ClearMixin,Demo):
627 class ClearDemo(ClearMixin,Demo):
627 pass
628 pass
628
629
629
630
630 class ClearIPDemo(ClearMixin,IPythonDemo):
631 class ClearIPDemo(ClearMixin,IPythonDemo):
631 pass
632 pass
632
633
633
634
634 def slide(file_path, noclear=False, format_rst=True, formatter="terminal",
635 def slide(file_path, noclear=False, format_rst=True, formatter="terminal",
635 style="native", auto_all=False, delimiter='...'):
636 style="native", auto_all=False, delimiter='...'):
636 if noclear:
637 if noclear:
637 demo_class = Demo
638 demo_class = Demo
638 else:
639 else:
639 demo_class = ClearDemo
640 demo_class = ClearDemo
640 demo = demo_class(file_path, format_rst=format_rst, formatter=formatter,
641 demo = demo_class(file_path, format_rst=format_rst, formatter=formatter,
641 style=style, auto_all=auto_all)
642 style=style, auto_all=auto_all)
642 while not demo.finished:
643 while not demo.finished:
643 demo()
644 demo()
644 try:
645 try:
645 py3compat.input('\n' + delimiter)
646 py3compat.input('\n' + delimiter)
646 except KeyboardInterrupt:
647 except KeyboardInterrupt:
647 exit(1)
648 exit(1)
648
649
649 if __name__ == '__main__':
650 if __name__ == '__main__':
650 import argparse
651 import argparse
651 parser = argparse.ArgumentParser(description='Run python demos')
652 parser = argparse.ArgumentParser(description='Run python demos')
652 parser.add_argument('--noclear', '-C', action='store_true',
653 parser.add_argument('--noclear', '-C', action='store_true',
653 help='Do not clear terminal on each slide')
654 help='Do not clear terminal on each slide')
654 parser.add_argument('--rst', '-r', action='store_true',
655 parser.add_argument('--rst', '-r', action='store_true',
655 help='Highlight comments and dostrings as rst')
656 help='Highlight comments and dostrings as rst')
656 parser.add_argument('--formatter', '-f', default='terminal',
657 parser.add_argument('--formatter', '-f', default='terminal',
657 help='pygments formatter name could be: terminal, '
658 help='pygments formatter name could be: terminal, '
658 'terminal256, terminal16m')
659 'terminal256, terminal16m')
659 parser.add_argument('--style', '-s', default='default',
660 parser.add_argument('--style', '-s', default='default',
660 help='pygments style name')
661 help='pygments style name')
661 parser.add_argument('--auto', '-a', action='store_true',
662 parser.add_argument('--auto', '-a', action='store_true',
662 help='Run all blocks automatically without'
663 help='Run all blocks automatically without'
663 'confirmation')
664 'confirmation')
664 parser.add_argument('--delimiter', '-d', default='...',
665 parser.add_argument('--delimiter', '-d', default='...',
665 help='slides delimiter added after each slide run')
666 help='slides delimiter added after each slide run')
666 parser.add_argument('file', nargs=1,
667 parser.add_argument('file', nargs=1,
667 help='python demo file')
668 help='python demo file')
668 args = parser.parse_args()
669 args = parser.parse_args()
669 slide(args.file[0], noclear=args.noclear, format_rst=args.rst,
670 slide(args.file[0], noclear=args.noclear, format_rst=args.rst,
670 formatter=args.formatter, style=args.style, auto_all=args.auto,
671 formatter=args.formatter, style=args.style, auto_all=args.auto,
671 delimiter=args.delimiter)
672 delimiter=args.delimiter)
General Comments 0
You need to be logged in to leave comments. Login now