##// END OF EJS Templates
Add TokenInputTransformer
Thomas Kluyver -
Show More
@@ -1,397 +1,441 b''
1 1 import abc
2 2 import functools
3 3 import re
4 4 from StringIO import StringIO
5 5 import tokenize
6 6
7 7 from IPython.core.splitinput import split_user_input, LineInfo
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Globals
11 11 #-----------------------------------------------------------------------------
12 12
13 13 # The escape sequences that define the syntax transformations IPython will
14 14 # apply to user input. These can NOT be just changed here: many regular
15 15 # expressions and other parts of the code may use their hardcoded values, and
16 16 # for all intents and purposes they constitute the 'IPython syntax', so they
17 17 # should be considered fixed.
18 18
19 19 ESC_SHELL = '!' # Send line to underlying system shell
20 20 ESC_SH_CAP = '!!' # Send line to system shell and capture output
21 21 ESC_HELP = '?' # Find information about object
22 22 ESC_HELP2 = '??' # Find extra-detailed information about object
23 23 ESC_MAGIC = '%' # Call magic function
24 24 ESC_MAGIC2 = '%%' # Call cell-magic function
25 25 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
26 26 ESC_QUOTE2 = ';' # Quote all args as a single string, call
27 27 ESC_PAREN = '/' # Call first argument with rest of line as arguments
28 28
29 29 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
30 30 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
31 31 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
32 32
33 33
34 34 class InputTransformer(object):
35 35 """Abstract base class for line-based input transformers."""
36 36 __metaclass__ = abc.ABCMeta
37 37
38 38 @abc.abstractmethod
39 39 def push(self, line):
40 40 """Send a line of input to the transformer, returning the transformed
41 41 input or None if the transformer is waiting for more input.
42 42
43 43 Must be overridden by subclasses.
44 44 """
45 45 pass
46 46
47 47 @abc.abstractmethod
48 48 def reset(self):
49 49 """Return, transformed any lines that the transformer has accumulated,
50 50 and reset its internal state.
51 51
52 52 Must be overridden by subclasses.
53 53 """
54 54 pass
55 55
56 56 # Set this to True to allow the transformer to act on lines inside strings.
57 57 look_in_string = False
58 58
59 59 @classmethod
60 60 def wrap(cls, func):
61 61 """Can be used by subclasses as a decorator, to return a factory that
62 62 will allow instantiation with the decorated object.
63 63 """
64 64 @functools.wraps(func)
65 65 def transformer_factory():
66 66 transformer = cls(func)
67 67 if getattr(transformer_factory, 'look_in_string', False):
68 68 transformer.look_in_string = True
69 69 return transformer
70 70
71 71 return transformer_factory
72 72
73 73 class StatelessInputTransformer(InputTransformer):
74 74 """Wrapper for a stateless input transformer implemented as a function."""
75 75 def __init__(self, func):
76 76 self.func = func
77 77
78 78 def __repr__(self):
79 79 return "StatelessInputTransformer(func={!r})".format(self.func)
80 80
81 81 def push(self, line):
82 82 """Send a line of input to the transformer, returning the
83 83 transformed input."""
84 84 return self.func(line)
85 85
86 86 def reset(self):
87 87 """No-op - exists for compatibility."""
88 88 pass
89 89
90 90 class CoroutineInputTransformer(InputTransformer):
91 91 """Wrapper for an input transformer implemented as a coroutine."""
92 92 def __init__(self, coro):
93 93 # Prime it
94 94 self.coro = coro()
95 95 next(self.coro)
96 96
97 97 def __repr__(self):
98 98 return "CoroutineInputTransformer(coro={!r})".format(self.coro)
99 99
100 100 def push(self, line):
101 101 """Send a line of input to the transformer, returning the
102 102 transformed input or None if the transformer is waiting for more
103 103 input.
104 104 """
105 105 return self.coro.send(line)
106 106
107 107 def reset(self):
108 108 """Return, transformed any lines that the transformer has
109 109 accumulated, and reset its internal state.
110 110 """
111 111 return self.coro.send(None)
112 112
113 class TokenInputTransformer(InputTransformer):
114 """Wrapper for a token-based input transformer.
115
116 func should accept a list of tokens (5-tuples, see tokenize docs), and
117 return an iterable which can be passed to tokenize.untokenize().
118 """
119 def __init__(self, func):
120 self.func = func
121 self.current_line = ""
122 self.tokenizer = tokenize.generate_tokens(self.get_line)
123 self.line_used= False
124
125 def get_line(self):
126 if self.line_used:
127 raise tokenize.TokenError
128 self.line_used = True
129 return self.current_line
130
131 def push(self, line):
132 self.current_line += line + "\n"
133 self.line_used = False
134 tokens = []
135 try:
136 for intok in self.tokenizer:
137 tokens.append(intok)
138 if intok[0] in (tokenize.NEWLINE, tokenize.NL):
139 # Stop before we try to pull a line we don't have yet
140 break
141 except tokenize.TokenError:
142 # Multi-line statement - stop and try again with the next line
143 self.tokenizer = tokenize.generate_tokens(self.get_line)
144 return None
145
146 self.current_line = ""
147 # Python bug 8478 - untokenize doesn't work quite correctly with a
148 # generator. We call list() to avoid this.
149 return tokenize.untokenize(list(self.func(tokens))).rstrip('\n')
150
151 def reset(self):
152 l = self.current_line
153 self.current_line = ""
154 if l:
155 return l.rstrip('\n')
156
113 157
114 158 # Utilities
115 159 def _make_help_call(target, esc, lspace, next_input=None):
116 160 """Prepares a pinfo(2)/psearch call from a target name and the escape
117 161 (i.e. ? or ??)"""
118 162 method = 'pinfo2' if esc == '??' \
119 163 else 'psearch' if '*' in target \
120 164 else 'pinfo'
121 165 arg = " ".join([method, target])
122 166 if next_input is None:
123 167 return '%sget_ipython().magic(%r)' % (lspace, arg)
124 168 else:
125 169 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
126 170 (lspace, next_input, arg)
127 171
128 172 @CoroutineInputTransformer.wrap
129 173 def escaped_transformer():
130 174 """Translate lines beginning with one of IPython's escape characters.
131 175
132 176 This is stateful to allow magic commands etc. to be continued over several
133 177 lines using explicit line continuations (\ at the end of a line).
134 178 """
135 179
136 180 # These define the transformations for the different escape characters.
137 181 def _tr_system(line_info):
138 182 "Translate lines escaped with: !"
139 183 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
140 184 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
141 185
142 186 def _tr_system2(line_info):
143 187 "Translate lines escaped with: !!"
144 188 cmd = line_info.line.lstrip()[2:]
145 189 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
146 190
147 191 def _tr_help(line_info):
148 192 "Translate lines escaped with: ?/??"
149 193 # A naked help line should just fire the intro help screen
150 194 if not line_info.line[1:]:
151 195 return 'get_ipython().show_usage()'
152 196
153 197 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
154 198
155 199 def _tr_magic(line_info):
156 200 "Translate lines escaped with: %"
157 201 tpl = '%sget_ipython().magic(%r)'
158 202 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
159 203 return tpl % (line_info.pre, cmd)
160 204
161 205 def _tr_quote(line_info):
162 206 "Translate lines escaped with: ,"
163 207 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
164 208 '", "'.join(line_info.the_rest.split()) )
165 209
166 210 def _tr_quote2(line_info):
167 211 "Translate lines escaped with: ;"
168 212 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
169 213 line_info.the_rest)
170 214
171 215 def _tr_paren(line_info):
172 216 "Translate lines escaped with: /"
173 217 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
174 218 ", ".join(line_info.the_rest.split()))
175 219
176 220 tr = { ESC_SHELL : _tr_system,
177 221 ESC_SH_CAP : _tr_system2,
178 222 ESC_HELP : _tr_help,
179 223 ESC_HELP2 : _tr_help,
180 224 ESC_MAGIC : _tr_magic,
181 225 ESC_QUOTE : _tr_quote,
182 226 ESC_QUOTE2 : _tr_quote2,
183 227 ESC_PAREN : _tr_paren }
184 228
185 229 line = ''
186 230 while True:
187 231 line = (yield line)
188 232 if not line or line.isspace():
189 233 continue
190 234 lineinf = LineInfo(line)
191 235 if lineinf.esc not in tr:
192 236 continue
193 237
194 238 parts = []
195 239 while line is not None:
196 240 parts.append(line.rstrip('\\'))
197 241 if not line.endswith('\\'):
198 242 break
199 243 line = (yield None)
200 244
201 245 # Output
202 246 lineinf = LineInfo(' '.join(parts))
203 247 line = tr[lineinf.esc](lineinf)
204 248
205 249 _initial_space_re = re.compile(r'\s*')
206 250
207 251 _help_end_re = re.compile(r"""(%{0,2}
208 252 [a-zA-Z_*][\w*]* # Variable name
209 253 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
210 254 )
211 255 (\?\??)$ # ? or ??""",
212 256 re.VERBOSE)
213 257
214 258 def has_comment(src):
215 259 """Indicate whether an input line has (i.e. ends in, or is) a comment.
216 260
217 261 This uses tokenize, so it can distinguish comments from # inside strings.
218 262
219 263 Parameters
220 264 ----------
221 265 src : string
222 266 A single line input string.
223 267
224 268 Returns
225 269 -------
226 270 Boolean: True if source has a comment.
227 271 """
228 272 readline = StringIO(src).readline
229 273 toktypes = set()
230 274 try:
231 275 for t in tokenize.generate_tokens(readline):
232 276 toktypes.add(t[0])
233 277 except tokenize.TokenError:
234 278 pass
235 279 return(tokenize.COMMENT in toktypes)
236 280
237 281
238 282 @StatelessInputTransformer.wrap
239 283 def help_end(line):
240 284 """Translate lines with ?/?? at the end"""
241 285 m = _help_end_re.search(line)
242 286 if m is None or has_comment(line):
243 287 return line
244 288 target = m.group(1)
245 289 esc = m.group(3)
246 290 lspace = _initial_space_re.match(line).group(0)
247 291
248 292 # If we're mid-command, put it back on the next prompt for the user.
249 293 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
250 294
251 295 return _make_help_call(target, esc, lspace, next_input)
252 296
253 297
254 298 @CoroutineInputTransformer.wrap
255 299 def cellmagic():
256 300 """Captures & transforms cell magics.
257 301
258 302 After a cell magic is started, this stores up any lines it gets until it is
259 303 reset (sent None).
260 304 """
261 305 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
262 306 cellmagic_help_re = re.compile('%%\w+\?')
263 307 line = ''
264 308 while True:
265 309 line = (yield line)
266 310 if (not line) or (not line.startswith(ESC_MAGIC2)):
267 311 continue
268 312
269 313 if cellmagic_help_re.match(line):
270 314 # This case will be handled by help_end
271 315 continue
272 316
273 317 first = line
274 318 body = []
275 319 line = (yield None)
276 320 while (line is not None) and (line.strip() != ''):
277 321 body.append(line)
278 322 line = (yield None)
279 323
280 324 # Output
281 325 magic_name, _, first = first.partition(' ')
282 326 magic_name = magic_name.lstrip(ESC_MAGIC2)
283 327 line = tpl % (magic_name, first, u'\n'.join(body))
284 328
285 329
286 330 def _strip_prompts(prompt1_re, prompt2_re):
287 331 """Remove matching input prompts from a block of input."""
288 332 line = ''
289 333 while True:
290 334 line = (yield line)
291 335
292 336 if line is None:
293 337 continue
294 338
295 339 m = prompt1_re.match(line)
296 340 if m:
297 341 while m:
298 342 line = (yield line[len(m.group(0)):])
299 343 if line is None:
300 344 break
301 345 m = prompt2_re.match(line)
302 346 else:
303 347 # Prompts not in input - wait for reset
304 348 while line is not None:
305 349 line = (yield line)
306 350
307 351 @CoroutineInputTransformer.wrap
308 352 def classic_prompt():
309 353 """Strip the >>>/... prompts of the Python interactive shell."""
310 354 prompt1_re = re.compile(r'^(>>> )')
311 355 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
312 356 return _strip_prompts(prompt1_re, prompt2_re)
313 357
314 358 classic_prompt.look_in_string = True
315 359
316 360 @CoroutineInputTransformer.wrap
317 361 def ipy_prompt():
318 362 """Strip IPython's In [1]:/...: prompts."""
319 363 prompt1_re = re.compile(r'^In \[\d+\]: ')
320 364 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
321 365 return _strip_prompts(prompt1_re, prompt2_re)
322 366
323 367 ipy_prompt.look_in_string = True
324 368
325 369
326 370 @CoroutineInputTransformer.wrap
327 371 def leading_indent():
328 372 """Remove leading indentation.
329 373
330 374 If the first line starts with a spaces or tabs, the same whitespace will be
331 375 removed from each following line until it is reset.
332 376 """
333 377 space_re = re.compile(r'^[ \t]+')
334 378 line = ''
335 379 while True:
336 380 line = (yield line)
337 381
338 382 if line is None:
339 383 continue
340 384
341 385 m = space_re.match(line)
342 386 if m:
343 387 space = m.group(0)
344 388 while line is not None:
345 389 if line.startswith(space):
346 390 line = line[len(space):]
347 391 line = (yield line)
348 392 else:
349 393 # No leading spaces - wait for reset
350 394 while line is not None:
351 395 line = (yield line)
352 396
353 397 leading_indent.look_in_string = True
354 398
355 399
356 400 def _special_assignment(assignment_re, template):
357 401 """Transform assignment from system & magic commands.
358 402
359 403 This is stateful so that it can handle magic commands continued on several
360 404 lines.
361 405 """
362 406 line = ''
363 407 while True:
364 408 line = (yield line)
365 409 if not line or line.isspace():
366 410 continue
367 411
368 412 m = assignment_re.match(line)
369 413 if not m:
370 414 continue
371 415
372 416 parts = []
373 417 while line is not None:
374 418 parts.append(line.rstrip('\\'))
375 419 if not line.endswith('\\'):
376 420 break
377 421 line = (yield None)
378 422
379 423 # Output
380 424 whole = assignment_re.match(' '.join(parts))
381 425 line = template % (whole.group('lhs'), whole.group('cmd'))
382 426
383 427 @CoroutineInputTransformer.wrap
384 428 def assign_from_system():
385 429 """Transform assignment from system commands (e.g. files = !ls)"""
386 430 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
387 431 r'\s*=\s*!\s*(?P<cmd>.*)')
388 432 template = '%s = get_ipython().getoutput(%r)'
389 433 return _special_assignment(assignment_re, template)
390 434
391 435 @CoroutineInputTransformer.wrap
392 436 def assign_from_magic():
393 437 """Transform assignment from magic commands (e.g. a = %who_ls)"""
394 438 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
395 439 r'\s*=\s*%\s*(?P<cmd>.*)')
396 440 template = '%s = get_ipython().magic(%r)'
397 441 return _special_assignment(assignment_re, template)
@@ -1,326 +1,368 b''
1 import tokenize
1 2 import unittest
2 3 import nose.tools as nt
3 4
4 5 from IPython.testing import tools as tt
5 6 from IPython.utils import py3compat
6 7 u_fmt = py3compat.u_format
7 8
8 9 from IPython.core import inputtransformer as ipt
9 10
10 11 def transform_and_reset(transformer):
11 12 transformer = transformer()
12 13 def transform(inp):
13 14 try:
14 15 return transformer.push(inp)
15 16 finally:
16 17 transformer.reset()
17 18
18 19 return transform
19 20
20 21 # Transformer tests
21 22 def transform_checker(tests, transformer):
22 23 """Utility to loop over test inputs"""
23 24 transformer = transformer()
24 25 try:
25 26 for inp, tr in tests:
26 27 if inp is None:
27 28 out = transformer.reset()
28 29 else:
29 30 out = transformer.push(inp)
30 31 nt.assert_equal(out, tr)
31 32 finally:
32 33 transformer.reset()
33 34
34 35 # Data for all the syntax tests in the form of lists of pairs of
35 36 # raw/transformed input. We store it here as a global dict so that we can use
36 37 # it both within single-function tests and also to validate the behavior of the
37 38 # larger objects
38 39
39 40 syntax = \
40 41 dict(assign_system =
41 42 [(i,py3compat.u_format(o)) for i,o in \
42 43 [(u'a =! ls', "a = get_ipython().getoutput({u}'ls')"),
43 44 (u'b = !ls', "b = get_ipython().getoutput({u}'ls')"),
44 45 ('x=1', 'x=1'), # normal input is unmodified
45 46 (' ',' '), # blank lines are kept intact
46 47 ]],
47 48
48 49 assign_magic =
49 50 [(i,py3compat.u_format(o)) for i,o in \
50 51 [(u'a =% who', "a = get_ipython().magic({u}'who')"),
51 52 (u'b = %who', "b = get_ipython().magic({u}'who')"),
52 53 ('x=1', 'x=1'), # normal input is unmodified
53 54 (' ',' '), # blank lines are kept intact
54 55 ]],
55 56
56 57 classic_prompt =
57 58 [('>>> x=1', 'x=1'),
58 59 ('x=1', 'x=1'), # normal input is unmodified
59 60 (' ', ' '), # blank lines are kept intact
60 61 ],
61 62
62 63 ipy_prompt =
63 64 [('In [1]: x=1', 'x=1'),
64 65 ('x=1', 'x=1'), # normal input is unmodified
65 66 (' ',' '), # blank lines are kept intact
66 67 ],
67 68
68 69 # Tests for the escape transformer to leave normal code alone
69 70 escaped_noesc =
70 71 [ (' ', ' '),
71 72 ('x=1', 'x=1'),
72 73 ],
73 74
74 75 # System calls
75 76 escaped_shell =
76 77 [(i,py3compat.u_format(o)) for i,o in \
77 78 [ (u'!ls', "get_ipython().system({u}'ls')"),
78 79 # Double-escape shell, this means to capture the output of the
79 80 # subprocess and return it
80 81 (u'!!ls', "get_ipython().getoutput({u}'ls')"),
81 82 ]],
82 83
83 84 # Help/object info
84 85 escaped_help =
85 86 [(i,py3compat.u_format(o)) for i,o in \
86 87 [ (u'?', 'get_ipython().show_usage()'),
87 88 (u'?x1', "get_ipython().magic({u}'pinfo x1')"),
88 89 (u'??x2', "get_ipython().magic({u}'pinfo2 x2')"),
89 90 (u'?a.*s', "get_ipython().magic({u}'psearch a.*s')"),
90 91 (u'?%hist1', "get_ipython().magic({u}'pinfo %hist1')"),
91 92 (u'?%%hist2', "get_ipython().magic({u}'pinfo %%hist2')"),
92 93 (u'?abc = qwe', "get_ipython().magic({u}'pinfo abc')"),
93 94 ]],
94 95
95 96 end_help =
96 97 [(i,py3compat.u_format(o)) for i,o in \
97 98 [ (u'x3?', "get_ipython().magic({u}'pinfo x3')"),
98 99 (u'x4??', "get_ipython().magic({u}'pinfo2 x4')"),
99 100 (u'%hist1?', "get_ipython().magic({u}'pinfo %hist1')"),
100 101 (u'%hist2??', "get_ipython().magic({u}'pinfo2 %hist2')"),
101 102 (u'%%hist3?', "get_ipython().magic({u}'pinfo %%hist3')"),
102 103 (u'%%hist4??', "get_ipython().magic({u}'pinfo2 %%hist4')"),
103 104 (u'f*?', "get_ipython().magic({u}'psearch f*')"),
104 105 (u'ax.*aspe*?', "get_ipython().magic({u}'psearch ax.*aspe*')"),
105 106 (u'a = abc?', "get_ipython().set_next_input({u}'a = abc');"
106 107 "get_ipython().magic({u}'pinfo abc')"),
107 108 (u'a = abc.qe??', "get_ipython().set_next_input({u}'a = abc.qe');"
108 109 "get_ipython().magic({u}'pinfo2 abc.qe')"),
109 110 (u'a = *.items?', "get_ipython().set_next_input({u}'a = *.items');"
110 111 "get_ipython().magic({u}'psearch *.items')"),
111 112 (u'plot(a?', "get_ipython().set_next_input({u}'plot(a');"
112 113 "get_ipython().magic({u}'pinfo a')"),
113 114 (u'a*2 #comment?', 'a*2 #comment?'),
114 115 ]],
115 116
116 117 # Explicit magic calls
117 118 escaped_magic =
118 119 [(i,py3compat.u_format(o)) for i,o in \
119 120 [ (u'%cd', "get_ipython().magic({u}'cd')"),
120 121 (u'%cd /home', "get_ipython().magic({u}'cd /home')"),
121 122 # Backslashes need to be escaped.
122 123 (u'%cd C:\\User', "get_ipython().magic({u}'cd C:\\\\User')"),
123 124 (u' %magic', " get_ipython().magic({u}'magic')"),
124 125 ]],
125 126
126 127 # Quoting with separate arguments
127 128 escaped_quote =
128 129 [ (',f', 'f("")'),
129 130 (',f x', 'f("x")'),
130 131 (' ,f y', ' f("y")'),
131 132 (',f a b', 'f("a", "b")'),
132 133 ],
133 134
134 135 # Quoting with single argument
135 136 escaped_quote2 =
136 137 [ (';f', 'f("")'),
137 138 (';f x', 'f("x")'),
138 139 (' ;f y', ' f("y")'),
139 140 (';f a b', 'f("a b")'),
140 141 ],
141 142
142 143 # Simply apply parens
143 144 escaped_paren =
144 145 [ ('/f', 'f()'),
145 146 ('/f x', 'f(x)'),
146 147 (' /f y', ' f(y)'),
147 148 ('/f a b', 'f(a, b)'),
148 149 ],
149 150
150 151 # Check that we transform prompts before other transforms
151 152 mixed =
152 153 [(i,py3compat.u_format(o)) for i,o in \
153 154 [ (u'In [1]: %lsmagic', "get_ipython().magic({u}'lsmagic')"),
154 155 (u'>>> %lsmagic', "get_ipython().magic({u}'lsmagic')"),
155 156 (u'In [2]: !ls', "get_ipython().system({u}'ls')"),
156 157 (u'In [3]: abs?', "get_ipython().magic({u}'pinfo abs')"),
157 158 (u'In [4]: b = %who', "b = get_ipython().magic({u}'who')"),
158 159 ]],
159 160 )
160 161
161 162 # multiline syntax examples. Each of these should be a list of lists, with
162 163 # each entry itself having pairs of raw/transformed input. The union (with
163 164 # '\n'.join() of the transformed inputs is what the splitter should produce
164 165 # when fed the raw lines one at a time via push.
165 166 syntax_ml = \
166 167 dict(classic_prompt =
167 168 [ [('>>> for i in range(10):','for i in range(10):'),
168 169 ('... print i',' print i'),
169 170 ('... ', ''),
170 171 ],
171 172 [('>>> a="""','a="""'),
172 173 ('... 123"""','123"""'),
173 174 ],
174 175 [('a="""','a="""'),
175 176 ('... 123"""','... 123"""'),
176 177 ],
177 178 ],
178 179
179 180 ipy_prompt =
180 181 [ [('In [24]: for i in range(10):','for i in range(10):'),
181 182 (' ....: print i',' print i'),
182 183 (' ....: ', ''),
183 184 ],
184 185 [('In [2]: a="""','a="""'),
185 186 (' ...: 123"""','123"""'),
186 187 ],
187 188 [('a="""','a="""'),
188 189 (' ...: 123"""',' ...: 123"""'),
189 190 ],
190 191 ],
191 192
192 193 multiline_datastructure =
193 194 [ [('>>> a = [1,','a = [1,'),
194 195 ('... 2]','2]'),
195 196 ],
196 197 ],
197 198
198 199 leading_indent =
199 200 [ [(' print "hi"','print "hi"'),
200 201 ],
201 202 [(' for a in range(5):','for a in range(5):'),
202 203 (' a*2',' a*2'),
203 204 ],
204 205 [(' a="""','a="""'),
205 206 (' 123"""','123"""'),
206 207 ],
207 208 [('a="""','a="""'),
208 209 (' 123"""',' 123"""'),
209 210 ],
210 211 ],
211 212
212 213 cellmagic =
213 214 [ [(u'%%foo a', None),
214 215 (None, u_fmt("get_ipython().run_cell_magic({u}'foo', {u}'a', {u}'')")),
215 216 ],
216 217 [(u'%%bar 123', None),
217 218 (u'hello', None),
218 219 (u'', u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
219 220 ],
220 221 ],
221 222
222 223 escaped =
223 224 [ [('%abc def \\', None),
224 225 ('ghi', u_fmt("get_ipython().magic({u}'abc def ghi')")),
225 226 ],
226 227 [('%abc def \\', None),
227 228 ('ghi\\', None),
228 229 (None, u_fmt("get_ipython().magic({u}'abc def ghi')")),
229 230 ],
230 231 ],
231 232
232 233 assign_magic =
233 234 [ [(u'a = %bc de \\', None),
234 235 (u'fg', u_fmt("a = get_ipython().magic({u}'bc de fg')")),
235 236 ],
236 237 [(u'a = %bc de \\', None),
237 238 (u'fg\\', None),
238 239 (None, u_fmt("a = get_ipython().magic({u}'bc de fg')")),
239 240 ],
240 241 ],
241 242
242 243 assign_system =
243 244 [ [(u'a = !bc de \\', None),
244 245 (u'fg', u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
245 246 ],
246 247 [(u'a = !bc de \\', None),
247 248 (u'fg\\', None),
248 249 (None, u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
249 250 ],
250 251 ],
251 252 )
252 253
253 254
254 255 def test_assign_system():
255 256 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
256 257 for example in syntax_ml['assign_system']:
257 258 transform_checker(example, ipt.assign_from_system)
258 259
259 260 def test_assign_magic():
260 261 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
261 262 for example in syntax_ml['assign_magic']:
262 263 transform_checker(example, ipt.assign_from_magic)
263 264
264 265
265 266 def test_classic_prompt():
266 267 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
267 268 for example in syntax_ml['classic_prompt']:
268 269 transform_checker(example, ipt.classic_prompt)
269 270 for example in syntax_ml['multiline_datastructure']:
270 271 transform_checker(example, ipt.classic_prompt)
271 272
272 273
273 274 def test_ipy_prompt():
274 275 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
275 276 for example in syntax_ml['ipy_prompt']:
276 277 transform_checker(example, ipt.ipy_prompt)
277 278
278 279 def test_help_end():
279 280 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
280 281
281 282 def test_escaped_noesc():
282 283 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_noesc'])
283 284
284 285
285 286 def test_escaped_shell():
286 287 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_shell'])
287 288
288 289
289 290 def test_escaped_help():
290 291 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_help'])
291 292
292 293
293 294 def test_escaped_magic():
294 295 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_magic'])
295 296
296 297
297 298 def test_escaped_quote():
298 299 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_quote'])
299 300
300 301
301 302 def test_escaped_quote2():
302 303 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_quote2'])
303 304
304 305
305 306 def test_escaped_paren():
306 307 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_paren'])
307 308
308 309 def test_escaped_multiline():
309 310 for example in syntax_ml['escaped']:
310 311 transform_checker(example, ipt.escaped_transformer)
311 312
312 313 def test_cellmagic():
313 314 for example in syntax_ml['cellmagic']:
314 315 transform_checker(example, ipt.cellmagic)
315 316
316 317 def test_has_comment():
317 318 tests = [('text', False),
318 319 ('text #comment', True),
319 320 ('text #comment\n', True),
320 321 ('#comment', True),
321 322 ('#comment\n', True),
322 323 ('a = "#string"', False),
323 324 ('a = "#string" # comment', True),
324 325 ('a #comment not "string"', True),
325 326 ]
326 327 tt.check_pairs(ipt.has_comment, tests)
328
329 @ipt.TokenInputTransformer.wrap
330 def decistmt(tokens):
331 """Substitute Decimals for floats in a string of statements.
332
333 Based on an example from the tokenize module docs.
334 """
335 result = []
336 for toknum, tokval, _, _, _ in tokens:
337 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
338 for newtok in [
339 (tokenize.NAME, 'Decimal'),
340 (tokenize.OP, '('),
341 (tokenize.STRING, repr(tokval)),
342 (tokenize.OP, ')')
343 ]:
344 yield newtok
345 else:
346 yield (toknum, tokval)
347
348
349
350 def test_token_input_transformer():
351 tests = [(u'1.2', u_fmt(u"Decimal ({u}'1.2')")),
352 (u'"1.2"', u'"1.2"'),
353 ]
354 tt.check_pairs(transform_and_reset(decistmt), tests)
355 ml_tests = \
356 [ [(u"a = 1.2; b = '''x", None),
357 (u"y'''", u_fmt(u"a =Decimal ({u}'1.2');b ='''x\ny'''")),
358 ],
359 [(u"a = [1.2,", u_fmt(u"a =[Decimal ({u}'1.2'),")),
360 (u"3]", u"3 ]"),
361 ],
362 [(u"a = '''foo", None), # Test resetting when within a multi-line string
363 (u"bar", None),
364 (None, u"a = '''foo\nbar"),
365 ],
366 ]
367 for example in ml_tests:
368 transform_checker(example, decistmt)
General Comments 0
You need to be logged in to leave comments. Login now