"""Extra magics for terminal use.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. from logging import error import os import sys from IPython.core.error import TryNext, UsageError from IPython.core.magic import Magics, magics_class, line_magic from IPython.lib.clipboard import ClipboardEmpty from IPython.testing.skipdoctest import skip_doctest from IPython.utils.text import SList, strip_email_quotes from 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('') return @magics_class class TerminalMagics(Magics): def __init__(self, shell): super(TerminalMagics, self).__init__(shell) 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)""" self.shell.set_autoindent() print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) @skip_doctest @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. 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! :: In [8]: %cpaste Pasting code; enter '--' alone on the line to stop. :>>> %alias_magic t timeit :>>> %t -n1 pass :-- Created `%t` as an alias for `%timeit`. Created `%%t` as an alias for `%%timeit`. 354 ns ± 224 ns per loop (mean ± std. dev. of 7 runs, 1 loop each) """ 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. 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 as e: raise UsageError("The clipboard appears to be empty") from e # 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")