Show More
@@ -73,7 +73,8 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 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 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 |
|
|
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 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 |
|
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 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 | |||
61 | nt.assert_raises(NameError, f.format, '{dne}', **ns) |
|
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) | |||
62 |
|
65 | |||
|
66 | nt.assert_raises(NameError, f.format, '{dne}', **ns) | |||
63 |
|
67 | |||
64 |
def |
|
68 | def eval_formatter_slicing_check(f): | |
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 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 |
|
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 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 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,12 +564,12 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 | create a slice. | |||
572 |
|
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. | |
@@ -580,16 +581,36 class EvalFormatter(Formatter): | |||||
580 | In [2]: f.format('{n//4}', n=8) |
|
581 | In [2]: f.format('{n//4}', n=8) | |
581 | Out[2]: '2' |
|
582 | Out [2]: '2' | |
582 |
|
583 | |||
583 | In [3]: f.format('{list(range(3))}') |
|
584 | In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello") | |
584 |
Out[3]: ' |
|
585 | Out [3]: 'll' | |
585 |
|
||||
586 | In [4]: f.format('{3*2}') |
|
|||
587 | Out[4]: '6' |
|
|||
588 | """ |
|
586 | """ | |
|
587 | def get_field(self, name, args, kwargs): | |||
|
588 | v = eval(name, kwargs) | |||
|
589 | return v, name | |||
|
590 | ||||
|
591 | @skip_doctest_py3 | |||
|
592 | class FullEvalFormatter(Formatter): | |||
|
593 | """A String Formatter that allows evaluation of simple expressions. | |||
589 |
|
594 | |||
590 | # should we allow slicing by disabling the format_spec feature? |
|
595 | Any time a format key is not found in the kwargs, | |
591 | allow_slicing = True |
|
596 | it will be tried as an expression in the kwargs namespace. | |
592 |
|
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() | |||
|
605 | In [2]: f.format('{n//4}', n=8) | |||
|
606 | Out[2]: u'2' | |||
|
607 | ||||
|
608 | In [3]: f.format('{list(range(5))[2:4]}') | |||
|
609 | Out[3]: u'[2, 3]' | |||
|
610 | ||||
|
611 | In [4]: f.format('{3*2}') | |||
|
612 | Out[4]: u'6' | |||
|
613 | """ | |||
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): | |
@@ -608,10 +629,9 class EvalFormatter(Formatter): | |||||
608 | # this is some markup, find the object and do |
|
629 | # this is some markup, find the object and do | |
609 |
# |
|
630 | # the formatting | |
610 |
|
631 | |||
611 |
if |
|
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 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, |
|
644 | result.append(self.format_field(obj, '')) | |
|
645 | ||||
|
646 | return u''.join(py3compat.cast_unicode(s) for s in result) | |||
|
647 | ||||
|
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. | |||
629 |
|
|
653 | ||
630 | return ''.join(result) |
|
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