Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

update EvalFormatter to allow arbitrary expressions

EvalFormatter should now support arbitrary Python expressions, including
slicing.  Since slicing conflicts with the format_spec part of fancy formatting, there is a switch to restore format_spec behavior at the expense of slicing.

Associated tests added as well.
  • Loading branch information...
commit 8f5539570ceb3f69a336c6ba4eca1a568aaebc41 1 parent adba85c
@minrk authored
Showing with 84 additions and 12 deletions.
  1. +43 −0 IPython/utils/tests/test_text.py
  2. +41 −12 IPython/utils/text.py
View
43 IPython/utils/tests/test_text.py
@@ -13,6 +13,7 @@
#-----------------------------------------------------------------------------
import os
+import math
import nose.tools as nt
@@ -42,3 +43,45 @@ def test_columnize_long():
items = [l*size for l in 'abc']
out = text.columnize(items, displaywidth=size-1)
nt.assert_equals(out, '\n'.join(items+['']))
+
+def test_eval_formatter():
+ f = text.EvalFormatter()
+ ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
+ 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)
+ nt.assert_equals(s, "12 6 4 3 2 2 1")
+ s = f.format('{[n//i for i in range(1,8)]}', **ns)
+ nt.assert_equals(s, "[12, 6, 4, 3, 2, 2, 1]")
+ s = f.format("{stuff!s}", **ns)
+ nt.assert_equals(s, ns['stuff'])
+ s = f.format("{stuff!r}", **ns)
+ nt.assert_equals(s, repr(ns['stuff']))
+
+ nt.assert_raises(NameError, f.format, '{dne}', **ns)
+
+
+def test_eval_formatter_slicing():
+ f = text.EvalFormatter()
+ f.allow_slicing = True
+ ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
+ s = f.format(" {stuff.split()[:]} ", **ns)
+ nt.assert_equals(s, " ['hello', 'there'] ")
+ s = f.format(" {stuff.split()[::-1]} ", **ns)
+ nt.assert_equals(s, " ['there', 'hello'] ")
+ s = f.format("{stuff[::2]}", **ns)
+ nt.assert_equals(s, ns['stuff'][::2])
+
+ nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
+
+
+def test_eval_formatter_no_slicing():
+ f = text.EvalFormatter()
+ f.allow_slicing = False
+ ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
+
+ s = f.format('{n:x} {pi**2:+f}', **ns)
+ nt.assert_equals(s, "c +9.869604")
+
+ nt.assert_raises(SyntaxError, f.format, "{a[:]}")
+
View
53 IPython/utils/text.py
@@ -597,18 +597,47 @@ class EvalFormatter(Formatter):
Out[4]: '6'
"""
- def get_value(self, key, args, kwargs):
- if isinstance(key, (int, long)):
- return args[key]
- elif key in kwargs:
- return kwargs[key]
- else:
- # evaluate the expression using kwargs as namespace
- try:
- return eval(key, kwargs)
- except Exception:
- # classify all bad expressions as key errors
- raise KeyError(key)
+ # should we allow slicing by disabling the format_spec feature?
+ allow_slicing = True
+
+ # copied from Formatter._vformat with minor changes to allow eval
+ # and replace the format_spec code with slicing
+ def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
+ if recursion_depth < 0:
+ raise ValueError('Max string recursion exceeded')
+ result = []
+ for literal_text, field_name, format_spec, conversion in \
+ self.parse(format_string):
+
+ # output the literal text
+ if literal_text:
+ result.append(literal_text)
+
+ # if there's a field, output it
+ if field_name is not None:
+ # this is some markup, find the object and do
+ # the formatting
+
+ if self.allow_slicing and format_spec:
+ # override format spec, to allow slicing:
+ field_name = ':'.join([field_name, format_spec])
+ format_spec = ''
+
+ # eval the contents of the field for the object
+ # to be formatted
+ obj = eval(field_name, kwargs)
+
+ # do any conversion on the resulting object
+ obj = self.convert_field(obj, conversion)
+
+ # expand the format spec, if needed
+ format_spec = self._vformat(format_spec, args, kwargs,
+ used_args, recursion_depth-1)
+
+ # format the object and append to the result
+ result.append(self.format_field(obj, format_spec))
+
+ return ''.join(result)
def columnize(items, separator=' ', displaywidth=80):
Please sign in to comment.
Something went wrong with that request. Please try again.