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