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