##// END OF EJS Templates
Catch clipboard exception in magic_paste and print error message
Matt Cottingham -
Show More
@@ -1,639 +1,646 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Subclass of InteractiveShell for terminal based frontends."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 7 # Copyright (C) 2008-2010 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import __builtin__
18 18 import bdb
19 19 import os
20 20 import re
21 21 import sys
22 22
23 23 try:
24 24 from contextlib import nested
25 25 except:
26 26 from IPython.utils.nested_context import nested
27 27
28 28 from IPython.core.error import TryNext
29 29 from IPython.core.usage import interactive_usage, default_banner
30 30 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
31 31 from IPython.lib.inputhook import enable_gui
32 32 from IPython.lib.pylabtools import pylab_activate
33 33 from IPython.testing.skipdoctest import skip_doctest
34 34 from IPython.utils import py3compat
35 35 from IPython.utils.terminal import toggle_set_term_title, set_term_title
36 36 from IPython.utils.process import abbrev_cwd
37 from IPython.utils.warn import warn
37 from IPython.utils.warn import warn, error
38 38 from IPython.utils.text import num_ini_spaces
39 39 from IPython.utils.traitlets import Int, CBool, Unicode
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Utilities
43 43 #-----------------------------------------------------------------------------
44 44
45 45 def get_default_editor():
46 46 try:
47 47 ed = os.environ['EDITOR']
48 48 except KeyError:
49 49 if os.name == 'posix':
50 50 ed = 'vi' # the only one guaranteed to be there!
51 51 else:
52 52 ed = 'notepad' # same in Windows!
53 53 return ed
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Main class
57 57 #-----------------------------------------------------------------------------
58 58
59 59 class TerminalInteractiveShell(InteractiveShell):
60 60
61 61 autoedit_syntax = CBool(False, config=True,
62 62 help="auto editing of files with syntax errors.")
63 63 banner = Unicode('')
64 64 banner1 = Unicode(default_banner, config=True,
65 65 help="""The part of the banner to be printed before the profile"""
66 66 )
67 67 banner2 = Unicode('', config=True,
68 68 help="""The part of the banner to be printed after the profile"""
69 69 )
70 70 confirm_exit = CBool(True, config=True,
71 71 help="""
72 72 Set to confirm when you try to exit IPython with an EOF (Control-D
73 73 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
74 74 you can force a direct exit without any confirmation.""",
75 75 )
76 76 # This display_banner only controls whether or not self.show_banner()
77 77 # is called when mainloop/interact are called. The default is False
78 78 # because for the terminal based application, the banner behavior
79 79 # is controlled by Global.display_banner, which IPythonApp looks at
80 80 # to determine if *it* should call show_banner() by hand or not.
81 81 display_banner = CBool(False) # This isn't configurable!
82 82 embedded = CBool(False)
83 83 embedded_active = CBool(False)
84 84 editor = Unicode(get_default_editor(), config=True,
85 85 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
86 86 )
87 87 pager = Unicode('less', config=True,
88 88 help="The shell program to be used for paging.")
89 89
90 90 screen_length = Int(0, config=True,
91 91 help=
92 92 """Number of lines of your screen, used to control printing of very
93 93 long strings. Strings longer than this number of lines will be sent
94 94 through a pager instead of directly printed. The default value for
95 95 this is 0, which means IPython will auto-detect your screen size every
96 96 time it needs to print certain potentially long strings (this doesn't
97 97 change the behavior of the 'print' keyword, it's only triggered
98 98 internally). If for some reason this isn't working well (it needs
99 99 curses support), specify it yourself. Otherwise don't change the
100 100 default.""",
101 101 )
102 102 term_title = CBool(False, config=True,
103 103 help="Enable auto setting the terminal title."
104 104 )
105 105
106 106 def __init__(self, config=None, ipython_dir=None, profile_dir=None, user_ns=None,
107 107 user_global_ns=None, custom_exceptions=((),None),
108 108 usage=None, banner1=None, banner2=None,
109 109 display_banner=None):
110 110
111 111 super(TerminalInteractiveShell, self).__init__(
112 112 config=config, profile_dir=profile_dir, user_ns=user_ns,
113 113 user_global_ns=user_global_ns, custom_exceptions=custom_exceptions
114 114 )
115 115 # use os.system instead of utils.process.system by default,
116 116 # because piped system doesn't make sense in the Terminal:
117 117 self.system = self.system_raw
118 118
119 119 self.init_term_title()
120 120 self.init_usage(usage)
121 121 self.init_banner(banner1, banner2, display_banner)
122 122
123 123 #-------------------------------------------------------------------------
124 124 # Things related to the terminal
125 125 #-------------------------------------------------------------------------
126 126
127 127 @property
128 128 def usable_screen_length(self):
129 129 if self.screen_length == 0:
130 130 return 0
131 131 else:
132 132 num_lines_bot = self.separate_in.count('\n')+1
133 133 return self.screen_length - num_lines_bot
134 134
135 135 def init_term_title(self):
136 136 # Enable or disable the terminal title.
137 137 if self.term_title:
138 138 toggle_set_term_title(True)
139 139 set_term_title('IPython: ' + abbrev_cwd())
140 140 else:
141 141 toggle_set_term_title(False)
142 142
143 143 #-------------------------------------------------------------------------
144 144 # Things related to aliases
145 145 #-------------------------------------------------------------------------
146 146
147 147 def init_alias(self):
148 148 # The parent class defines aliases that can be safely used with any
149 149 # frontend.
150 150 super(TerminalInteractiveShell, self).init_alias()
151 151
152 152 # Now define aliases that only make sense on the terminal, because they
153 153 # need direct access to the console in a way that we can't emulate in
154 154 # GUI or web frontend
155 155 if os.name == 'posix':
156 156 aliases = [('clear', 'clear'), ('more', 'more'), ('less', 'less'),
157 157 ('man', 'man')]
158 158 elif os.name == 'nt':
159 159 aliases = [('cls', 'cls')]
160 160
161 161
162 162 for name, cmd in aliases:
163 163 self.alias_manager.define_alias(name, cmd)
164 164
165 165 #-------------------------------------------------------------------------
166 166 # Things related to the banner and usage
167 167 #-------------------------------------------------------------------------
168 168
169 169 def _banner1_changed(self):
170 170 self.compute_banner()
171 171
172 172 def _banner2_changed(self):
173 173 self.compute_banner()
174 174
175 175 def _term_title_changed(self, name, new_value):
176 176 self.init_term_title()
177 177
178 178 def init_banner(self, banner1, banner2, display_banner):
179 179 if banner1 is not None:
180 180 self.banner1 = banner1
181 181 if banner2 is not None:
182 182 self.banner2 = banner2
183 183 if display_banner is not None:
184 184 self.display_banner = display_banner
185 185 self.compute_banner()
186 186
187 187 def show_banner(self, banner=None):
188 188 if banner is None:
189 189 banner = self.banner
190 190 self.write(banner)
191 191
192 192 def compute_banner(self):
193 193 self.banner = self.banner1
194 194 if self.profile and self.profile != 'default':
195 195 self.banner += '\nIPython profile: %s\n' % self.profile
196 196 if self.banner2:
197 197 self.banner += '\n' + self.banner2
198 198
199 199 def init_usage(self, usage=None):
200 200 if usage is None:
201 201 self.usage = interactive_usage
202 202 else:
203 203 self.usage = usage
204 204
205 205 #-------------------------------------------------------------------------
206 206 # Mainloop and code execution logic
207 207 #-------------------------------------------------------------------------
208 208
209 209 def mainloop(self, display_banner=None):
210 210 """Start the mainloop.
211 211
212 212 If an optional banner argument is given, it will override the
213 213 internally created default banner.
214 214 """
215 215
216 216 with nested(self.builtin_trap, self.display_trap):
217 217
218 218 while 1:
219 219 try:
220 220 self.interact(display_banner=display_banner)
221 221 #self.interact_with_readline()
222 222 # XXX for testing of a readline-decoupled repl loop, call
223 223 # interact_with_readline above
224 224 break
225 225 except KeyboardInterrupt:
226 226 # this should not be necessary, but KeyboardInterrupt
227 227 # handling seems rather unpredictable...
228 228 self.write("\nKeyboardInterrupt in interact()\n")
229 229
230 230 def _replace_rlhist_multiline(self, source_raw, hlen_before_cell):
231 231 """Store multiple lines as a single entry in history"""
232 232
233 233 # do nothing without readline or disabled multiline
234 234 if not self.has_readline or not self.multiline_history:
235 235 return hlen_before_cell
236 236
237 237 # windows rl has no remove_history_item
238 238 if not hasattr(self.readline, "remove_history_item"):
239 239 return hlen_before_cell
240 240
241 241 # skip empty cells
242 242 if not source_raw.rstrip():
243 243 return hlen_before_cell
244 244
245 245 # nothing changed do nothing, e.g. when rl removes consecutive dups
246 246 hlen = self.readline.get_current_history_length()
247 247 if hlen == hlen_before_cell:
248 248 return hlen_before_cell
249 249
250 250 for i in range(hlen - hlen_before_cell):
251 251 self.readline.remove_history_item(hlen - i - 1)
252 252 stdin_encoding = sys.stdin.encoding or "utf-8"
253 253 self.readline.add_history(py3compat.unicode_to_str(source_raw.rstrip(),
254 254 stdin_encoding))
255 255 return self.readline.get_current_history_length()
256 256
257 257 def interact(self, display_banner=None):
258 258 """Closely emulate the interactive Python console."""
259 259
260 260 # batch run -> do not interact
261 261 if self.exit_now:
262 262 return
263 263
264 264 if display_banner is None:
265 265 display_banner = self.display_banner
266 266
267 267 if isinstance(display_banner, basestring):
268 268 self.show_banner(display_banner)
269 269 elif display_banner:
270 270 self.show_banner()
271 271
272 272 more = False
273 273
274 274 # Mark activity in the builtins
275 275 __builtin__.__dict__['__IPYTHON__active'] += 1
276 276
277 277 if self.has_readline:
278 278 self.readline_startup_hook(self.pre_readline)
279 279 hlen_b4_cell = self.readline.get_current_history_length()
280 280 else:
281 281 hlen_b4_cell = 0
282 282 # exit_now is set by a call to %Exit or %Quit, through the
283 283 # ask_exit callback.
284 284
285 285 while not self.exit_now:
286 286 self.hooks.pre_prompt_hook()
287 287 if more:
288 288 try:
289 289 prompt = self.hooks.generate_prompt(True)
290 290 except:
291 291 self.showtraceback()
292 292 if self.autoindent:
293 293 self.rl_do_indent = True
294 294
295 295 else:
296 296 try:
297 297 prompt = self.hooks.generate_prompt(False)
298 298 except:
299 299 self.showtraceback()
300 300 try:
301 301 line = self.raw_input(prompt)
302 302 if self.exit_now:
303 303 # quick exit on sys.std[in|out] close
304 304 break
305 305 if self.autoindent:
306 306 self.rl_do_indent = False
307 307
308 308 except KeyboardInterrupt:
309 309 #double-guard against keyboardinterrupts during kbdint handling
310 310 try:
311 311 self.write('\nKeyboardInterrupt\n')
312 312 source_raw = self.input_splitter.source_raw_reset()[1]
313 313 hlen_b4_cell = \
314 314 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
315 315 more = False
316 316 except KeyboardInterrupt:
317 317 pass
318 318 except EOFError:
319 319 if self.autoindent:
320 320 self.rl_do_indent = False
321 321 if self.has_readline:
322 322 self.readline_startup_hook(None)
323 323 self.write('\n')
324 324 self.exit()
325 325 except bdb.BdbQuit:
326 326 warn('The Python debugger has exited with a BdbQuit exception.\n'
327 327 'Because of how pdb handles the stack, it is impossible\n'
328 328 'for IPython to properly format this particular exception.\n'
329 329 'IPython will resume normal operation.')
330 330 except:
331 331 # exceptions here are VERY RARE, but they can be triggered
332 332 # asynchronously by signal handlers, for example.
333 333 self.showtraceback()
334 334 else:
335 335 self.input_splitter.push(line)
336 336 more = self.input_splitter.push_accepts_more()
337 337 if (self.SyntaxTB.last_syntax_error and
338 338 self.autoedit_syntax):
339 339 self.edit_syntax_error()
340 340 if not more:
341 341 source_raw = self.input_splitter.source_raw_reset()[1]
342 342 self.run_cell(source_raw, store_history=True)
343 343 hlen_b4_cell = \
344 344 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
345 345
346 346 # We are off again...
347 347 __builtin__.__dict__['__IPYTHON__active'] -= 1
348 348
349 349 # Turn off the exit flag, so the mainloop can be restarted if desired
350 350 self.exit_now = False
351 351
352 352 def raw_input(self, prompt=''):
353 353 """Write a prompt and read a line.
354 354
355 355 The returned line does not include the trailing newline.
356 356 When the user enters the EOF key sequence, EOFError is raised.
357 357
358 358 Optional inputs:
359 359
360 360 - prompt(''): a string to be printed to prompt the user.
361 361
362 362 - continue_prompt(False): whether this line is the first one or a
363 363 continuation in a sequence of inputs.
364 364 """
365 365 # Code run by the user may have modified the readline completer state.
366 366 # We must ensure that our completer is back in place.
367 367
368 368 if self.has_readline:
369 369 self.set_readline_completer()
370 370
371 371 try:
372 372 line = py3compat.str_to_unicode(self.raw_input_original(prompt))
373 373 except ValueError:
374 374 warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
375 375 " or sys.stdout.close()!\nExiting IPython!")
376 376 self.ask_exit()
377 377 return ""
378 378
379 379 # Try to be reasonably smart about not re-indenting pasted input more
380 380 # than necessary. We do this by trimming out the auto-indent initial
381 381 # spaces, if the user's actual input started itself with whitespace.
382 382 if self.autoindent:
383 383 if num_ini_spaces(line) > self.indent_current_nsp:
384 384 line = line[self.indent_current_nsp:]
385 385 self.indent_current_nsp = 0
386 386
387 387 return line
388 388
389 389 #-------------------------------------------------------------------------
390 390 # Methods to support auto-editing of SyntaxErrors.
391 391 #-------------------------------------------------------------------------
392 392
393 393 def edit_syntax_error(self):
394 394 """The bottom half of the syntax error handler called in the main loop.
395 395
396 396 Loop until syntax error is fixed or user cancels.
397 397 """
398 398
399 399 while self.SyntaxTB.last_syntax_error:
400 400 # copy and clear last_syntax_error
401 401 err = self.SyntaxTB.clear_err_state()
402 402 if not self._should_recompile(err):
403 403 return
404 404 try:
405 405 # may set last_syntax_error again if a SyntaxError is raised
406 406 self.safe_execfile(err.filename,self.user_ns)
407 407 except:
408 408 self.showtraceback()
409 409 else:
410 410 try:
411 411 f = file(err.filename)
412 412 try:
413 413 # This should be inside a display_trap block and I
414 414 # think it is.
415 415 sys.displayhook(f.read())
416 416 finally:
417 417 f.close()
418 418 except:
419 419 self.showtraceback()
420 420
421 421 def _should_recompile(self,e):
422 422 """Utility routine for edit_syntax_error"""
423 423
424 424 if e.filename in ('<ipython console>','<input>','<string>',
425 425 '<console>','<BackgroundJob compilation>',
426 426 None):
427 427
428 428 return False
429 429 try:
430 430 if (self.autoedit_syntax and
431 431 not self.ask_yes_no('Return to editor to correct syntax error? '
432 432 '[Y/n] ','y')):
433 433 return False
434 434 except EOFError:
435 435 return False
436 436
437 437 def int0(x):
438 438 try:
439 439 return int(x)
440 440 except TypeError:
441 441 return 0
442 442 # always pass integer line and offset values to editor hook
443 443 try:
444 444 self.hooks.fix_error_editor(e.filename,
445 445 int0(e.lineno),int0(e.offset),e.msg)
446 446 except TryNext:
447 447 warn('Could not open editor')
448 448 return False
449 449 return True
450 450
451 451 #-------------------------------------------------------------------------
452 452 # Things related to GUI support and pylab
453 453 #-------------------------------------------------------------------------
454 454
455 455 def enable_pylab(self, gui=None, import_all=True):
456 456 """Activate pylab support at runtime.
457 457
458 458 This turns on support for matplotlib, preloads into the interactive
459 459 namespace all of numpy and pylab, and configures IPython to correcdtly
460 460 interact with the GUI event loop. The GUI backend to be used can be
461 461 optionally selected with the optional :param:`gui` argument.
462 462
463 463 Parameters
464 464 ----------
465 465 gui : optional, string
466 466
467 467 If given, dictates the choice of matplotlib GUI backend to use
468 468 (should be one of IPython's supported backends, 'tk', 'qt', 'wx' or
469 469 'gtk'), otherwise we use the default chosen by matplotlib (as
470 470 dictated by the matplotlib build-time options plus the user's
471 471 matplotlibrc configuration file).
472 472 """
473 473 # We want to prevent the loading of pylab to pollute the user's
474 474 # namespace as shown by the %who* magics, so we execute the activation
475 475 # code in an empty namespace, and we update *both* user_ns and
476 476 # user_ns_hidden with this information.
477 477 ns = {}
478 478 try:
479 479 gui = pylab_activate(ns, gui, import_all)
480 480 except KeyError:
481 481 error("Backend %r not supported" % gui)
482 482 return
483 483 self.user_ns.update(ns)
484 484 self.user_ns_hidden.update(ns)
485 485 # Now we must activate the gui pylab wants to use, and fix %run to take
486 486 # plot updates into account
487 487 enable_gui(gui)
488 488 self.magic_run = self._pylab_magic_run
489 489
490 490 #-------------------------------------------------------------------------
491 491 # Things related to exiting
492 492 #-------------------------------------------------------------------------
493 493
494 494 def ask_exit(self):
495 495 """ Ask the shell to exit. Can be overiden and used as a callback. """
496 496 self.exit_now = True
497 497
498 498 def exit(self):
499 499 """Handle interactive exit.
500 500
501 501 This method calls the ask_exit callback."""
502 502 if self.confirm_exit:
503 503 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'):
504 504 self.ask_exit()
505 505 else:
506 506 self.ask_exit()
507 507
508 508 #------------------------------------------------------------------------
509 509 # Magic overrides
510 510 #------------------------------------------------------------------------
511 511 # Once the base class stops inheriting from magic, this code needs to be
512 512 # moved into a separate machinery as well. For now, at least isolate here
513 513 # the magics which this class needs to implement differently from the base
514 514 # class, or that are unique to it.
515 515
516 516 def magic_autoindent(self, parameter_s = ''):
517 517 """Toggle autoindent on/off (if available)."""
518 518
519 519 self.shell.set_autoindent()
520 520 print "Automatic indentation is:",['OFF','ON'][self.shell.autoindent]
521 521
522 522 @skip_doctest
523 523 def magic_cpaste(self, parameter_s=''):
524 524 """Paste & execute a pre-formatted code block from clipboard.
525 525
526 526 You must terminate the block with '--' (two minus-signs) or Ctrl-D alone on the
527 527 line. You can also provide your own sentinel with '%paste -s %%' ('%%'
528 528 is the new sentinel for this operation)
529 529
530 530 The block is dedented prior to execution to enable execution of method
531 531 definitions. '>' and '+' characters at the beginning of a line are
532 532 ignored, to allow pasting directly from e-mails, diff files and
533 533 doctests (the '...' continuation prompt is also stripped). The
534 534 executed block is also assigned to variable named 'pasted_block' for
535 535 later editing with '%edit pasted_block'.
536 536
537 537 You can also pass a variable name as an argument, e.g. '%cpaste foo'.
538 538 This assigns the pasted block to variable 'foo' as string, without
539 539 dedenting or executing it (preceding >>> and + is still stripped)
540 540
541 541 '%cpaste -r' re-executes the block previously entered by cpaste.
542 542
543 543 Do not be alarmed by garbled output on Windows (it's a readline bug).
544 544 Just press enter and type -- (and press enter again) and the block
545 545 will be what was just pasted.
546 546
547 547 IPython statements (magics, shell escapes) are not supported (yet).
548 548
549 549 See also
550 550 --------
551 551 paste: automatically pull code from clipboard.
552 552
553 553 Examples
554 554 --------
555 555 ::
556 556
557 557 In [8]: %cpaste
558 558 Pasting code; enter '--' alone on the line to stop.
559 559 :>>> a = ["world!", "Hello"]
560 560 :>>> print " ".join(sorted(a))
561 561 :--
562 562 Hello world!
563 563 """
564 564
565 565 opts,args = self.parse_options(parameter_s,'rs:',mode='string')
566 566 par = args.strip()
567 567 if opts.has_key('r'):
568 568 self._rerun_pasted()
569 569 return
570 570
571 571 sentinel = opts.get('s','--')
572 572
573 573 block = self._strip_pasted_lines_for_code(
574 574 self._get_pasted_lines(sentinel))
575 575
576 576 self._execute_block(block, par)
577 577
578 578 def magic_paste(self, parameter_s=''):
579 579 """Paste & execute a pre-formatted code block from clipboard.
580 580
581 581 The text is pulled directly from the clipboard without user
582 582 intervention and printed back on the screen before execution (unless
583 583 the -q flag is given to force quiet mode).
584 584
585 585 The block is dedented prior to execution to enable execution of method
586 586 definitions. '>' and '+' characters at the beginning of a line are
587 587 ignored, to allow pasting directly from e-mails, diff files and
588 588 doctests (the '...' continuation prompt is also stripped). The
589 589 executed block is also assigned to variable named 'pasted_block' for
590 590 later editing with '%edit pasted_block'.
591 591
592 592 You can also pass a variable name as an argument, e.g. '%paste foo'.
593 593 This assigns the pasted block to variable 'foo' as string, without
594 594 dedenting or executing it (preceding >>> and + is still stripped)
595 595
596 596 Options
597 597 -------
598 598
599 599 -r: re-executes the block previously entered by cpaste.
600 600
601 601 -q: quiet mode: do not echo the pasted text back to the terminal.
602 602
603 603 IPython statements (magics, shell escapes) are not supported (yet).
604 604
605 605 See also
606 606 --------
607 607 cpaste: manually paste code into terminal until you mark its end.
608 608 """
609 609 opts,args = self.parse_options(parameter_s,'rq',mode='string')
610 610 par = args.strip()
611 611 if opts.has_key('r'):
612 612 self._rerun_pasted()
613 613 return
614
615 text = self.shell.hooks.clipboard_get()
616 block = self._strip_pasted_lines_for_code(text.splitlines())
614 try:
615 text = self.shell.hooks.clipboard_get()
616 block = self._strip_pasted_lines_for_code(text.splitlines())
617 except TryNext as clipboard_exc:
618 message = getattr(clipboard_exc, 'args')
619 if message:
620 error(message)
621 else:
622 error('Could not get text from the clipboard.')
623 return
617 624
618 625 # By default, echo back to terminal unless quiet mode is requested
619 626 if not opts.has_key('q'):
620 627 write = self.shell.write
621 628 write(self.shell.pycolorize(block))
622 629 if not block.endswith('\n'):
623 630 write('\n')
624 631 write("## -- End pasted text --\n")
625 632
626 633 self._execute_block(block, par)
627 634
628 635 if sys.platform == 'win32':
629 636 def magic_cls(self, s):
630 637 """Clear screen.
631 638 """
632 639 os.system("cls")
633 640
634 641 def showindentationerror(self):
635 642 super(TerminalInteractiveShell, self).showindentationerror()
636 643 print("If you want to paste code into IPython, try the %paste magic function.")
637 644
638 645
639 646 InteractiveShellABC.register(TerminalInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now