Skip to content

Commit

Permalink
Merge pull request #1007 from fperez/paste_bug
Browse files Browse the repository at this point in the history
Fix paste/cpaste bug and refactor/cleanup that code a lot.

In fixing a pasting bug (mishandling of whitespace when input had
prompts) it became clear the pasting code hadn't been updated
when the new prefiltering machinery was added.  Furthermore, the
pasting magics are only for the terminal, but the code was in the
base classes.

This refactors and simplifies the pasting code, moving it to the
terminal shell only, and removing unnecessary methods from the
main class (using small utility functions instead).  That will make it easier later to move them into dedicated magic objects when we have a chance to refactor the magic system.
  • Loading branch information
fperez committed Nov 26, 2011
2 parents 5f4ba44 + 84830ec commit 780b7c5
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 203 deletions.
60 changes: 0 additions & 60 deletions IPython/core/magic.py
Expand Up @@ -25,7 +25,6 @@
import shutil
import re
import time
import textwrap
from StringIO import StringIO
from getopt import getopt,GetoptError
from pprint import pformat
Expand Down Expand Up @@ -3203,65 +3202,6 @@ def magic_pycat(self, parameter_s=''):

page.page(self.shell.pycolorize(cont))

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 interactiveshell
print "Pasting code; enter '%s' alone on the line to stop." % sentinel
while True:
try:
l = self.shell.raw_input_original(':')
if l == sentinel:
return
else:
yield l
except EOFError:
print '<EOF>'
return

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
self.run_cell(b)
else:
self.user_ns[par] = SList(block.splitlines())
print "Block assigned to '%s'" % par

def magic_quickref(self,arg):
""" Show a quick reference sheet """
import IPython.core.usage
Expand Down
122 changes: 1 addition & 121 deletions IPython/core/tests/test_magic.py
Expand Up @@ -9,21 +9,17 @@
#-----------------------------------------------------------------------------

import os
import sys
import tempfile
import types
from StringIO import StringIO

import nose.tools as nt

from IPython.utils.path import get_long_path_name
from IPython.testing import decorators as dec
from IPython.testing import tools as tt
from IPython.utils import py3compat

#-----------------------------------------------------------------------------
# Test functions begin
#-----------------------------------------------------------------------------

def test_rehashx():
# clear up everything
_ip = get_ipython()
Expand Down Expand Up @@ -202,67 +198,6 @@ def test_numpy_clear_array_undec():
yield (nt.assert_false, 'a' in _ip.user_ns)


# Multiple tests for clipboard pasting
@dec.parametric
def test_paste():
_ip = get_ipython()
def paste(txt, flags='-q'):
"""Paste input text, by default in quiet mode"""
hooks.clipboard_get = lambda : txt
_ip.magic('paste '+flags)

# Inject fake clipboard hook but save original so we can restore it later
hooks = _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])

# Now, test that paste -r works
user_ns.pop('x', None)
yield nt.assert_false('x' in user_ns)
_ip.magic('paste -r')
yield nt.assert_equal(user_ns['x'], [1,2,3])

# Also test paste echoing, by temporarily faking the writer
w = StringIO()
writer = _ip.write
_ip.write = w.write
code = """
a = 100
b = 200"""
try:
paste(code,'')
out = w.getvalue()
finally:
_ip.write = writer
yield nt.assert_equal(user_ns['a'], 100)
yield nt.assert_equal(user_ns['b'], 200)
yield nt.assert_equal(out, code+"\n## -- End pasted text --\n")

finally:
# Restore original hook
hooks.clipboard_get = original_clip


def test_time():
_ip.magic('time None')

Expand Down Expand Up @@ -317,61 +252,6 @@ def test_dirops():
os.chdir(startdir)


def check_cpaste(code, should_fail=False):
"""Execute code via 'cpaste' and ensure it was executed, unless
should_fail is set.
"""
_ip.user_ns['code_ran'] = False

src = StringIO()
if not hasattr(src, 'encoding'):
# IPython expects stdin to have an encoding attribute
src.encoding = None
src.write('\n')
src.write(code)
src.write('\n--\n')
src.seek(0)

stdin_save = sys.stdin
sys.stdin = src

try:
context = tt.AssertPrints if should_fail else tt.AssertNotPrints
with context("Traceback (most recent call last)"):
_ip.magic('cpaste')

if not should_fail:
assert _ip.user_ns['code_ran']
finally:
sys.stdin = stdin_save


def test_cpaste():
"""Test cpaste magic"""

def run():
"""Marker function: sets a flag when executed.
"""
_ip.user_ns['code_ran'] = True
return 'run' # return string so '+ run()' doesn't result in success

tests = {'pass': ["> > > run()",
">>> > run()",
"+++ run()",
"++ run()",
" >>> run()"],

'fail': ["+ + run()",
" ++ run()"]}

_ip.user_ns['run'] = run

for code in tests['pass']:
check_cpaste(code)

for code in tests['fail']:
check_cpaste(code, should_fail=True)

def test_xmode():
# Calling xmode three times should be a no-op
xmode = _ip.InteractiveTB.mode
Expand Down
164 changes: 164 additions & 0 deletions IPython/core/tests/test_magic_terminal.py
@@ -0,0 +1,164 @@
"""Tests for various magic functions specific to the terminal frontend.
Needs to be run by nose (to make ipython session available).
"""
from __future__ import absolute_import

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

import sys
from StringIO import StringIO

import nose.tools as nt

from IPython.testing import decorators as dec
from IPython.testing import tools as tt

#-----------------------------------------------------------------------------
# Test functions begin
#-----------------------------------------------------------------------------

def check_cpaste(code, should_fail=False):
"""Execute code via 'cpaste' and ensure it was executed, unless
should_fail is set.
"""
_ip.user_ns['code_ran'] = False

src = StringIO()
if not hasattr(src, 'encoding'):
# IPython expects stdin to have an encoding attribute
src.encoding = None
src.write('\n')
src.write(code)
src.write('\n--\n')
src.seek(0)

stdin_save = sys.stdin
sys.stdin = src

try:
context = tt.AssertPrints if should_fail else tt.AssertNotPrints
with context("Traceback (most recent call last)"):
_ip.magic('cpaste')

if not should_fail:
assert _ip.user_ns['code_ran']
finally:
sys.stdin = stdin_save


def test_cpaste():
"""Test cpaste magic"""

def run():
"""Marker function: sets a flag when executed.
"""
_ip.user_ns['code_ran'] = True
return 'run' # return string so '+ run()' doesn't result in success

tests = {'pass': ["run()",
"In [1]: run()",
"In [1]: if 1:\n ...: run()",
"> > > run()",
">>> run()",
" >>> run()",
],

'fail': ["1 + run()",
"++ run()"]}

_ip.user_ns['run'] = run

for code in tests['pass']:
check_cpaste(code)

for code in tests['fail']:
check_cpaste(code, should_fail=True)


# Multiple tests for clipboard pasting
def test_paste():
_ip = get_ipython()

def paste(txt, flags='-q'):
"""Paste input text, by default in quiet mode"""
hooks.clipboard_get = lambda : txt
_ip.magic('paste '+flags)

# Inject fake clipboard hook but save original so we can restore it later
hooks = _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')
nt.assert_equal(user_ns['x'], 1)

user_ns.pop('x', None)
paste('>>> x=2')
nt.assert_equal(user_ns['x'], 2)

paste("""
>>> x = [1,2,3]
>>> y = []
>>> for i in x:
... y.append(i**2)
...
""")
nt.assert_equal(user_ns['x'], [1,2,3])
nt.assert_equal(user_ns['y'], [1,4,9])

# Now, test that paste -r works
user_ns.pop('x', None)
nt.assert_false('x' in user_ns)
_ip.magic('paste -r')
nt.assert_equal(user_ns['x'], [1,2,3])

# Test pasting of email-quoted contents
paste("""
>> def foo(x):
>> return x + 1
>> x = foo(1.1)
""")
nt.assert_equal(user_ns['x'], 2.1)

# Email again; some programs add a space also at each quoting level
paste("""
> > def foo(x):
> > return x + 1
> > x = foo(2.1)
""")
nt.assert_equal(user_ns['x'], 3.1)

# Email quoting of interactive input
paste("""
>> >>> def f(x):
>> ... return x+1
>> ...
>> >>> x = f(2.5)
""")
nt.assert_equal(user_ns['x'], 3.5)

# Also test paste echoing, by temporarily faking the writer
w = StringIO()
writer = _ip.write
_ip.write = w.write
code = """
a = 100
b = 200"""
try:
paste(code,'')
out = w.getvalue()
finally:
_ip.write = writer
nt.assert_equal(user_ns['a'], 100)
nt.assert_equal(user_ns['b'], 200)
nt.assert_equal(out, code+"\n## -- End pasted text --\n")

finally:
# Restore original hook
hooks.clipboard_get = original_clip

0 comments on commit 780b7c5

Please sign in to comment.