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