##// 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 from IPython.utils.process import system, getoutput
73 from IPython.utils.process import system, getoutput
74 from IPython.utils.strdispatch import StrDispatch
74 from IPython.utils.strdispatch import StrDispatch
75 from IPython.utils.syspathcontext import prepended_to_syspath
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 from IPython.utils.traitlets import (Integer, CBool, CaselessStrEnum, Enum,
78 from IPython.utils.traitlets import (Integer, CBool, CaselessStrEnum, Enum,
78 List, Unicode, Instance, Type)
79 List, Unicode, Instance, Type)
79 from IPython.utils.warn import warn, error, fatal
80 from IPython.utils.warn import warn, error, fatal
@@ -2571,7 +2572,7 b' class InteractiveShell(SingletonConfigurable, Magic):'
2571 # Utilities
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 """Expand python variables in a string.
2576 """Expand python variables in a string.
2576
2577
2577 The depth argument indicates how many frames above the caller should
2578 The depth argument indicates how many frames above the caller should
@@ -2580,11 +2581,10 b' class InteractiveShell(SingletonConfigurable, Magic):'
2580 The global namespace for expansion is always the user's interactive
2581 The global namespace for expansion is always the user's interactive
2581 namespace.
2582 namespace.
2582 """
2583 """
2583 res = ItplNS(cmd, self.user_ns, # globals
2584 ns = self.user_ns.copy()
2584 # Skip our own frame in searching for locals:
2585 ns.update(sys._getframe(depth+1).f_locals)
2585 sys._getframe(depth+1).f_locals # locals
2586 ns.pop('self', None)
2586 )
2587 return formatter.format(cmd, **ns)
2587 return py3compat.str_to_unicode(str(res), res.codec)
2588
2588
2589 def mktempfile(self, data=None, prefix='ipython_edit_'):
2589 def mktempfile(self, data=None, prefix='ipython_edit_'):
2590 """Make a new tempfile and return its filename.
2590 """Make a new tempfile and return its filename.
@@ -44,9 +44,8 b' def test_columnize_long():'
44 out = text.columnize(items, displaywidth=size-1)
44 out = text.columnize(items, displaywidth=size-1)
45 nt.assert_equals(out, '\n'.join(items+['']))
45 nt.assert_equals(out, '\n'.join(items+['']))
46
46
47 def test_eval_formatter():
47 def eval_formatter_check(f):
48 f = text.EvalFormatter()
48 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
49 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
50 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
49 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
51 nt.assert_equals(s, "12 3 hello")
50 nt.assert_equals(s, "12 3 hello")
52 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
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 s = f.format("{stuff!r}", **ns)
57 s = f.format("{stuff!r}", **ns)
59 nt.assert_equals(s, repr(ns['stuff']))
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 nt.assert_raises(NameError, f.format, '{dne}', **ns)
66 nt.assert_raises(NameError, f.format, '{dne}', **ns)
62
67
63
68 def eval_formatter_slicing_check(f):
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)
69 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
68 s = f.format(" {stuff.split()[:]} ", **ns)
70 s = f.format(" {stuff.split()[:]} ", **ns)
69 nt.assert_equals(s, " ['hello', 'there'] ")
71 nt.assert_equals(s, " ['hello', 'there'] ")
@@ -75,9 +77,7 b' def test_eval_formatter_slicing():'
75 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
77 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
76
78
77
79
78 def test_eval_formatter_no_slicing():
80 def eval_formatter_no_slicing_check(f):
79 f = text.EvalFormatter()
80 f.allow_slicing = False
81 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
81 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
82
82
83 s = f.format('{n:x} {pi**2:+f}', **ns)
83 s = f.format('{n:x} {pi**2:+f}', **ns)
@@ -85,3 +85,25 b' def test_eval_formatter_no_slicing():'
85
85
86 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
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 from string import Formatter
25 from string import Formatter
26
26
27 from IPython.external.path import path
27 from IPython.external.path import path
28 from IPython.testing.skipdoctest import skip_doctest_py3
28 from IPython.utils import py3compat
29 from IPython.utils import py3compat
29 from IPython.utils.io import nlprint
30 from IPython.utils.io import nlprint
30 from IPython.utils.data import flatten
31 from IPython.utils.data import flatten
@@ -563,33 +564,53 b' def wrap_paragraphs(text, ncols=80):'
563 return out_ps
564 return out_ps
564
565
565
566
566
567 class EvalFormatter(Formatter):
567 class EvalFormatter(Formatter):
568 """A String Formatter that allows evaluation of simple expressions.
568 """A String Formatter that allows evaluation of simple expressions.
569
569
570 Any time a format key is not found in the kwargs,
570 Note that this version interprets a : as specifying a format string (as per
571 it will be tried as an expression in the kwargs namespace.
571 standard string formatting), so if slicing is required, you must explicitly
572
572 create a slice.
573
573 This is to be used in templating cases, such as the parallel batch
574 This is to be used in templating cases, such as the parallel batch
574 script templates, where simple arithmetic on arguments is useful.
575 script templates, where simple arithmetic on arguments is useful.
575
576
576 Examples
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 In [2]: f.format('{n//4}', n=8)
605 In [2]: f.format('{n//4}', n=8)
581 Out[2]: '2'
606 Out[2]: u'2'
582
607
583 In [3]: f.format('{list(range(3))}')
608 In [3]: f.format('{list(range(5))[2:4]}')
584 Out[3]: '[0, 1, 2]'
609 Out[3]: u'[2, 3]'
585
610
586 In [4]: f.format('{3*2}')
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 # copied from Formatter._vformat with minor changes to allow eval
614 # copied from Formatter._vformat with minor changes to allow eval
594 # and replace the format_spec code with slicing
615 # and replace the format_spec code with slicing
595 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
616 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
@@ -606,12 +627,11 b' class EvalFormatter(Formatter):'
606 # if there's a field, output it
627 # if there's a field, output it
607 if field_name is not None:
628 if field_name is not None:
608 # this is some markup, find the object and do
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 # override format spec, to allow slicing:
633 # override format spec, to allow slicing:
613 field_name = ':'.join([field_name, format_spec])
634 field_name = ':'.join([field_name, format_spec])
614 format_spec = ''
615
635
616 # eval the contents of the field for the object
636 # eval the contents of the field for the object
617 # to be formatted
637 # to be formatted
@@ -620,14 +640,43 b' class EvalFormatter(Formatter):'
620 # do any conversion on the resulting object
640 # do any conversion on the resulting object
621 obj = self.convert_field(obj, conversion)
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 # format the object and append to the result
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 def columnize(items, separator=' ', displaywidth=80):
682 def columnize(items, separator=' ', displaywidth=80):
General Comments 0
You need to be logged in to leave comments. Login now