diff --git a/IPython/utils/tests/test_text.py b/IPython/utils/tests/test_text.py index ba7f160..b39093c 100644 --- a/IPython/utils/tests/test_text.py +++ b/IPython/utils/tests/test_text.py @@ -44,8 +44,7 @@ def test_columnize_long(): out = text.columnize(items, displaywidth=size-1) nt.assert_equals(out, '\n'.join(items+[''])) -def test_eval_formatter(): - f = text.EvalFormatter() +def eval_formatter_check(f): ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) s = f.format("{n} {n//4} {stuff.split()[0]}", **ns) nt.assert_equals(s, "12 3 hello") @@ -60,10 +59,7 @@ def test_eval_formatter(): nt.assert_raises(NameError, f.format, '{dne}', **ns) - -def test_eval_formatter_slicing(): - f = text.EvalFormatter() - f.allow_slicing = True +def eval_formatter_slicing_check(f): ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) s = f.format(" {stuff.split()[:]} ", **ns) nt.assert_equals(s, " ['hello', 'there'] ") @@ -75,9 +71,7 @@ def test_eval_formatter_slicing(): nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns) -def test_eval_formatter_no_slicing(): - f = text.EvalFormatter() - f.allow_slicing = False +def eval_formatter_no_slicing_check(f): ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) s = f.format('{n:x} {pi**2:+f}', **ns) @@ -85,3 +79,25 @@ def test_eval_formatter_no_slicing(): nt.assert_raises(SyntaxError, f.format, "{a[:]}") +def test_eval_formatter(): + f = text.EvalFormatter() + eval_formatter_check(f) + eval_formatter_no_slicing_check(f) + +def test_full_eval_formatter(): + f = text.FullEvalFormatter() + eval_formatter_check(f) + eval_formatter_slicing_check(f) + +def test_dollar_formatter(): + f = text.DollarFormatter() + eval_formatter_check(f) + eval_formatter_slicing_check(f) + + ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) + s = f.format("$n", **ns) + nt.assert_equals(s, "12") + s = f.format("$n.real", **ns) + nt.assert_equals(s, "12") + s = f.format("$n/{stuff[:5]}", **ns) + nt.assert_equals(s, "12/hello") diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 01d3d8d..4fd3a5c 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -677,6 +677,38 @@ class FullEvalFormatter(Formatter): return ''.join(result) +class DollarFormatter(FullEvalFormatter): + """Formatter allowing Itpl style $foo replacement, for names and attribute + access only. Standard {foo} replacement also works, and allows full + evaluation of its arguments. + + Examples + -------- + In [1]: f = DollarFormatter() + In [2]: f.format('{n//4}', n=8) + Out[2]: '2' + + In [3]: f.format('23 * 76 is $result', result=23*76) + Out[3]: '23 * 76 is 1748' + + In [4]: f.format('$a or {b}', a=1, b=2) + Out[4]: '1 or 2' + """ + _dollar_pattern = re.compile("(.*)\$([\w\.]+)") + def parse(self, fmt_string): + for literal_txt, field_name, format_spec, conversion \ + in Formatter.parse(self, fmt_string): + + # Find $foo patterns in the literal text. + continue_from = 0 + for m in self._dollar_pattern.finditer(literal_txt): + new_txt, new_field = m.group(1,2) + yield (new_txt, new_field, "", "s") + continue_from = m.end() + + # Re-yield the {foo} style pattern + yield (literal_txt[continue_from:], field_name, format_spec, conversion) + def columnize(items, separator=' ', displaywidth=80): """ Transform a list of strings into a single string with columns.