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