##// END OF EJS Templates
Merge pull request #4060 from minrk/multiprompt...
Thomas Kluyver -
r12329:11babdf1 merge
parent child Browse files
Show More
@@ -1,482 +1,502 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, initial_re=None):
363 """Remove matching input prompts from a block of input.
364
365 Parameters
366 ----------
367 prompt_re : regular expression
368 A regular expression matching any input prompt (including continuation)
369 initial_re : regular expression, optional
370 A regular expression matching only the initial prompt, but not continuation.
371 If no initial expression is given, prompt_re will be used everywhere.
372 Used mainly for plain Python prompts, where the continuation prompt
373 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
374
375 If initial_re and prompt_re differ,
376 only initial_re will be tested against the first line.
377 If any prompt is found on the first two lines,
378 prompts will be stripped from the rest of the block.
379 """
380 if initial_re is None:
381 initial_re = prompt_re
364 382 line = ''
365 383 while True:
366 384 line = (yield line)
367 385
368 386 # First line of cell
369 387 if line is None:
370 388 continue
371 out, n1 = prompt_re.subn('', line, count=1)
389 out, n1 = initial_re.subn('', line, count=1)
372 390 line = (yield out)
373 391
374 # Second line of cell, because people often copy from just after the
375 # first prompt, so we might not see it in the first line.
376 392 if line is None:
377 393 continue
394 # check for any prompt on the second line of the cell,
395 # because people often copy from just after the first prompt,
396 # so we might not see it in the first line.
378 397 out, n2 = prompt_re.subn('', line, count=1)
379 398 line = (yield out)
380 399
381 400 if n1 or n2:
382 # Found the input prompt in the first two lines - check for it in
401 # Found a prompt in the first two lines - check for it in
383 402 # the rest of the cell as well.
384 403 while line is not None:
385 404 line = (yield prompt_re.sub('', line, count=1))
386 405
387 406 else:
388 407 # Prompts not in input - wait for reset
389 408 while line is not None:
390 409 line = (yield line)
391 410
392 411 @CoroutineInputTransformer.wrap
393 412 def classic_prompt():
394 413 """Strip the >>>/... prompts of the Python interactive shell."""
395 414 # FIXME: non-capturing version (?:...) usable?
396 415 prompt_re = re.compile(r'^(>>> ?|\.\.\. ?)')
397 return _strip_prompts(prompt_re)
416 initial_re = re.compile(r'^(>>> ?)')
417 return _strip_prompts(prompt_re, initial_re)
398 418
399 419 @CoroutineInputTransformer.wrap
400 420 def ipy_prompt():
401 421 """Strip IPython's In [1]:/...: prompts."""
402 422 # FIXME: non-capturing version (?:...) usable?
403 423 # FIXME: r'^(In \[\d+\]: | {3}\.{3,}: )' clearer?
404 424 prompt_re = re.compile(r'^(In \[\d+\]: |\ \ \ \.\.\.+: )')
405 425 return _strip_prompts(prompt_re)
406 426
407 427
408 428 @CoroutineInputTransformer.wrap
409 429 def leading_indent():
410 430 """Remove leading indentation.
411 431
412 432 If the first line starts with a spaces or tabs, the same whitespace will be
413 433 removed from each following line until it is reset.
414 434 """
415 435 space_re = re.compile(r'^[ \t]+')
416 436 line = ''
417 437 while True:
418 438 line = (yield line)
419 439
420 440 if line is None:
421 441 continue
422 442
423 443 m = space_re.match(line)
424 444 if m:
425 445 space = m.group(0)
426 446 while line is not None:
427 447 if line.startswith(space):
428 448 line = line[len(space):]
429 449 line = (yield line)
430 450 else:
431 451 # No leading spaces - wait for reset
432 452 while line is not None:
433 453 line = (yield line)
434 454
435 455
436 456 @CoroutineInputTransformer.wrap
437 457 def strip_encoding_cookie():
438 458 """Remove encoding comment if found in first two lines
439 459
440 460 If the first or second line has the `# coding: utf-8` comment,
441 461 it will be removed.
442 462 """
443 463 line = ''
444 464 while True:
445 465 line = (yield line)
446 466 # check comment on first two lines
447 467 for i in range(2):
448 468 if line is None:
449 469 break
450 470 if cookie_comment_re.match(line):
451 471 line = (yield "")
452 472 else:
453 473 line = (yield line)
454 474
455 475 # no-op on the rest of the cell
456 476 while line is not None:
457 477 line = (yield line)
458 478
459 479
460 480 assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
461 481 r'\s*=\s*!\s*(?P<cmd>.*)')
462 482 assign_system_template = '%s = get_ipython().getoutput(%r)'
463 483 @StatelessInputTransformer.wrap
464 484 def assign_from_system(line):
465 485 """Transform assignment from system commands (e.g. files = !ls)"""
466 486 m = assign_system_re.match(line)
467 487 if m is None:
468 488 return line
469 489
470 490 return assign_system_template % m.group('lhs', 'cmd')
471 491
472 492 assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
473 493 r'\s*=\s*%\s*(?P<cmd>.*)')
474 494 assign_magic_template = '%s = get_ipython().magic(%r)'
475 495 @StatelessInputTransformer.wrap
476 496 def assign_from_magic(line):
477 497 """Transform assignment from magic commands (e.g. a = %who_ls)"""
478 498 m = assign_magic_re.match(line)
479 499 if m is None:
480 500 return line
481 501
482 502 return assign_magic_template % m.group('lhs', 'cmd')
@@ -1,451 +1,464 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 182 ('... 123','123'),
183 183 ('... 456"""','456"""'),
184 184 ],
185 185 [('a="""','a="""'),
186 ('>>> 123','123'),
187 ('... 456"""','456"""'),
188 ],
189 [('a="""','a="""'),
186 190 ('123','123'),
187 191 ('... 456"""','... 456"""'),
188 192 ],
193 [('....__class__','....__class__'),
194 ],
195 [('a=5', 'a=5'),
196 ('...', ''),
197 ],
189 198 [('>>> def f(x):', 'def f(x):'),
190 199 ('...', ''),
191 200 ('... return x', ' return x'),
192 201 ],
193 202 ],
194 203
195 204 ipy_prompt =
196 205 [ [('In [24]: for i in range(10):','for i in range(10):'),
197 206 (' ....: print i',' print i'),
198 207 (' ....: ', ''),
199 208 ],
200 209 [('In [2]: a="""','a="""'),
201 210 (' ...: 123"""','123"""'),
202 211 ],
203 212 [('a="""','a="""'),
204 213 (' ...: 123','123'),
205 214 (' ...: 456"""','456"""'),
206 215 ],
207 216 [('a="""','a="""'),
217 ('In [1]: 123','123'),
218 (' ...: 456"""','456"""'),
219 ],
220 [('a="""','a="""'),
208 221 ('123','123'),
209 222 (' ...: 456"""',' ...: 456"""'),
210 223 ],
211 224 ],
212 225
213 226 strip_encoding_cookie =
214 227 [
215 228 [
216 229 ('# -*- coding: utf-8 -*-', ''),
217 230 ('foo', 'foo'),
218 231 ],
219 232 [
220 233 ('#!/usr/bin/env python', '#!/usr/bin/env python'),
221 234 ('# -*- coding: latin-1 -*-', ''),
222 235 # only the first-two lines
223 236 ('# -*- coding: latin-1 -*-', '# -*- coding: latin-1 -*-'),
224 237 ],
225 238 ],
226 239
227 240 multiline_datastructure_prompt =
228 241 [ [('>>> a = [1,','a = [1,'),
229 242 ('... 2]','2]'),
230 243 ],
231 244 ],
232 245
233 246 multiline_datastructure =
234 247 [ [('b = ("%s"', None),
235 248 ('# comment', None),
236 249 ('%foo )', 'b = ("%s"\n# comment\n%foo )'),
237 250 ],
238 251 ],
239 252
240 253 leading_indent =
241 254 [ [(' print "hi"','print "hi"'),
242 255 ],
243 256 [(' for a in range(5):','for a in range(5):'),
244 257 (' a*2',' a*2'),
245 258 ],
246 259 [(' a="""','a="""'),
247 260 (' 123"""','123"""'),
248 261 ],
249 262 [('a="""','a="""'),
250 263 (' 123"""',' 123"""'),
251 264 ],
252 265 ],
253 266
254 267 cellmagic =
255 268 [ [(u'%%foo a', None),
256 269 (None, u_fmt("get_ipython().run_cell_magic({u}'foo', {u}'a', {u}'')")),
257 270 ],
258 271 [(u'%%bar 123', None),
259 272 (u'hello', None),
260 273 (None , u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
261 274 ],
262 275 [(u'a=5', 'a=5'),
263 276 (u'%%cellmagic', '%%cellmagic'),
264 277 ],
265 278 ],
266 279
267 280 escaped =
268 281 [ [('%abc def \\', None),
269 282 ('ghi', u_fmt("get_ipython().magic({u}'abc def ghi')")),
270 283 ],
271 284 [('%abc def \\', None),
272 285 ('ghi\\', None),
273 286 (None, u_fmt("get_ipython().magic({u}'abc def ghi')")),
274 287 ],
275 288 ],
276 289
277 290 assign_magic =
278 291 [ [(u'a = %bc de \\', None),
279 292 (u'fg', u_fmt("a = get_ipython().magic({u}'bc de fg')")),
280 293 ],
281 294 [(u'a = %bc de \\', None),
282 295 (u'fg\\', None),
283 296 (None, u_fmt("a = get_ipython().magic({u}'bc de fg')")),
284 297 ],
285 298 ],
286 299
287 300 assign_system =
288 301 [ [(u'a = !bc de \\', None),
289 302 (u'fg', u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
290 303 ],
291 304 [(u'a = !bc de \\', None),
292 305 (u'fg\\', None),
293 306 (None, u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
294 307 ],
295 308 ],
296 309 )
297 310
298 311
299 312 def test_assign_system():
300 313 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
301 314
302 315 def test_assign_magic():
303 316 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
304 317
305 318 def test_classic_prompt():
306 319 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
307 320 for example in syntax_ml['classic_prompt']:
308 321 transform_checker(example, ipt.classic_prompt)
309 322 for example in syntax_ml['multiline_datastructure_prompt']:
310 323 transform_checker(example, ipt.classic_prompt)
311 324
312 325
313 326 def test_ipy_prompt():
314 327 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
315 328 for example in syntax_ml['ipy_prompt']:
316 329 transform_checker(example, ipt.ipy_prompt)
317 330
318 331 def test_coding_cookie():
319 332 tt.check_pairs(transform_and_reset(ipt.strip_encoding_cookie), syntax['strip_encoding_cookie'])
320 333 for example in syntax_ml['strip_encoding_cookie']:
321 334 transform_checker(example, ipt.strip_encoding_cookie)
322 335
323 336 def test_assemble_logical_lines():
324 337 tests = \
325 338 [ [(u"a = \\", None),
326 339 (u"123", u"a = 123"),
327 340 ],
328 341 [(u"a = \\", None), # Test resetting when within a multi-line string
329 342 (u"12 *\\", None),
330 343 (None, u"a = 12 *"),
331 344 ],
332 345 [(u"# foo\\", u"# foo\\"), # Comments can't be continued like this
333 346 ],
334 347 ]
335 348 for example in tests:
336 349 transform_checker(example, ipt.assemble_logical_lines)
337 350
338 351 def test_assemble_python_lines():
339 352 tests = \
340 353 [ [(u"a = '''", None),
341 354 (u"abc'''", u"a = '''\nabc'''"),
342 355 ],
343 356 [(u"a = '''", None), # Test resetting when within a multi-line string
344 357 (u"def", None),
345 358 (None, u"a = '''\ndef"),
346 359 ],
347 360 [(u"a = [1,", None),
348 361 (u"2]", u"a = [1,\n2]"),
349 362 ],
350 363 [(u"a = [1,", None), # Test resetting when within a multi-line string
351 364 (u"2,", None),
352 365 (None, u"a = [1,\n2,"),
353 366 ],
354 367 ] + syntax_ml['multiline_datastructure']
355 368 for example in tests:
356 369 transform_checker(example, ipt.assemble_python_lines)
357 370
358 371
359 372 def test_help_end():
360 373 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
361 374
362 375 def test_escaped_noesc():
363 376 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
364 377
365 378
366 379 def test_escaped_shell():
367 380 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
368 381
369 382
370 383 def test_escaped_help():
371 384 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
372 385
373 386
374 387 def test_escaped_magic():
375 388 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
376 389
377 390
378 391 def test_escaped_quote():
379 392 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
380 393
381 394
382 395 def test_escaped_quote2():
383 396 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
384 397
385 398
386 399 def test_escaped_paren():
387 400 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
388 401
389 402
390 403 def test_cellmagic():
391 404 for example in syntax_ml['cellmagic']:
392 405 transform_checker(example, ipt.cellmagic)
393 406
394 407 line_example = [(u'%%bar 123', None),
395 408 (u'hello', None),
396 409 (u'' , u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
397 410 ]
398 411 transform_checker(line_example, ipt.cellmagic, end_on_blank_line=True)
399 412
400 413 def test_has_comment():
401 414 tests = [('text', False),
402 415 ('text #comment', True),
403 416 ('text #comment\n', True),
404 417 ('#comment', True),
405 418 ('#comment\n', True),
406 419 ('a = "#string"', False),
407 420 ('a = "#string" # comment', True),
408 421 ('a #comment not "string"', True),
409 422 ]
410 423 tt.check_pairs(ipt.has_comment, tests)
411 424
412 425 @ipt.TokenInputTransformer.wrap
413 426 def decistmt(tokens):
414 427 """Substitute Decimals for floats in a string of statements.
415 428
416 429 Based on an example from the tokenize module docs.
417 430 """
418 431 result = []
419 432 for toknum, tokval, _, _, _ in tokens:
420 433 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
421 434 for newtok in [
422 435 (tokenize.NAME, 'Decimal'),
423 436 (tokenize.OP, '('),
424 437 (tokenize.STRING, repr(tokval)),
425 438 (tokenize.OP, ')')
426 439 ]:
427 440 yield newtok
428 441 else:
429 442 yield (toknum, tokval)
430 443
431 444
432 445
433 446 def test_token_input_transformer():
434 447 tests = [(u'1.2', u_fmt(u"Decimal ({u}'1.2')")),
435 448 (u'"1.2"', u'"1.2"'),
436 449 ]
437 450 tt.check_pairs(transform_and_reset(decistmt), tests)
438 451 ml_tests = \
439 452 [ [(u"a = 1.2; b = '''x", None),
440 453 (u"y'''", u_fmt(u"a =Decimal ({u}'1.2');b ='''x\ny'''")),
441 454 ],
442 455 [(u"a = [1.2,", None),
443 456 (u"3]", u_fmt(u"a =[Decimal ({u}'1.2'),\n3 ]")),
444 457 ],
445 458 [(u"a = '''foo", None), # Test resetting when within a multi-line string
446 459 (u"bar", None),
447 460 (None, u"a = '''foo\nbar"),
448 461 ],
449 462 ]
450 463 for example in ml_tests:
451 464 transform_checker(example, decistmt)
General Comments 0
You need to be logged in to leave comments. Login now