From edd2e623ee5ae4ddb48ff3ec8faad4138b3ed42b 2009-08-03 20:19:57 From: Brian Granger Date: 2009-08-03 20:19:57 Subject: [PATCH] Merging -r 1181 from lp:ipython. A few changes were made to resolve conflicts: * clipboard.py moved to core. * Imports fixed. --- diff --git a/IPython/core/hooks.py b/IPython/core/hooks.py index c6225c0..2616d0b 100644 --- a/IPython/core/hooks.py +++ b/IPython/core/hooks.py @@ -43,8 +43,9 @@ somewhere in your configuration files or ipython command line. from IPython.core import ipapi -import os,bisect -from IPython.utils.genutils import Term,shell +import os, bisect +import sys +from IPython.utils.genutils import Term, shell from pprint import PrettyPrinter # List here all the default hooks. For now it's just the editor functions @@ -53,7 +54,8 @@ from pprint import PrettyPrinter __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor', 'result_display', 'input_prefilter', 'shutdown_hook', 'late_startup_hook', 'generate_prompt', 'generate_output_prompt','shell_hook', - 'show_in_pager','pre_prompt_hook', 'pre_runcode_hook'] + 'show_in_pager','pre_prompt_hook', 'pre_runcode_hook', + 'clipboard_get'] # vds: << pformat = PrettyPrinter().pformat @@ -243,5 +245,22 @@ def pre_prompt_hook(self): def pre_runcode_hook(self): """ Executed before running the (prefiltered) code in IPython """ return None - +def clipboard_get(self): + """ Get text from the clipboard. + """ + from IPython.lib.clipboard import ( + osx_clipboard_get, tkinter_clipboard_get, + win32_clipboard_get + ) + if sys.platform == 'win32': + chain = [win32_clipboard_get, tkinter_clipboard_get] + elif sys.platform == 'darwin': + chain = [osx_clipboard_get, tkinter_clipboard_get] + else: + chain = [tkinter_clipboard_get] + dispatcher = CommandChainDispatcher() + for func in chain: + dispatcher.add(func) + text = dispatcher() + return text diff --git a/IPython/core/magic.py b/IPython/core/magic.py index aa9fff3..41aefd9 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -3262,6 +3262,61 @@ Defaulting color scheme to 'NoColor'""" page(self.shell.pycolorize(cont), screen_lines=self.shell.rc.screen_length) + def _rerun_pasted(self): + """ Rerun a previously pasted command. + """ + b = self.user_ns.get('pasted_block', None) + if b is None: + raise UsageError('No previous pasted block available') + print "Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)) + exec b in self.user_ns + + def _get_pasted_lines(self, sentinel): + """ Yield pasted lines until the user enters the given sentinel value. + """ + from IPython.core import iplib + print "Pasting code; enter '%s' alone on the line to stop." % sentinel + while True: + l = iplib.raw_input_original(':') + if l == sentinel: + return + else: + yield l + + def _strip_pasted_lines_for_code(self, raw_lines): + """ Strip non-code parts of a sequence of lines to return a block of + code. + """ + # Regular expressions that declare text we strip from the input: + strip_re = [r'^\s*In \[\d+\]:', # IPython input prompt + r'^\s*(\s?>)+', # Python input prompt + r'^\s*\.{3,}', # Continuation prompts + r'^\++', + ] + + strip_from_start = map(re.compile,strip_re) + + lines = [] + for l in raw_lines: + for pat in strip_from_start: + l = pat.sub('',l) + lines.append(l) + + block = "\n".join(lines) + '\n' + #print "block:\n",block + return block + + def _execute_block(self, block, par): + """ Execute a block, or store it in a variable, per the user's request. + """ + if not par: + b = textwrap.dedent(block) + self.user_ns['pasted_block'] = b + exec b in self.user_ns + else: + self.user_ns[par] = SList(block.splitlines()) + print "Block assigned to '%s'" % par + def magic_cpaste(self, parameter_s=''): """Allows you to paste & execute a pre-formatted code block from clipboard. @@ -3287,50 +3342,60 @@ Defaulting color scheme to 'NoColor'""" will be what was just pasted. IPython statements (magics, shell escapes) are not supported (yet). + + See also + -------- + %paste: automatically pull code from clipboard. """ + opts,args = self.parse_options(parameter_s,'rs:',mode='string') par = args.strip() if opts.has_key('r'): - b = self.user_ns.get('pasted_block', None) - if b is None: - raise UsageError('No previous pasted block available') - print "Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)) - exec b in self.user_ns + self._rerun_pasted() return sentinel = opts.get('s','--') - # Regular expressions that declare text we strip from the input: - strip_re = [r'^\s*In \[\d+\]:', # IPython input prompt - r'^\s*(\s?>)+', # Python input prompt - r'^\s*\.{3,}', # Continuation prompts - r'^\++', - ] + block = self._strip_pasted_lines_for_code( + self._get_pasted_lines(sentinel)) - strip_from_start = map(re.compile,strip_re) + self._execute_block(block, par) + + def magic_paste(self, parameter_s=''): + """Allows you to paste & execute a pre-formatted code block from clipboard. - from IPython.core import iplib - lines = [] - print "Pasting code; enter '%s' alone on the line to stop." % sentinel - while 1: - l = iplib.raw_input_original(':') - if l ==sentinel: - break - - for pat in strip_from_start: - l = pat.sub('',l) - lines.append(l) - - block = "\n".join(lines) + '\n' - #print "block:\n",block - if not par: - b = textwrap.dedent(block) - self.user_ns['pasted_block'] = b - exec b in self.user_ns - else: - self.user_ns[par] = SList(block.splitlines()) - print "Block assigned to '%s'" % par - + The text is pulled directly from the clipboard without user + intervention. + + 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 + dedenting or executing it (preceding >>> and + is still stripped) + + '%paste -r' re-executes the block previously entered by cpaste. + + IPython statements (magics, shell escapes) are not supported (yet). + + See also + -------- + %cpaste: manually paste code into terminal until you mark its end. + """ + opts,args = self.parse_options(parameter_s,'r:',mode='string') + par = args.strip() + if opts.has_key('r'): + self._rerun_pasted() + return + + text = self.shell.hooks.clipboard_get() + block = self._strip_pasted_lines_for_code(text.splitlines()) + self._execute_block(block, par) + def magic_quickref(self,arg): """ Show a quick reference sheet """ import IPython.core.usage diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 6e48d30..588adc4 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -252,3 +252,39 @@ class TestMagicRun(object): def teardown(self): self.tmpfile.close() + +# Multiple tests for clipboard pasting +def test_paste(): + + def paste(txt): + hooks.clipboard_get = lambda : txt + _ip.magic('paste') + + # Inject fake clipboard hook but save original so we can restore it later + hooks = _ip.IP.hooks + user_ns = _ip.user_ns + original_clip = hooks.clipboard_get + + try: + # Run tests with fake clipboard function + user_ns.pop('x', None) + paste('x=1') + yield (nt.assert_equal, user_ns['x'], 1) + + user_ns.pop('x', None) + paste('>>> x=2') + yield (nt.assert_equal, user_ns['x'], 2) + + paste(""" + >>> x = [1,2,3] + >>> y = [] + >>> for i in x: + ... y.append(i**2) + ... + """) + yield (nt.assert_equal, user_ns['x'], [1,2,3]) + yield (nt.assert_equal, user_ns['y'], [1,4,9]) + + finally: + # Restore original hook + hooks.clipboard_get = original_clip diff --git a/IPython/lib/clipboard.py b/IPython/lib/clipboard.py new file mode 100644 index 0000000..6607f6e --- /dev/null +++ b/IPython/lib/clipboard.py @@ -0,0 +1,56 @@ +""" Utilities for accessing the platform's clipboard. +""" + +import subprocess +import sys + +from IPython.ipapi import TryNext + + +def win32_clipboard_get(): + """ Get the current clipboard's text on Windows. + + Requires Mark Hammond's pywin32 extensions. + """ + try: + import win32clipboard + except ImportError: + message = ("Getting text from the clipboard requires the pywin32 " + "extensions: http://sourceforge.net/projects/pywin32/") + raise TryNext(message) + win32clipboard.OpenClipboard() + text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT) + # FIXME: convert \r\n to \n? + win32clipboard.CloseClipboard() + return text + +def osx_clipboard_get(): + """ Get the clipboard's text on OS X. + """ + p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'], + stdout=subprocess.PIPE) + text, stderr = p.communicate() + # Text comes in with old Mac \r line endings. Change them to \n. + text = text.replace('\r', '\n') + return text + +def tkinter_clipboard_get(): + """ Get the clipboard's text using Tkinter. + + This is the default on systems that are not Windows or OS X. It may + interfere with other UI toolkits and should be replaced with an + implementation that uses that toolkit. + """ + try: + import Tkinter + except ImportError: + message = ("Getting text from the clipboard on this platform " + "requires Tkinter.") + raise TryNext(message) + root = Tkinter.Tk() + root.withdraw() + text = root.clipboard_get() + root.destroy() + return text + +