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 |
|
|
|
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 |
|
|
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 |
|
|
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( |
|
|
584 |
Out[3]: |
|
|
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 |
# |
|
|
630 | # the formatting | |
|
610 | 631 | |
|
611 |
if |
|
|
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, |
|
|
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