##// END OF EJS Templates
More input transformers
Thomas Kluyver -
Show More
@@ -1,112 +1,175 b''
1 import abc
1 import abc
2 import re
2 import re
3
3
4 from IPython.core.splitinput import split_user_input, LineInfo
4 from IPython.core.splitinput import split_user_input, LineInfo
5 from IPython.core.inputsplitter import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
5 from IPython.core.inputsplitter import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
6 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
6 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
7 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN)
7 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN)
8 from IPython.core.inputsplitter import EscapedTransformer, _make_help_call, has_comment
8 from IPython.core.inputsplitter import EscapedTransformer, _make_help_call, has_comment
9
9
10 class InputTransformer(object):
10 class InputTransformer(object):
11 __metaclass__ = abc.ABCMeta
11 __metaclass__ = abc.ABCMeta
12
12
13 @abc.abstractmethod
13 @abc.abstractmethod
14 def push(self, line):
14 def push(self, line):
15 pass
15 pass
16
16
17 @abc.abstractmethod
17 @abc.abstractmethod
18 def reset(self):
18 def reset(self):
19 pass
19 pass
20
21 look_in_string = False
20
22
21 class StatelessInputTransformer(InputTransformer):
23 class StatelessInputTransformer(InputTransformer):
22 """Decorator for a stateless input transformer implemented as a function."""
24 """Decorator for a stateless input transformer implemented as a function."""
23 def __init__(self, func):
25 def __init__(self, func):
24 self.func = func
26 self.func = func
25
27
26 def push(self, line):
28 def push(self, line):
27 return self.func(line)
29 return self.func(line)
28
30
29 def reset(self):
31 def reset(self):
30 pass
32 pass
31
33
32 class CoroutineInputTransformer(InputTransformer):
34 class CoroutineInputTransformer(InputTransformer):
33 """Decorator for an input transformer implemented as a coroutine."""
35 """Decorator for an input transformer implemented as a coroutine."""
34 def __init__(self, coro):
36 def __init__(self, coro):
35 # Prime it
37 # Prime it
36 self.coro = coro()
38 self.coro = coro()
37 next(self.coro)
39 next(self.coro)
38
40
39 def push(self, line):
41 def push(self, line):
40 return self.coro.send(line)
42 return self.coro.send(line)
41
43
42 def reset(self):
44 def reset(self):
43 self.coro.send(None)
45 self.coro.send(None)
44
46
45 @CoroutineInputTransformer
47 @CoroutineInputTransformer
46 def escaped_transformer():
48 def escaped_transformer():
47 et = EscapedTransformer()
49 et = EscapedTransformer()
48 line = ''
50 line = ''
49 while True:
51 while True:
50 line = (yield line)
52 line = (yield line)
51 if not line or line.isspace():
53 if not line or line.isspace():
52 continue
54 continue
53 lineinf = LineInfo(line)
55 lineinf = LineInfo(line)
54 if lineinf.esc not in et.tr:
56 if lineinf.esc not in et.tr:
55 continue
57 continue
56
58
57 parts = []
59 parts = []
58 while line is not None:
60 while line is not None:
59 parts.append(line.rstrip('\\'))
61 parts.append(line.rstrip('\\'))
60 if not line.endswith('\\'):
62 if not line.endswith('\\'):
61 break
63 break
62 line = (yield None)
64 line = (yield None)
63
65
64 # Output
66 # Output
65 lineinf = LineInfo(' '.join(parts))
67 lineinf = LineInfo(' '.join(parts))
66 line = et.tr[lineinf.esc](lineinf)
68 line = et.tr[lineinf.esc](lineinf)
67
69
68 _initial_space_re = re.compile(r'\s*')
70 _initial_space_re = re.compile(r'\s*')
69
71
70 _help_end_re = re.compile(r"""(%{0,2}
72 _help_end_re = re.compile(r"""(%{0,2}
71 [a-zA-Z_*][\w*]* # Variable name
73 [a-zA-Z_*][\w*]* # Variable name
72 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
74 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
73 )
75 )
74 (\?\??)$ # ? or ??""",
76 (\?\??)$ # ? or ??""",
75 re.VERBOSE)
77 re.VERBOSE)
76
78
77 @StatelessInputTransformer
79 @StatelessInputTransformer
78 def transform_help_end(line):
80 def transform_help_end(line):
79 """Translate lines with ?/?? at the end"""
81 """Translate lines with ?/?? at the end"""
80 m = _help_end_re.search(line)
82 m = _help_end_re.search(line)
81 if m is None or has_comment(line):
83 if m is None or has_comment(line):
82 return line
84 return line
83 target = m.group(1)
85 target = m.group(1)
84 esc = m.group(3)
86 esc = m.group(3)
85 lspace = _initial_space_re.match(line).group(0)
87 lspace = _initial_space_re.match(line).group(0)
86
88
87 # If we're mid-command, put it back on the next prompt for the user.
89 # If we're mid-command, put it back on the next prompt for the user.
88 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
90 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
89
91
90 return _make_help_call(target, esc, lspace, next_input)
92 return _make_help_call(target, esc, lspace, next_input)
91
93
92
94
93 @CoroutineInputTransformer
95 @CoroutineInputTransformer
94 def cellmagic():
96 def cellmagic():
95 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
97 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
96 line = ''
98 line = ''
97 while True:
99 while True:
98 line = (yield line)
100 line = (yield line)
99 if not line.startswith(ESC_MAGIC2):
101 if (not line) or (not line.startswith(ESC_MAGIC2)):
100 continue
102 continue
101
103
102 first = line
104 first = line
103 body = []
105 body = []
104 line = (yield None)
106 line = (yield None)
105 while (line is not None) and (line.strip() != ''):
107 while (line is not None) and (line.strip() != ''):
106 body.append(line)
108 body.append(line)
107 line = (yield None)
109 line = (yield None)
108
110
109 # Output
111 # Output
110 magic_name, _, first = first.partition(' ')
112 magic_name, _, first = first.partition(' ')
111 magic_name = magic_name.lstrip(ESC_MAGIC2)
113 magic_name = magic_name.lstrip(ESC_MAGIC2)
112 line = tpl % (magic_name, first, '\n'.join(body))
114 line = tpl % (magic_name, first, '\n'.join(body))
115
116 def _strip_prompts(prompt1_re, prompt2_re):
117 """Remove matching input prompts from a block of input."""
118 line = ''
119 while True:
120 line = (yield line)
121
122 if line is None:
123 continue
124
125 m = prompt1_re.match(line)
126 if m:
127 while m:
128 line = (yield line[len(m.group(0)):])
129 if line is None:
130 break
131 m = prompt2_re.match(line)
132 else:
133 # Prompts not in input - wait for reset
134 while line is not None:
135 line = (yield line)
136
137 @CoroutineInputTransformer
138 def classic_prompt():
139 prompt1_re = re.compile(r'^(>>> )')
140 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
141 return _strip_prompts(prompt1_re, prompt2_re)
142
143 classic_prompt.look_in_string = True
144
145 @CoroutineInputTransformer
146 def ipy_prompt():
147 prompt1_re = re.compile(r'^In \[\d+\]: ')
148 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
149 return _strip_prompts(prompt1_re, prompt2_re)
150
151 ipy_prompt.look_in_string = True
152
153 @CoroutineInputTransformer
154 def leading_indent():
155 space_re = re.compile(r'^[ \t]+')
156 line = ''
157 while True:
158 line = (yield line)
159
160 if line is None:
161 continue
162
163 m = space_re.match(line)
164 if m:
165 space = m.group(0)
166 while line is not None:
167 if line.startswith(space):
168 line = line[len(space):]
169 line = (yield line)
170 else:
171 # No leading spaces - wait for reset
172 while line is not None:
173 line = (yield line)
174
175 leading_indent.look_in_string = True
@@ -1,37 +1,68 b''
1 import unittest
1 import unittest
2 import nose.tools as nt
2 import nose.tools as nt
3
3
4 from IPython.testing import tools as tt
4 from IPython.testing import tools as tt
5 from IPython.utils import py3compat
5 from IPython.utils import py3compat
6
6
7 from IPython.core import inputtransformer
7 from IPython.core import inputtransformer
8 from IPython.core.tests.test_inputsplitter import syntax
8 from IPython.core.tests.test_inputsplitter import syntax
9
9
10 def wrap_transform(transformer):
10 def wrap_transform(transformer):
11 def transform(inp):
11 def transform(inp):
12 results = []
12 for line in inp:
13 for line in inp:
13 res = transformer.push(line)
14 res = transformer.push(line)
14 if res is not None:
15 if res is not None:
15 return res
16 results.append(res)
16 return transformer.push(None)
17 transformer.reset()
18 return results
17
19
18 return transform
20 return transform
19
21
20 cellmagic_tests = [
22 cellmagic_tests = [
21 (['%%foo a'], "get_ipython().run_cell_magic('foo', 'a', '')"),
23 (['%%foo a', None], ["get_ipython().run_cell_magic('foo', 'a', '')"]),
22 (['%%bar 123', 'hello', ''], "get_ipython().run_cell_magic('bar', '123', 'hello')"),
24 (['%%bar 123', 'hello', ''], ["get_ipython().run_cell_magic('bar', '123', 'hello')"]),
23 ]
25 ]
24
26
25 def test_transform_cellmagic():
27 def test_transform_cellmagic():
26 tt.check_pairs(wrap_transform(inputtransformer.cellmagic), cellmagic_tests)
28 tt.check_pairs(wrap_transform(inputtransformer.cellmagic), cellmagic_tests)
27
29
28 esctransform_tests = [(i, py3compat.u_format(o)) for i,o in [
30 esctransform_tests = [(i, [py3compat.u_format(ol) for ol in o]) for i,o in [
29 (['%pdef zip'], "get_ipython().magic({u}'pdef zip')"),
31 (['%pdef zip'], ["get_ipython().magic({u}'pdef zip')"]),
30 (['%abc def \\', 'ghi'], "get_ipython().magic({u}'abc def ghi')"),
32 (['%abc def \\', 'ghi'], ["get_ipython().magic({u}'abc def ghi')"]),
33 (['%abc def \\', 'ghi\\', None], ["get_ipython().magic({u}'abc def ghi')"]),
31 ]]
34 ]]
32
35
33 def test_transform_escaped():
36 def test_transform_escaped():
34 tt.check_pairs(wrap_transform(inputtransformer.escaped_transformer), esctransform_tests)
37 tt.check_pairs(wrap_transform(inputtransformer.escaped_transformer), esctransform_tests)
35
38
36 def endhelp_test():
39 def endhelp_test():
37 tt.check_pairs(inputtransformer.transform_help_end.push, syntax['end_help'])
40 tt.check_pairs(inputtransformer.transform_help_end.push, syntax['end_help'])
41
42 classic_prompt_tests = [
43 (['>>> a=1'], ['a=1']),
44 (['>>> a="""','... 123"""'], ['a="""', '123"""']),
45 (['a="""','... 123"""'], ['a="""', '... 123"""']),
46 ]
47
48 def test_classic_prompt():
49 tt.check_pairs(wrap_transform(inputtransformer.classic_prompt), classic_prompt_tests)
50
51 ipy_prompt_tests = [
52 (['In [1]: a=1'], ['a=1']),
53 (['In [2]: a="""',' ...: 123"""'], ['a="""', '123"""']),
54 (['a="""',' ...: 123"""'], ['a="""', ' ...: 123"""']),
55 ]
56
57 def test_ipy_prompt():
58 tt.check_pairs(wrap_transform(inputtransformer.ipy_prompt), ipy_prompt_tests)
59
60 leading_indent_tests = [
61 ([' print "hi"'], ['print "hi"']),
62 ([' for a in range(5):', ' a*2'], ['for a in range(5):', ' a*2']),
63 ([' a="""',' 123"""'], ['a="""', '123"""']),
64 (['a="""',' 123"""'], ['a="""', ' 123"""']),
65 ]
66
67 def test_leading_indent():
68 tt.check_pairs(wrap_transform(inputtransformer.leading_indent), leading_indent_tests)
General Comments 0
You need to be logged in to leave comments. Login now