From f5687fc7e0d0c5d373f8e6e26d7908716e7ec9fb 2011-11-20 17:23:23 From: Thomas Kluyver Date: 2011-11-20 17:23:23 Subject: [PATCH] Use DollarFormatter to fill in names in ! shell calls. Closes gh-822 --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index ce2ebc3..eea851c 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -73,7 +73,8 @@ from IPython.utils.pickleshare import PickleShareDB from IPython.utils.process import system, getoutput from IPython.utils.strdispatch import StrDispatch from IPython.utils.syspathcontext import prepended_to_syspath -from IPython.utils.text import num_ini_spaces, format_screen, LSString, SList +from IPython.utils.text import (num_ini_spaces, format_screen, LSString, SList, + DollarFormatter) from IPython.utils.traitlets import (Integer, CBool, CaselessStrEnum, Enum, List, Unicode, Instance, Type) from IPython.utils.warn import warn, error, fatal @@ -2571,7 +2572,7 @@ class InteractiveShell(SingletonConfigurable, Magic): # Utilities #------------------------------------------------------------------------- - def var_expand(self,cmd,depth=0): + def var_expand(self, cmd, depth=0, formatter=DollarFormatter()): """Expand python variables in a string. The depth argument indicates how many frames above the caller should @@ -2580,11 +2581,10 @@ class InteractiveShell(SingletonConfigurable, Magic): The global namespace for expansion is always the user's interactive namespace. """ - res = ItplNS(cmd, self.user_ns, # globals - # Skip our own frame in searching for locals: - sys._getframe(depth+1).f_locals # locals - ) - return py3compat.str_to_unicode(str(res), res.codec) + ns = self.user_ns.copy() + ns.update(sys._getframe(depth+1).f_locals) + ns.pop('self', None) + return formatter.format(cmd, **ns) def mktempfile(self, data=None, prefix='ipython_edit_'): """Make a new tempfile and return its filename. diff --git a/IPython/utils/tests/test_text.py b/IPython/utils/tests/test_text.py index b39093c..1d0b70a 100644 --- a/IPython/utils/tests/test_text.py +++ b/IPython/utils/tests/test_text.py @@ -45,7 +45,7 @@ def test_columnize_long(): nt.assert_equals(out, '\n'.join(items+[''])) def eval_formatter_check(f): - ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) + ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"café", b="café") s = f.format("{n} {n//4} {stuff.split()[0]}", **ns) nt.assert_equals(s, "12 3 hello") s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns) @@ -57,6 +57,12 @@ def eval_formatter_check(f): s = f.format("{stuff!r}", **ns) nt.assert_equals(s, repr(ns['stuff'])) + # Check with unicode: + s = f.format("{u}", **ns) + nt.assert_equals(s, ns['u']) + # This decodes in a platform dependent manner, but it shouldn't error out + s = f.format("{b}", **ns) + nt.assert_raises(NameError, f.format, '{dne}', **ns) def eval_formatter_slicing_check(f): diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 4fd3a5c..1ad9e60 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -25,6 +25,7 @@ import textwrap from string import Formatter from IPython.external.path import path +from IPython.testing.skipdoctest import skip_doctest_py3 from IPython.utils import py3compat from IPython.utils.io import nlprint from IPython.utils.data import flatten @@ -621,6 +622,7 @@ class EvalFormatter(Formatter): v = eval(name, kwargs) return v, name +@skip_doctest_py3 class FullEvalFormatter(Formatter): """A String Formatter that allows evaluation of simple expressions. @@ -635,13 +637,13 @@ class FullEvalFormatter(Formatter): In [1]: f = FullEvalFormatter() In [2]: f.format('{n//4}', n=8) - Out[2]: '2' + Out[2]: u'2' In [3]: f.format('{list(range(5))[2:4]}') - Out[3]: '[2, 3]' + Out[3]: u'[2, 3]' In [4]: f.format('{3*2}') - Out[4]: '6' + Out[4]: u'6' """ # copied from Formatter._vformat with minor changes to allow eval # and replace the format_spec code with slicing @@ -675,8 +677,9 @@ class FullEvalFormatter(Formatter): # format the object and append to the result result.append(self.format_field(obj, '')) - return ''.join(result) + return u''.join(py3compat.cast_unicode(s) for s in result) +@skip_doctest_py3 class DollarFormatter(FullEvalFormatter): """Formatter allowing Itpl style $foo replacement, for names and attribute access only. Standard {foo} replacement also works, and allows full @@ -686,13 +689,13 @@ class DollarFormatter(FullEvalFormatter): -------- In [1]: f = DollarFormatter() In [2]: f.format('{n//4}', n=8) - Out[2]: '2' + Out[2]: u'2' In [3]: f.format('23 * 76 is $result', result=23*76) - Out[3]: '23 * 76 is 1748' + Out[3]: u'23 * 76 is 1748' In [4]: f.format('$a or {b}', a=1, b=2) - Out[4]: '1 or 2' + Out[4]: u'1 or 2' """ _dollar_pattern = re.compile("(.*)\$([\w\.]+)") def parse(self, fmt_string): @@ -703,7 +706,7 @@ class DollarFormatter(FullEvalFormatter): continue_from = 0 for m in self._dollar_pattern.finditer(literal_txt): new_txt, new_field = m.group(1,2) - yield (new_txt, new_field, "", "s") + yield (new_txt, new_field, "", None) continue_from = m.end() # Re-yield the {foo} style pattern