Skip to content
This repository

update EvalFormatter to allow arbitrary expressions #716

Merged
merged 1 commit into from over 2 years ago

2 participants

Min RK Fernando Perez
Min RK
Owner

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. If we don't want to support slicing with colons, then there is a simpler implementation overriding only Formatter.get_field().

Associated tests also added.

Min RK 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.
8f55395
Fernando Perez fperez merged commit 8f55395 into from August 21, 2011
Fernando Perez fperez closed this August 21, 2011
Fernando Perez
Owner

Looks perfect, merged. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Aug 20, 2011
Min RK 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.
8f55395
This page is out of date. Refresh to see the latest.
43  IPython/utils/tests/test_text.py
@@ -13,6 +13,7 @@
13 13
 #-----------------------------------------------------------------------------
14 14
 
15 15
 import os
  16
+import math
16 17
 
17 18
 import nose.tools as nt
18 19
 
@@ -42,3 +43,45 @@ def test_columnize_long():
42 43
     items = [l*size for l in 'abc']
43 44
     out = text.columnize(items, displaywidth=size-1)
44 45
     nt.assert_equals(out, '\n'.join(items+['']))
  46
+
  47
+def test_eval_formatter():
  48
+    f = text.EvalFormatter()
  49
+    ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
  50
+    s = f.format("{n} {n/4} {stuff.split()[0]}", **ns)
  51
+    nt.assert_equals(s, "12 3 hello")
  52
+    s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
  53
+    nt.assert_equals(s, "12 6 4 3 2 2 1")
  54
+    s = f.format('{[n//i for i in range(1,8)]}', **ns)
  55
+    nt.assert_equals(s, "[12, 6, 4, 3, 2, 2, 1]")
  56
+    s = f.format("{stuff!s}", **ns)
  57
+    nt.assert_equals(s, ns['stuff'])
  58
+    s = f.format("{stuff!r}", **ns)
  59
+    nt.assert_equals(s, repr(ns['stuff']))
  60
+    
  61
+    nt.assert_raises(NameError, f.format, '{dne}', **ns)
  62
+
  63
+
  64
+def test_eval_formatter_slicing():
  65
+    f = text.EvalFormatter()
  66
+    f.allow_slicing = True
  67
+    ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
  68
+    s = f.format(" {stuff.split()[:]} ", **ns)
  69
+    nt.assert_equals(s, " ['hello', 'there'] ")
  70
+    s = f.format(" {stuff.split()[::-1]} ", **ns)
  71
+    nt.assert_equals(s, " ['there', 'hello'] ")
  72
+    s = f.format("{stuff[::2]}", **ns)
  73
+    nt.assert_equals(s, ns['stuff'][::2])
  74
+    
  75
+    nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
  76
+    
  77
+
  78
+def test_eval_formatter_no_slicing():
  79
+    f = text.EvalFormatter()
  80
+    f.allow_slicing = False
  81
+    ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
  82
+    
  83
+    s = f.format('{n:x} {pi**2:+f}', **ns)
  84
+    nt.assert_equals(s, "c +9.869604")
  85
+    
  86
+    nt.assert_raises(SyntaxError, f.format, "{a[:]}")
  87
+
53  IPython/utils/text.py
@@ -597,18 +597,47 @@ class EvalFormatter(Formatter):
597 597
     Out[4]: '6'
598 598
     """
599 599
     
600  
-    def get_value(self, key, args, kwargs):
601  
-        if isinstance(key, (int, long)):
602  
-            return args[key]
603  
-        elif key in kwargs:
604  
-            return kwargs[key]
605  
-        else:
606  
-            # evaluate the expression using kwargs as namespace
607  
-            try:
608  
-                return eval(key, kwargs)
609  
-            except Exception:
610  
-                # classify all bad expressions as key errors
611  
-                raise KeyError(key)
  600
+    # should we allow slicing by disabling the format_spec feature?
  601
+    allow_slicing = True
  602
+    
  603
+    # copied from Formatter._vformat with minor changes to allow eval
  604
+    # and replace the format_spec code with slicing
  605
+    def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
  606
+        if recursion_depth < 0:
  607
+            raise ValueError('Max string recursion exceeded')
  608
+        result = []
  609
+        for literal_text, field_name, format_spec, conversion in \
  610
+                self.parse(format_string):
  611
+
  612
+            # output the literal text
  613
+            if literal_text:
  614
+                result.append(literal_text)
  615
+
  616
+            # if there's a field, output it
  617
+            if field_name is not None:
  618
+                # this is some markup, find the object and do
  619
+                #  the formatting
  620
+
  621
+                if self.allow_slicing and format_spec:
  622
+                    # override format spec, to allow slicing:
  623
+                    field_name = ':'.join([field_name, format_spec])
  624
+                    format_spec = ''
  625
+
  626
+                # eval the contents of the field for the object
  627
+                # to be formatted
  628
+                obj = eval(field_name, kwargs)
  629
+
  630
+                # do any conversion on the resulting object
  631
+                obj = self.convert_field(obj, conversion)
  632
+
  633
+                # expand the format spec, if needed
  634
+                format_spec = self._vformat(format_spec, args, kwargs,
  635
+                                            used_args, recursion_depth-1)
  636
+
  637
+                # format the object and append to the result
  638
+                result.append(self.format_field(obj, format_spec))
  639
+
  640
+        return ''.join(result)
612 641
 
613 642
 
614 643
 def columnize(items, separator='  ', displaywidth=80):
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.