##// END OF EJS Templates
Transformers for assignment from %magic and \!system calls
Thomas Kluyver -
Show More
@@ -1,175 +1,211 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
20
21 look_in_string = False
21 look_in_string = False
22
22
23 class StatelessInputTransformer(InputTransformer):
23 class StatelessInputTransformer(InputTransformer):
24 """Decorator for a stateless input transformer implemented as a function."""
24 """Decorator for a stateless input transformer implemented as a function."""
25 def __init__(self, func):
25 def __init__(self, func):
26 self.func = func
26 self.func = func
27
27
28 def push(self, line):
28 def push(self, line):
29 return self.func(line)
29 return self.func(line)
30
30
31 def reset(self):
31 def reset(self):
32 pass
32 pass
33
33
34 class CoroutineInputTransformer(InputTransformer):
34 class CoroutineInputTransformer(InputTransformer):
35 """Decorator for an input transformer implemented as a coroutine."""
35 """Decorator for an input transformer implemented as a coroutine."""
36 def __init__(self, coro):
36 def __init__(self, coro):
37 # Prime it
37 # Prime it
38 self.coro = coro()
38 self.coro = coro()
39 next(self.coro)
39 next(self.coro)
40
40
41 def push(self, line):
41 def push(self, line):
42 return self.coro.send(line)
42 return self.coro.send(line)
43
43
44 def reset(self):
44 def reset(self):
45 self.coro.send(None)
45 self.coro.send(None)
46
46
47 @CoroutineInputTransformer
47 @CoroutineInputTransformer
48 def escaped_transformer():
48 def escaped_transformer():
49 et = EscapedTransformer()
49 et = EscapedTransformer()
50 line = ''
50 line = ''
51 while True:
51 while True:
52 line = (yield line)
52 line = (yield line)
53 if not line or line.isspace():
53 if not line or line.isspace():
54 continue
54 continue
55 lineinf = LineInfo(line)
55 lineinf = LineInfo(line)
56 if lineinf.esc not in et.tr:
56 if lineinf.esc not in et.tr:
57 continue
57 continue
58
58
59 parts = []
59 parts = []
60 while line is not None:
60 while line is not None:
61 parts.append(line.rstrip('\\'))
61 parts.append(line.rstrip('\\'))
62 if not line.endswith('\\'):
62 if not line.endswith('\\'):
63 break
63 break
64 line = (yield None)
64 line = (yield None)
65
65
66 # Output
66 # Output
67 lineinf = LineInfo(' '.join(parts))
67 lineinf = LineInfo(' '.join(parts))
68 line = et.tr[lineinf.esc](lineinf)
68 line = et.tr[lineinf.esc](lineinf)
69
69
70 _initial_space_re = re.compile(r'\s*')
70 _initial_space_re = re.compile(r'\s*')
71
71
72 _help_end_re = re.compile(r"""(%{0,2}
72 _help_end_re = re.compile(r"""(%{0,2}
73 [a-zA-Z_*][\w*]* # Variable name
73 [a-zA-Z_*][\w*]* # Variable name
74 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
74 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
75 )
75 )
76 (\?\??)$ # ? or ??""",
76 (\?\??)$ # ? or ??""",
77 re.VERBOSE)
77 re.VERBOSE)
78
78
79 @StatelessInputTransformer
79 @StatelessInputTransformer
80 def transform_help_end(line):
80 def transform_help_end(line):
81 """Translate lines with ?/?? at the end"""
81 """Translate lines with ?/?? at the end"""
82 m = _help_end_re.search(line)
82 m = _help_end_re.search(line)
83 if m is None or has_comment(line):
83 if m is None or has_comment(line):
84 return line
84 return line
85 target = m.group(1)
85 target = m.group(1)
86 esc = m.group(3)
86 esc = m.group(3)
87 lspace = _initial_space_re.match(line).group(0)
87 lspace = _initial_space_re.match(line).group(0)
88
88
89 # 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.
90 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
91
91
92 return _make_help_call(target, esc, lspace, next_input)
92 return _make_help_call(target, esc, lspace, next_input)
93
93
94
94
95 @CoroutineInputTransformer
95 @CoroutineInputTransformer
96 def cellmagic():
96 def cellmagic():
97 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
97 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
98 line = ''
98 line = ''
99 while True:
99 while True:
100 line = (yield line)
100 line = (yield line)
101 if (not line) or (not line.startswith(ESC_MAGIC2)):
101 if (not line) or (not line.startswith(ESC_MAGIC2)):
102 continue
102 continue
103
103
104 first = line
104 first = line
105 body = []
105 body = []
106 line = (yield None)
106 line = (yield None)
107 while (line is not None) and (line.strip() != ''):
107 while (line is not None) and (line.strip() != ''):
108 body.append(line)
108 body.append(line)
109 line = (yield None)
109 line = (yield None)
110
110
111 # Output
111 # Output
112 magic_name, _, first = first.partition(' ')
112 magic_name, _, first = first.partition(' ')
113 magic_name = magic_name.lstrip(ESC_MAGIC2)
113 magic_name = magic_name.lstrip(ESC_MAGIC2)
114 line = tpl % (magic_name, first, '\n'.join(body))
114 line = tpl % (magic_name, first, '\n'.join(body))
115
115
116 def _strip_prompts(prompt1_re, prompt2_re):
116 def _strip_prompts(prompt1_re, prompt2_re):
117 """Remove matching input prompts from a block of input."""
117 """Remove matching input prompts from a block of input."""
118 line = ''
118 line = ''
119 while True:
119 while True:
120 line = (yield line)
120 line = (yield line)
121
121
122 if line is None:
122 if line is None:
123 continue
123 continue
124
124
125 m = prompt1_re.match(line)
125 m = prompt1_re.match(line)
126 if m:
126 if m:
127 while m:
127 while m:
128 line = (yield line[len(m.group(0)):])
128 line = (yield line[len(m.group(0)):])
129 if line is None:
129 if line is None:
130 break
130 break
131 m = prompt2_re.match(line)
131 m = prompt2_re.match(line)
132 else:
132 else:
133 # Prompts not in input - wait for reset
133 # Prompts not in input - wait for reset
134 while line is not None:
134 while line is not None:
135 line = (yield line)
135 line = (yield line)
136
136
137 @CoroutineInputTransformer
137 @CoroutineInputTransformer
138 def classic_prompt():
138 def classic_prompt():
139 prompt1_re = re.compile(r'^(>>> )')
139 prompt1_re = re.compile(r'^(>>> )')
140 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
140 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
141 return _strip_prompts(prompt1_re, prompt2_re)
141 return _strip_prompts(prompt1_re, prompt2_re)
142
142
143 classic_prompt.look_in_string = True
143 classic_prompt.look_in_string = True
144
144
145 @CoroutineInputTransformer
145 @CoroutineInputTransformer
146 def ipy_prompt():
146 def ipy_prompt():
147 prompt1_re = re.compile(r'^In \[\d+\]: ')
147 prompt1_re = re.compile(r'^In \[\d+\]: ')
148 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
148 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
149 return _strip_prompts(prompt1_re, prompt2_re)
149 return _strip_prompts(prompt1_re, prompt2_re)
150
150
151 ipy_prompt.look_in_string = True
151 ipy_prompt.look_in_string = True
152
152
153 @CoroutineInputTransformer
153 @CoroutineInputTransformer
154 def leading_indent():
154 def leading_indent():
155 space_re = re.compile(r'^[ \t]+')
155 space_re = re.compile(r'^[ \t]+')
156 line = ''
156 line = ''
157 while True:
157 while True:
158 line = (yield line)
158 line = (yield line)
159
159
160 if line is None:
160 if line is None:
161 continue
161 continue
162
162
163 m = space_re.match(line)
163 m = space_re.match(line)
164 if m:
164 if m:
165 space = m.group(0)
165 space = m.group(0)
166 while line is not None:
166 while line is not None:
167 if line.startswith(space):
167 if line.startswith(space):
168 line = line[len(space):]
168 line = line[len(space):]
169 line = (yield line)
169 line = (yield line)
170 else:
170 else:
171 # No leading spaces - wait for reset
171 # No leading spaces - wait for reset
172 while line is not None:
172 while line is not None:
173 line = (yield line)
173 line = (yield line)
174
174
175 leading_indent.look_in_string = True
175 leading_indent.look_in_string = True
176
177 def _special_assignment(assignment_re, template):
178 line = ''
179 while True:
180 line = (yield line)
181 if not line or line.isspace():
182 continue
183
184 m = assignment_re.match(line)
185 if not m:
186 continue
187
188 parts = []
189 while line is not None:
190 parts.append(line.rstrip('\\'))
191 if not line.endswith('\\'):
192 break
193 line = (yield None)
194
195 # Output
196 whole = assignment_re.match(' '.join(parts))
197 line = template % (whole.group('lhs'), whole.group('cmd'))
198
199 @CoroutineInputTransformer
200 def assign_from_system():
201 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
202 r'\s*=\s*!\s*(?P<cmd>.*)')
203 template = '%s = get_ipython().getoutput(%r)'
204 return _special_assignment(assignment_re, template)
205
206 @CoroutineInputTransformer
207 def assign_from_magic():
208 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
209 r'\s*=\s*%\s*(?P<cmd>.*)')
210 template = '%s = get_ipython().magic(%r)'
211 return _special_assignment(assignment_re, template)
@@ -1,68 +1,84 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 results = []
13 for line in inp:
13 for line in inp:
14 res = transformer.push(line)
14 res = transformer.push(line)
15 if res is not None:
15 if res is not None:
16 results.append(res)
16 results.append(res)
17 transformer.reset()
17 transformer.reset()
18 return results
18 return results
19
19
20 return transform
20 return transform
21
21
22 cellmagic_tests = [
22 cellmagic_tests = [
23 (['%%foo a', None], ["get_ipython().run_cell_magic('foo', 'a', '')"]),
23 (['%%foo a', None], ["get_ipython().run_cell_magic('foo', 'a', '')"]),
24 (['%%bar 123', 'hello', ''], ["get_ipython().run_cell_magic('bar', '123', 'hello')"]),
24 (['%%bar 123', 'hello', ''], ["get_ipython().run_cell_magic('bar', '123', 'hello')"]),
25 ]
25 ]
26
26
27 def test_transform_cellmagic():
27 def test_transform_cellmagic():
28 tt.check_pairs(wrap_transform(inputtransformer.cellmagic), cellmagic_tests)
28 tt.check_pairs(wrap_transform(inputtransformer.cellmagic), cellmagic_tests)
29
29
30 esctransform_tests = [(i, [py3compat.u_format(ol) for ol in o]) for i,o in [
30 esctransform_tests = [(i, [py3compat.u_format(ol) for ol in o]) for i,o in [
31 (['%pdef zip'], ["get_ipython().magic({u}'pdef zip')"]),
31 (['%pdef zip'], ["get_ipython().magic({u}'pdef zip')"]),
32 (['%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')"]),
33 (['%abc def \\', 'ghi\\', None], ["get_ipython().magic({u}'abc def ghi')"]),
34 ]]
34 ]]
35
35
36 def test_transform_escaped():
36 def test_transform_escaped():
37 tt.check_pairs(wrap_transform(inputtransformer.escaped_transformer), esctransform_tests)
37 tt.check_pairs(wrap_transform(inputtransformer.escaped_transformer), esctransform_tests)
38
38
39 def endhelp_test():
39 def endhelp_test():
40 tt.check_pairs(inputtransformer.transform_help_end.push, syntax['end_help'])
40 tt.check_pairs(inputtransformer.transform_help_end.push, syntax['end_help'])
41
41
42 classic_prompt_tests = [
42 classic_prompt_tests = [
43 (['>>> a=1'], ['a=1']),
43 (['>>> a=1'], ['a=1']),
44 (['>>> a="""','... 123"""'], ['a="""', '123"""']),
44 (['>>> a="""','... 123"""'], ['a="""', '123"""']),
45 (['a="""','... 123"""'], ['a="""', '... 123"""']),
45 (['a="""','... 123"""'], ['a="""', '... 123"""']),
46 ]
46 ]
47
47
48 def test_classic_prompt():
48 def test_classic_prompt():
49 tt.check_pairs(wrap_transform(inputtransformer.classic_prompt), classic_prompt_tests)
49 tt.check_pairs(wrap_transform(inputtransformer.classic_prompt), classic_prompt_tests)
50
50
51 ipy_prompt_tests = [
51 ipy_prompt_tests = [
52 (['In [1]: a=1'], ['a=1']),
52 (['In [1]: a=1'], ['a=1']),
53 (['In [2]: a="""',' ...: 123"""'], ['a="""', '123"""']),
53 (['In [2]: a="""',' ...: 123"""'], ['a="""', '123"""']),
54 (['a="""',' ...: 123"""'], ['a="""', ' ...: 123"""']),
54 (['a="""',' ...: 123"""'], ['a="""', ' ...: 123"""']),
55 ]
55 ]
56
56
57 def test_ipy_prompt():
57 def test_ipy_prompt():
58 tt.check_pairs(wrap_transform(inputtransformer.ipy_prompt), ipy_prompt_tests)
58 tt.check_pairs(wrap_transform(inputtransformer.ipy_prompt), ipy_prompt_tests)
59
59
60 leading_indent_tests = [
60 leading_indent_tests = [
61 ([' print "hi"'], ['print "hi"']),
61 ([' print "hi"'], ['print "hi"']),
62 ([' for a in range(5):', ' a*2'], ['for a in range(5):', ' a*2']),
62 ([' for a in range(5):', ' a*2'], ['for a in range(5):', ' a*2']),
63 ([' a="""',' 123"""'], ['a="""', '123"""']),
63 ([' a="""',' 123"""'], ['a="""', '123"""']),
64 (['a="""',' 123"""'], ['a="""', ' 123"""']),
64 (['a="""',' 123"""'], ['a="""', ' 123"""']),
65 ]
65 ]
66
66
67 def test_leading_indent():
67 def test_leading_indent():
68 tt.check_pairs(wrap_transform(inputtransformer.leading_indent), leading_indent_tests)
68 tt.check_pairs(wrap_transform(inputtransformer.leading_indent), leading_indent_tests)
69
70 assign_magic_tests = [(i, [py3compat.u_format(ol) for ol in o]) for i,o in [
71 (['a = %bc de \\', 'fg'], ["a = get_ipython().magic('bc de fg')"]),
72 (['a = %bc de \\', 'fg\\', None], ["a = get_ipython().magic('bc de fg')"]),
73 ]]
74
75 def test_assign_magic():
76 tt.check_pairs(wrap_transform(inputtransformer.assign_from_magic), assign_magic_tests)
77
78 assign_system_tests = [(i, [py3compat.u_format(ol) for ol in o]) for i,o in [
79 (['a = !bc de \\', 'fg'], ["a = get_ipython().getoutput('bc de fg')"]),
80 (['a = !bc de \\', 'fg\\', None], ["a = get_ipython().getoutput('bc de fg')"]),
81 ]]
82
83 def test_assign_system():
84 tt.check_pairs(wrap_transform(inputtransformer.assign_from_system), assign_system_tests)
General Comments 0
You need to be logged in to leave comments. Login now