##// END OF EJS Templates
Merge pull request #1020 from takluyver/dollar-formatter...
Fernando Perez -
r5358:09c9952f merge
parent child Browse files
Show More
@@ -73,7 +73,8 b' from IPython.utils.pickleshare import PickleShareDB'
73 73 from IPython.utils.process import system, getoutput
74 74 from IPython.utils.strdispatch import StrDispatch
75 75 from IPython.utils.syspathcontext import prepended_to_syspath
76 from IPython.utils.text import num_ini_spaces, format_screen, LSString, SList
76 from IPython.utils.text import (num_ini_spaces, format_screen, LSString, SList,
77 DollarFormatter)
77 78 from IPython.utils.traitlets import (Integer, CBool, CaselessStrEnum, Enum,
78 79 List, Unicode, Instance, Type)
79 80 from IPython.utils.warn import warn, error, fatal
@@ -2571,7 +2572,7 b' class InteractiveShell(SingletonConfigurable, Magic):'
2571 2572 # Utilities
2572 2573 #-------------------------------------------------------------------------
2573 2574
2574 def var_expand(self,cmd,depth=0):
2575 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
2575 2576 """Expand python variables in a string.
2576 2577
2577 2578 The depth argument indicates how many frames above the caller should
@@ -2580,11 +2581,10 b' class InteractiveShell(SingletonConfigurable, Magic):'
2580 2581 The global namespace for expansion is always the user's interactive
2581 2582 namespace.
2582 2583 """
2583 res = ItplNS(cmd, self.user_ns, # globals
2584 # Skip our own frame in searching for locals:
2585 sys._getframe(depth+1).f_locals # locals
2586 )
2587 return py3compat.str_to_unicode(str(res), res.codec)
2584 ns = self.user_ns.copy()
2585 ns.update(sys._getframe(depth+1).f_locals)
2586 ns.pop('self', None)
2587 return formatter.format(cmd, **ns)
2588 2588
2589 2589 def mktempfile(self, data=None, prefix='ipython_edit_'):
2590 2590 """Make a new tempfile and return its filename.
@@ -44,9 +44,8 b' def test_columnize_long():'
44 44 out = text.columnize(items, displaywidth=size-1)
45 45 nt.assert_equals(out, '\n'.join(items+['']))
46 46
47 def test_eval_formatter():
48 f = text.EvalFormatter()
49 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
47 def eval_formatter_check(f):
48 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
50 49 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
51 50 nt.assert_equals(s, "12 3 hello")
52 51 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
@@ -58,12 +57,15 b' def test_eval_formatter():'
58 57 s = f.format("{stuff!r}", **ns)
59 58 nt.assert_equals(s, repr(ns['stuff']))
60 59
60 # Check with unicode:
61 s = f.format("{u}", **ns)
62 nt.assert_equals(s, ns['u'])
63 # This decodes in a platform dependent manner, but it shouldn't error out
64 s = f.format("{b}", **ns)
65
61 66 nt.assert_raises(NameError, f.format, '{dne}', **ns)
62 67
63
64 def test_eval_formatter_slicing():
65 f = text.EvalFormatter()
66 f.allow_slicing = True
68 def eval_formatter_slicing_check(f):
67 69 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
68 70 s = f.format(" {stuff.split()[:]} ", **ns)
69 71 nt.assert_equals(s, " ['hello', 'there'] ")
@@ -75,9 +77,7 b' def test_eval_formatter_slicing():'
75 77 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
76 78
77 79
78 def test_eval_formatter_no_slicing():
79 f = text.EvalFormatter()
80 f.allow_slicing = False
80 def eval_formatter_no_slicing_check(f):
81 81 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
82 82
83 83 s = f.format('{n:x} {pi**2:+f}', **ns)
@@ -85,3 +85,25 b' def test_eval_formatter_no_slicing():'
85 85
86 86 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
87 87
88 def test_eval_formatter():
89 f = text.EvalFormatter()
90 eval_formatter_check(f)
91 eval_formatter_no_slicing_check(f)
92
93 def test_full_eval_formatter():
94 f = text.FullEvalFormatter()
95 eval_formatter_check(f)
96 eval_formatter_slicing_check(f)
97
98 def test_dollar_formatter():
99 f = text.DollarFormatter()
100 eval_formatter_check(f)
101 eval_formatter_slicing_check(f)
102
103 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
104 s = f.format("$n", **ns)
105 nt.assert_equals(s, "12")
106 s = f.format("$n.real", **ns)
107 nt.assert_equals(s, "12")
108 s = f.format("$n/{stuff[:5]}", **ns)
109 nt.assert_equals(s, "12/hello")
@@ -25,6 +25,7 b' import textwrap'
25 25 from string import Formatter
26 26
27 27 from IPython.external.path import path
28 from IPython.testing.skipdoctest import skip_doctest_py3
28 29 from IPython.utils import py3compat
29 30 from IPython.utils.io import nlprint
30 31 from IPython.utils.data import flatten
@@ -563,33 +564,53 b' def wrap_paragraphs(text, ncols=80):'
563 564 return out_ps
564 565
565 566
566
567 567 class EvalFormatter(Formatter):
568 568 """A String Formatter that allows evaluation of simple expressions.
569
570 Any time a format key is not found in the kwargs,
571 it will be tried as an expression in the kwargs namespace.
572
569
570 Note that this version interprets a : as specifying a format string (as per
571 standard string formatting), so if slicing is required, you must explicitly
572 create a slice.
573
573 574 This is to be used in templating cases, such as the parallel batch
574 575 script templates, where simple arithmetic on arguments is useful.
575 576
576 577 Examples
577 578 --------
579
580 In [1]: f = EvalFormatter()
581 In [2]: f.format('{n//4}', n=8)
582 Out [2]: '2'
583
584 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
585 Out [3]: 'll'
586 """
587 def get_field(self, name, args, kwargs):
588 v = eval(name, kwargs)
589 return v, name
578 590
579 In [1]: f = EvalFormatter()
591 @skip_doctest_py3
592 class FullEvalFormatter(Formatter):
593 """A String Formatter that allows evaluation of simple expressions.
594
595 Any time a format key is not found in the kwargs,
596 it will be tried as an expression in the kwargs namespace.
597
598 Note that this version allows slicing using [1:2], so you cannot specify
599 a format string. Use :class:`EvalFormatter` to permit format strings.
600
601 Examples
602 --------
603
604 In [1]: f = FullEvalFormatter()
580 605 In [2]: f.format('{n//4}', n=8)
581 Out[2]: '2'
582
583 In [3]: f.format('{list(range(3))}')
584 Out[3]: '[0, 1, 2]'
606 Out[2]: u'2'
607
608 In [3]: f.format('{list(range(5))[2:4]}')
609 Out[3]: u'[2, 3]'
585 610
586 611 In [4]: f.format('{3*2}')
587 Out[4]: '6'
612 Out[4]: u'6'
588 613 """
589
590 # should we allow slicing by disabling the format_spec feature?
591 allow_slicing = True
592
593 614 # copied from Formatter._vformat with minor changes to allow eval
594 615 # and replace the format_spec code with slicing
595 616 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
@@ -606,12 +627,11 b' class EvalFormatter(Formatter):'
606 627 # if there's a field, output it
607 628 if field_name is not None:
608 629 # this is some markup, find the object and do
609 # the formatting
630 # the formatting
610 631
611 if self.allow_slicing and format_spec:
632 if format_spec:
612 633 # override format spec, to allow slicing:
613 634 field_name = ':'.join([field_name, format_spec])
614 format_spec = ''
615 635
616 636 # eval the contents of the field for the object
617 637 # to be formatted
@@ -620,14 +640,43 b' class EvalFormatter(Formatter):'
620 640 # do any conversion on the resulting object
621 641 obj = self.convert_field(obj, conversion)
622 642
623 # expand the format spec, if needed
624 format_spec = self._vformat(format_spec, args, kwargs,
625 used_args, recursion_depth-1)
626
627 643 # format the object and append to the result
628 result.append(self.format_field(obj, format_spec))
644 result.append(self.format_field(obj, ''))
645
646 return u''.join(py3compat.cast_unicode(s) for s in result)
629 647
630 return ''.join(result)
648 @skip_doctest_py3
649 class DollarFormatter(FullEvalFormatter):
650 """Formatter allowing Itpl style $foo replacement, for names and attribute
651 access only. Standard {foo} replacement also works, and allows full
652 evaluation of its arguments.
653
654 Examples
655 --------
656 In [1]: f = DollarFormatter()
657 In [2]: f.format('{n//4}', n=8)
658 Out[2]: u'2'
659
660 In [3]: f.format('23 * 76 is $result', result=23*76)
661 Out[3]: u'23 * 76 is 1748'
662
663 In [4]: f.format('$a or {b}', a=1, b=2)
664 Out[4]: u'1 or 2'
665 """
666 _dollar_pattern = re.compile("(.*)\$([\w\.]+)")
667 def parse(self, fmt_string):
668 for literal_txt, field_name, format_spec, conversion \
669 in Formatter.parse(self, fmt_string):
670
671 # Find $foo patterns in the literal text.
672 continue_from = 0
673 for m in self._dollar_pattern.finditer(literal_txt):
674 new_txt, new_field = m.group(1,2)
675 yield (new_txt, new_field, "", None)
676 continue_from = m.end()
677
678 # Re-yield the {foo} style pattern
679 yield (literal_txt[continue_from:], field_name, format_spec, conversion)
631 680
632 681
633 682 def columnize(items, separator=' ', displaywidth=80):
General Comments 0
You need to be logged in to leave comments. Login now