208 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			208 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | """Extra magics for terminal use.""" | ||
|  | 
 | ||
|  | # Copyright (c) yap_ipython Development Team. | ||
|  | # Distributed under the terms of the Modified BSD License. | ||
|  | 
 | ||
|  | 
 | ||
|  | from logging import error | ||
|  | import os | ||
|  | import sys | ||
|  | 
 | ||
|  | from yap_ipython.core.error import TryNext, UsageError | ||
|  | from yap_ipython.core.inputsplitter import IPythonInputSplitter | ||
|  | from yap_ipython.core.magic import Magics, magics_class, line_magic | ||
|  | from yap_ipython.lib.clipboard import ClipboardEmpty | ||
|  | from yap_ipython.utils.text import SList, strip_email_quotes | ||
|  | from yap_ipython.utils import py3compat | ||
|  | 
 | ||
|  | def get_pasted_lines(sentinel, l_input=py3compat.input, quiet=False): | ||
|  |     """ Yield pasted lines until the user enters the given sentinel value.
 | ||
|  |     """
 | ||
|  |     if not quiet: | ||
|  |         print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \ | ||
|  |               % sentinel) | ||
|  |         prompt = ":" | ||
|  |     else: | ||
|  |         prompt = "" | ||
|  |     while True: | ||
|  |         try: | ||
|  |             l = l_input(prompt) | ||
|  |             if l == sentinel: | ||
|  |                 return | ||
|  |             else: | ||
|  |                 yield l | ||
|  |         except EOFError: | ||
|  |             print('<EOF>') | ||
|  |             return | ||
|  | 
 | ||
|  | 
 | ||
|  | @magics_class | ||
|  | class TerminalMagics(Magics): | ||
|  |     def __init__(self, shell): | ||
|  |         super(TerminalMagics, self).__init__(shell) | ||
|  |         self.input_splitter = IPythonInputSplitter() | ||
|  | 
 | ||
|  |     def store_or_execute(self, block, name): | ||
|  |         """ Execute a block, or store it in a variable, per the user's request.
 | ||
|  |         """
 | ||
|  |         if name: | ||
|  |             # If storing it for further editing | ||
|  |             self.shell.user_ns[name] = SList(block.splitlines()) | ||
|  |             print("Block assigned to '%s'" % name) | ||
|  |         else: | ||
|  |             b = self.preclean_input(block) | ||
|  |             self.shell.user_ns['pasted_block'] = b | ||
|  |             self.shell.using_paste_magics = True | ||
|  |             try: | ||
|  |                 self.shell.run_cell(b) | ||
|  |             finally: | ||
|  |                 self.shell.using_paste_magics = False | ||
|  | 
 | ||
|  |     def preclean_input(self, block): | ||
|  |         lines = block.splitlines() | ||
|  |         while lines and not lines[0].strip(): | ||
|  |             lines = lines[1:] | ||
|  |         return strip_email_quotes('\n'.join(lines)) | ||
|  | 
 | ||
|  |     def rerun_pasted(self, name='pasted_block'): | ||
|  |         """ Rerun a previously pasted command.
 | ||
|  |         """
 | ||
|  |         b = self.shell.user_ns.get(name) | ||
|  | 
 | ||
|  |         # Sanity checks | ||
|  |         if b is None: | ||
|  |             raise UsageError('No previous pasted block available') | ||
|  |         if not isinstance(b, str): | ||
|  |             raise UsageError( | ||
|  |                 "Variable 'pasted_block' is not a string, can't execute") | ||
|  | 
 | ||
|  |         print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b))) | ||
|  |         self.shell.run_cell(b) | ||
|  | 
 | ||
|  |     @line_magic | ||
|  |     def autoindent(self, parameter_s = ''): | ||
|  |         """Toggle autoindent on/off (deprecated)""" | ||
|  |         print("%autoindent is deprecated since yap_ipython 5: you can now paste " | ||
|  |               "multiple lines without turning autoindentation off.") | ||
|  |         self.shell.set_autoindent() | ||
|  |         print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) | ||
|  | 
 | ||
|  |     @line_magic | ||
|  |     def cpaste(self, parameter_s=''): | ||
|  |         """Paste & execute a pre-formatted code block from clipboard.
 | ||
|  | 
 | ||
|  |         You must terminate the block with '--' (two minus-signs) or Ctrl-D | ||
|  |         alone on the line. You can also provide your own sentinel with '%paste | ||
|  |         -s %%' ('%%' is the new sentinel for this operation). | ||
|  | 
 | ||
|  |         The block is dedented prior to execution to enable execution of method | ||
|  |         definitions. '>' and '+' characters at the beginning of a line are | ||
|  |         ignored, to allow pasting directly from e-mails, diff files and | ||
|  |         doctests (the '...' continuation prompt is also stripped).  The | ||
|  |         executed block is also assigned to variable named 'pasted_block' for | ||
|  |         later editing with '%edit pasted_block'. | ||
|  | 
 | ||
|  |         You can also pass a variable name as an argument, e.g. '%cpaste foo'. | ||
|  |         This assigns the pasted block to variable 'foo' as string, without | ||
|  |         dedenting or executing it (preceding >>> and + is still stripped) | ||
|  | 
 | ||
|  |         '%cpaste -r' re-executes the block previously entered by cpaste. | ||
|  |         '%cpaste -q' suppresses any additional output messages. | ||
|  | 
 | ||
|  |         Do not be alarmed by garbled output on Windows (it's a readline bug). | ||
|  |         Just press enter and type -- (and press enter again) and the block | ||
|  |         will be what was just pasted. | ||
|  | 
 | ||
|  |         yap_ipython statements (magics, shell escapes) are not supported (yet). | ||
|  | 
 | ||
|  |         See also | ||
|  |         -------- | ||
|  |         paste: automatically pull code from clipboard. | ||
|  | 
 | ||
|  |         Examples | ||
|  |         -------- | ||
|  |         :: | ||
|  | 
 | ||
|  |           In [8]: %cpaste | ||
|  |           Pasting code; enter '--' alone on the line to stop. | ||
|  |           :>>> a = ["world!", "Hello"] | ||
|  |           :>>> print " ".join(sorted(a)) | ||
|  |           :-- | ||
|  |           Hello world! | ||
|  |         """
 | ||
|  |         opts, name = self.parse_options(parameter_s, 'rqs:', mode='string') | ||
|  |         if 'r' in opts: | ||
|  |             self.rerun_pasted() | ||
|  |             return | ||
|  | 
 | ||
|  |         quiet = ('q' in opts) | ||
|  | 
 | ||
|  |         sentinel = opts.get('s', u'--') | ||
|  |         block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet)) | ||
|  |         self.store_or_execute(block, name) | ||
|  | 
 | ||
|  |     @line_magic | ||
|  |     def paste(self, parameter_s=''): | ||
|  |         """Paste & execute a pre-formatted code block from clipboard.
 | ||
|  | 
 | ||
|  |         The text is pulled directly from the clipboard without user | ||
|  |         intervention and printed back on the screen before execution (unless | ||
|  |         the -q flag is given to force quiet mode). | ||
|  | 
 | ||
|  |         The block is dedented prior to execution to enable execution of method | ||
|  |         definitions. '>' and '+' characters at the beginning of a line are | ||
|  |         ignored, to allow pasting directly from e-mails, diff files and | ||
|  |         doctests (the '...' continuation prompt is also stripped).  The | ||
|  |         executed block is also assigned to variable named 'pasted_block' for | ||
|  |         later editing with '%edit pasted_block'. | ||
|  | 
 | ||
|  |         You can also pass a variable name as an argument, e.g. '%paste foo'. | ||
|  |         This assigns the pasted block to variable 'foo' as string, without | ||
|  |         executing it (preceding >>> and + is still stripped). | ||
|  | 
 | ||
|  |         Options: | ||
|  | 
 | ||
|  |           -r: re-executes the block previously entered by cpaste. | ||
|  | 
 | ||
|  |           -q: quiet mode: do not echo the pasted text back to the terminal. | ||
|  | 
 | ||
|  |         yap_ipython statements (magics, shell escapes) are not supported (yet). | ||
|  | 
 | ||
|  |         See also | ||
|  |         -------- | ||
|  |         cpaste: manually paste code into terminal until you mark its end. | ||
|  |         """
 | ||
|  |         opts, name = self.parse_options(parameter_s, 'rq', mode='string') | ||
|  |         if 'r' in opts: | ||
|  |             self.rerun_pasted() | ||
|  |             return | ||
|  |         try: | ||
|  |             block = self.shell.hooks.clipboard_get() | ||
|  |         except TryNext as clipboard_exc: | ||
|  |             message = getattr(clipboard_exc, 'args') | ||
|  |             if message: | ||
|  |                 error(message[0]) | ||
|  |             else: | ||
|  |                 error('Could not get text from the clipboard.') | ||
|  |             return | ||
|  |         except ClipboardEmpty: | ||
|  |             raise UsageError("The clipboard appears to be empty") | ||
|  | 
 | ||
|  |         # By default, echo back to terminal unless quiet mode is requested | ||
|  |         if 'q' not in opts: | ||
|  |             write = self.shell.write | ||
|  |             write(self.shell.pycolorize(block)) | ||
|  |             if not block.endswith('\n'): | ||
|  |                 write('\n') | ||
|  |             write("## -- End pasted text --\n") | ||
|  | 
 | ||
|  |         self.store_or_execute(block, name) | ||
|  | 
 | ||
|  |     # Class-level: add a '%cls' magic only on Windows | ||
|  |     if sys.platform == 'win32': | ||
|  |         @line_magic | ||
|  |         def cls(self, s): | ||
|  |             """Clear screen.
 | ||
|  |             """
 | ||
|  |             os.system("cls") |