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