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