##// END OF EJS Templates
Final cleanups responding to Brian's code review....
Fernando Perez -
Show More
@@ -1,798 +1,817 b''
1 1 """Analysis of text input into executable blocks.
2 2
3 3 The main class in this module, :class:`InputSplitter`, is designed to break
4 4 input from either interactive, line-by-line environments or block-based ones,
5 5 into standalone blocks that can be executed by Python as 'single' statements
6 6 (thus triggering sys.displayhook).
7 7
8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
9 with full support for the extended IPython syntax (magics, system calls, etc).
10
8 11 For more details, see the class docstring below.
9 12
13 ToDo
14 ----
15
16 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
17 at least just attributes of a class so not really very exposed.
18
19 - Think about the best way to support dynamic things: automagic, autocall,
20 macros, etc.
21
22 - Think of a better heuristic for the application of the transforms in
23 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
24 track indentation change events (indent, dedent, nothing) and apply them only
25 if the indentation went up, but not otherwise.
26
27 - Think of the cleanest way for supporting user-specified transformations (the
28 user prefilters we had before).
29
10 30 Authors
31 -------
11 32
12 33 * Fernando Perez
13 34 * Brian Granger
14 35 """
15 36 #-----------------------------------------------------------------------------
16 37 # Copyright (C) 2010 The IPython Development Team
17 38 #
18 39 # Distributed under the terms of the BSD License. The full license is in
19 40 # the file COPYING, distributed as part of this software.
20 41 #-----------------------------------------------------------------------------
21 42
22 43 #-----------------------------------------------------------------------------
23 44 # Imports
24 45 #-----------------------------------------------------------------------------
25 46 # stdlib
26 47 import codeop
27 48 import re
28 49 import sys
29 50
30 51 # IPython modules
31 52 from IPython.utils.text import make_quoted_expr
32 53
33 54 #-----------------------------------------------------------------------------
34 55 # Globals
35 56 #-----------------------------------------------------------------------------
36 57
37 58 # The escape sequences that define the syntax transformations IPython will
38 59 # apply to user input. These can NOT be just changed here: many regular
39 60 # expressions and other parts of the code may use their hardcoded values, and
40 61 # for all intents and purposes they constitute the 'IPython syntax', so they
41 62 # should be considered fixed.
42 63
43 64 ESC_SHELL = '!'
44 65 ESC_SH_CAP = '!!'
45 66 ESC_HELP = '?'
46 67 ESC_HELP2 = '??'
47 68 ESC_MAGIC = '%'
48 69 ESC_QUOTE = ','
49 70 ESC_QUOTE2 = ';'
50 71 ESC_PAREN = '/'
51 72
52 73 #-----------------------------------------------------------------------------
53 74 # Utilities
54 75 #-----------------------------------------------------------------------------
55 76
56 77 # FIXME: These are general-purpose utilities that later can be moved to the
57 78 # general ward. Kept here for now because we're being very strict about test
58 79 # coverage with this code, and this lets us ensure that we keep 100% coverage
59 80 # while developing.
60 81
61 82 # compiled regexps for autoindent management
62 83 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
63 84 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
64 85
65 86
66 87 def num_ini_spaces(s):
67 88 """Return the number of initial spaces in a string.
68 89
69 90 Note that tabs are counted as a single space. For now, we do *not* support
70 91 mixing of tabs and spaces in the user's input.
71 92
72 93 Parameters
73 94 ----------
74 95 s : string
75 96
76 97 Returns
77 98 -------
78 99 n : int
79 100 """
80 101
81 102 ini_spaces = ini_spaces_re.match(s)
82 103 if ini_spaces:
83 104 return ini_spaces.end()
84 105 else:
85 106 return 0
86 107
87 108
88 109 def remove_comments(src):
89 110 """Remove all comments from input source.
90 111
91 112 Note: comments are NOT recognized inside of strings!
92 113
93 114 Parameters
94 115 ----------
95 116 src : string
96 117 A single or multiline input string.
97 118
98 119 Returns
99 120 -------
100 121 String with all Python comments removed.
101 122 """
102 123
103 124 return re.sub('#.*', '', src)
104 125
105 126
106 127 def get_input_encoding():
107 128 """Return the default standard input encoding.
108 129
109 130 If sys.stdin has no encoding, 'ascii' is returned."""
110 131 # There are strange environments for which sys.stdin.encoding is None. We
111 132 # ensure that a valid encoding is returned.
112 133 encoding = getattr(sys.stdin, 'encoding', None)
113 134 if encoding is None:
114 135 encoding = 'ascii'
115 136 return encoding
116 137
117 138 #-----------------------------------------------------------------------------
118 139 # Classes and functions for normal Python syntax handling
119 140 #-----------------------------------------------------------------------------
120 141
121 142 class InputSplitter(object):
122 143 """An object that can split Python source input in executable blocks.
123 144
124 145 This object is designed to be used in one of two basic modes:
125 146
126 147 1. By feeding it python source line-by-line, using :meth:`push`. In this
127 148 mode, it will return on each push whether the currently pushed code
128 149 could be executed already. In addition, it provides a method called
129 150 :meth:`push_accepts_more` that can be used to query whether more input
130 151 can be pushed into a single interactive block.
131 152
132 153 2. By calling :meth:`split_blocks` with a single, multiline Python string,
133 154 that is then split into blocks each of which can be executed
134 155 interactively as a single statement.
135 156
136 157 This is a simple example of how an interactive terminal-based client can use
137 158 this tool::
138 159
139 160 isp = InputSplitter()
140 161 while isp.push_accepts_more():
141 162 indent = ' '*isp.indent_spaces
142 163 prompt = '>>> ' + indent
143 164 line = indent + raw_input(prompt)
144 165 isp.push(line)
145 166 print 'Input source was:\n', isp.source_reset(),
146 167 """
147 168 # Number of spaces of indentation computed from input that has been pushed
148 169 # so far. This is the attributes callers should query to get the current
149 170 # indentation level, in order to provide auto-indent facilities.
150 171 indent_spaces = 0
151 172 # String, indicating the default input encoding. It is computed by default
152 173 # at initialization time via get_input_encoding(), but it can be reset by a
153 174 # client with specific knowledge of the encoding.
154 175 encoding = ''
155 176 # String where the current full source input is stored, properly encoded.
156 177 # Reading this attribute is the normal way of querying the currently pushed
157 178 # source code, that has been properly encoded.
158 179 source = ''
159 180 # Code object corresponding to the current source. It is automatically
160 181 # synced to the source, so it can be queried at any time to obtain the code
161 182 # object; it will be None if the source doesn't compile to valid Python.
162 183 code = None
163 184 # Input mode
164 185 input_mode = 'append'
165 186
166 187 # Private attributes
167 188
168 189 # List with lines of input accumulated so far
169 190 _buffer = None
170 191 # Command compiler
171 192 _compile = None
172 193 # Mark when input has changed indentation all the way back to flush-left
173 194 _full_dedent = False
174 195 # Boolean indicating whether the current block is complete
175 196 _is_complete = None
176 197
177 198 def __init__(self, input_mode=None):
178 199 """Create a new InputSplitter instance.
179 200
180 201 Parameters
181 202 ----------
182 203 input_mode : str
183 204
184 205 One of 'append', 'replace', default is 'append'. This controls how
185 206 new inputs are used: in 'append' mode, they are appended to the
186 207 existing buffer and the whole buffer is compiled; in 'replace' mode,
187 208 each new input completely replaces all prior inputs. Replace mode is
188 209 thus equivalent to prepending a full reset() to every push() call.
189 210
190 211 In practice, line-oriented clients likely want to use 'append' mode
191 212 while block-oriented ones will want to use 'replace'.
192 213 """
193 214 self._buffer = []
194 215 self._compile = codeop.CommandCompiler()
195 216 self.encoding = get_input_encoding()
196 217 self.input_mode = InputSplitter.input_mode if input_mode is None \
197 218 else input_mode
198 219
199 220 def reset(self):
200 221 """Reset the input buffer and associated state."""
201 222 self.indent_spaces = 0
202 223 self._buffer[:] = []
203 224 self.source = ''
204 225 self.code = None
205 226 self._is_complete = False
206 227 self._full_dedent = False
207 228
208 229 def source_reset(self):
209 230 """Return the input source and perform a full reset.
210 231 """
211 232 out = self.source
212 233 self.reset()
213 234 return out
214 235
215 236 def push(self, lines):
216 237 """Push one ore more lines of input.
217 238
218 239 This stores the given lines and returns a status code indicating
219 240 whether the code forms a complete Python block or not.
220 241
221 242 Any exceptions generated in compilation are swallowed, but if an
222 243 exception was produced, the method returns True.
223 244
224 245 Parameters
225 246 ----------
226 247 lines : string
227 248 One or more lines of Python input.
228 249
229 250 Returns
230 251 -------
231 252 is_complete : boolean
232 253 True if the current input source (the result of the current input
233 254 plus prior inputs) forms a complete Python execution block. Note that
234 255 this value is also stored as a private attribute (_is_complete), so it
235 256 can be queried at any time.
236 257 """
237 258 if self.input_mode == 'replace':
238 259 self.reset()
239 260
240 261 # If the source code has leading blanks, add 'if 1:\n' to it
241 262 # this allows execution of indented pasted code. It is tempting
242 263 # to add '\n' at the end of source to run commands like ' a=1'
243 264 # directly, but this fails for more complicated scenarios
244 265 if not self._buffer and lines[:1] in [' ', '\t']:
245 266 lines = 'if 1:\n%s' % lines
246 267
247 268 self._store(lines)
248 269 source = self.source
249 270
250 271 # Before calling _compile(), reset the code object to None so that if an
251 272 # exception is raised in compilation, we don't mislead by having
252 273 # inconsistent code/source attributes.
253 274 self.code, self._is_complete = None, None
254 275
255 276 self._update_indent(lines)
256 277 try:
257 278 self.code = self._compile(source)
258 279 # Invalid syntax can produce any of a number of different errors from
259 280 # inside the compiler, so we have to catch them all. Syntax errors
260 281 # immediately produce a 'ready' block, so the invalid Python can be
261 282 # sent to the kernel for evaluation with possible ipython
262 283 # special-syntax conversion.
263 284 except (SyntaxError, OverflowError, ValueError, TypeError,
264 285 MemoryError):
265 286 self._is_complete = True
266 287 else:
267 288 # Compilation didn't produce any exceptions (though it may not have
268 289 # given a complete code object)
269 290 self._is_complete = self.code is not None
270 291
271 292 return self._is_complete
272 293
273 294 def push_accepts_more(self):
274 295 """Return whether a block of interactive input can accept more input.
275 296
276 297 This method is meant to be used by line-oriented frontends, who need to
277 298 guess whether a block is complete or not based solely on prior and
278 299 current input lines. The InputSplitter considers it has a complete
279 300 interactive block and will not accept more input only when either a
280 301 SyntaxError is raised, or *all* of the following are true:
281 302
282 303 1. The input compiles to a complete statement.
283 304
284 305 2. The indentation level is flush-left (because if we are indented,
285 306 like inside a function definition or for loop, we need to keep
286 307 reading new input).
287 308
288 309 3. There is one extra line consisting only of whitespace.
289 310
290 311 Because of condition #3, this method should be used only by
291 312 *line-oriented* frontends, since it means that intermediate blank lines
292 313 are not allowed in function definitions (or any other indented block).
293 314
294 315 Block-oriented frontends that have a separate keyboard event to
295 316 indicate execution should use the :meth:`split_blocks` method instead.
296 317
297 318 If the current input produces a syntax error, this method immediately
298 319 returns False but does *not* raise the syntax error exception, as
299 320 typically clients will want to send invalid syntax to an execution
300 321 backend which might convert the invalid syntax into valid Python via
301 322 one of the dynamic IPython mechanisms.
302 323 """
303 324
304 325 if not self._is_complete:
305 326 return True
306 327
307 328 if self.indent_spaces==0:
308 329 return False
309 330
310 331 last_line = self.source.splitlines()[-1]
311 332 return bool(last_line and not last_line.isspace())
312 333
313 334 def split_blocks(self, lines):
314 335 """Split a multiline string into multiple input blocks.
315 336
316 337 Note: this method starts by performing a full reset().
317 338
318 339 Parameters
319 340 ----------
320 341 lines : str
321 342 A possibly multiline string.
322 343
323 344 Returns
324 345 -------
325 346 blocks : list
326 347 A list of strings, each possibly multiline. Each string corresponds
327 348 to a single block that can be compiled in 'single' mode (unless it
328 349 has a syntax error)."""
329 350
330 351 # This code is fairly delicate. If you make any changes here, make
331 352 # absolutely sure that you do run the full test suite and ALL tests
332 353 # pass.
333 354
334 355 self.reset()
335 356 blocks = []
336 357
337 358 # Reversed copy so we can use pop() efficiently and consume the input
338 359 # as a stack
339 360 lines = lines.splitlines()[::-1]
340 361 # Outer loop over all input
341 362 while lines:
342 363 # Inner loop to build each block
343 364 while True:
344 365 # Safety exit from inner loop
345 366 if not lines:
346 367 break
347 368 # Grab next line but don't push it yet
348 369 next_line = lines.pop()
349 370 # Blank/empty lines are pushed as-is
350 371 if not next_line or next_line.isspace():
351 372 self.push(next_line)
352 373 continue
353 374
354 375 # Check indentation changes caused by the *next* line
355 376 indent_spaces, _full_dedent = self._find_indent(next_line)
356 377
357 378 # If the next line causes a dedent, it can be for two differnt
358 379 # reasons: either an explicit de-dent by the user or a
359 380 # return/raise/pass statement. These MUST be handled
360 381 # separately:
361 382 #
362 383 # 1. the first case is only detected when the actual explicit
363 384 # dedent happens, and that would be the *first* line of a *new*
364 385 # block. Thus, we must put the line back into the input buffer
365 386 # so that it starts a new block on the next pass.
366 387 #
367 388 # 2. the second case is detected in the line before the actual
368 389 # dedent happens, so , we consume the line and we can break out
369 390 # to start a new block.
370 391
371 392 # Case 1, explicit dedent causes a break
372 393 if _full_dedent and not next_line.startswith(' '):
373 394 lines.append(next_line)
374 395 break
375 396
376 397 # Otherwise any line is pushed
377 398 self.push(next_line)
378 399
379 400 # Case 2, full dedent with full block ready:
380 401 if _full_dedent or \
381 402 self.indent_spaces==0 and not self.push_accepts_more():
382 403 break
383 404 # Form the new block with the current source input
384 405 blocks.append(self.source_reset())
385 406
386 407 return blocks
387 408
388 409 #------------------------------------------------------------------------
389 410 # Private interface
390 411 #------------------------------------------------------------------------
391 412
392 413 def _find_indent(self, line):
393 414 """Compute the new indentation level for a single line.
394 415
395 416 Parameters
396 417 ----------
397 418 line : str
398 419 A single new line of non-whitespace, non-comment Python input.
399 420
400 421 Returns
401 422 -------
402 423 indent_spaces : int
403 424 New value for the indent level (it may be equal to self.indent_spaces
404 425 if indentation doesn't change.
405 426
406 427 full_dedent : boolean
407 428 Whether the new line causes a full flush-left dedent.
408 429 """
409 430 indent_spaces = self.indent_spaces
410 431 full_dedent = self._full_dedent
411 432
412 433 inisp = num_ini_spaces(line)
413 434 if inisp < indent_spaces:
414 435 indent_spaces = inisp
415 436 if indent_spaces <= 0:
416 437 #print 'Full dedent in text',self.source # dbg
417 438 full_dedent = True
418 439
419 440 if line[-1] == ':':
420 441 indent_spaces += 4
421 442 elif dedent_re.match(line):
422 443 indent_spaces -= 4
423 444 if indent_spaces <= 0:
424 445 full_dedent = True
425 446
426 447 # Safety
427 448 if indent_spaces < 0:
428 449 indent_spaces = 0
429 450 #print 'safety' # dbg
430 451
431 452 return indent_spaces, full_dedent
432 453
433 454 def _update_indent(self, lines):
434 455 for line in remove_comments(lines).splitlines():
435 456 if line and not line.isspace():
436 457 self.indent_spaces, self._full_dedent = self._find_indent(line)
437 458
438 459 def _store(self, lines):
439 460 """Store one or more lines of input.
440 461
441 462 If input lines are not newline-terminated, a newline is automatically
442 463 appended."""
443 464
444 465 if lines.endswith('\n'):
445 466 self._buffer.append(lines)
446 467 else:
447 468 self._buffer.append(lines+'\n')
448 469 self._set_source()
449 470
450 471 def _set_source(self):
451 472 self.source = ''.join(self._buffer).encode(self.encoding)
452 473
453 474
454 475 #-----------------------------------------------------------------------------
455 476 # Functions and classes for IPython-specific syntactic support
456 477 #-----------------------------------------------------------------------------
457 478
458 479 # RegExp for splitting line contents into pre-char//first word-method//rest.
459 480 # For clarity, each group in on one line.
460 481
461 482 line_split = re.compile("""
462 483 ^(\s*) # any leading space
463 484 ([,;/%]|!!?|\?\??) # escape character or characters
464 485 \s*([\w\.]*) # function/method part (mix of \w and '.')
465 486 (\s+.*$|$) # rest of line
466 487 """, re.VERBOSE)
467 488
468 489
469 490 def split_user_input(line):
470 491 """Split user input into early whitespace, esc-char, function part and rest.
471 492
472 493 This is currently handles lines with '=' in them in a very inconsistent
473 494 manner.
474 495
475 496 Examples
476 497 ========
477 498 >>> split_user_input('x=1')
478 499 ('', '', 'x=1', '')
479 500 >>> split_user_input('?')
480 501 ('', '?', '', '')
481 502 >>> split_user_input('??')
482 503 ('', '??', '', '')
483 504 >>> split_user_input(' ?')
484 505 (' ', '?', '', '')
485 506 >>> split_user_input(' ??')
486 507 (' ', '??', '', '')
487 508 >>> split_user_input('??x')
488 509 ('', '??', 'x', '')
489 510 >>> split_user_input('?x=1')
490 511 ('', '', '?x=1', '')
491 512 >>> split_user_input('!ls')
492 513 ('', '!', 'ls', '')
493 514 >>> split_user_input(' !ls')
494 515 (' ', '!', 'ls', '')
495 516 >>> split_user_input('!!ls')
496 517 ('', '!!', 'ls', '')
497 518 >>> split_user_input(' !!ls')
498 519 (' ', '!!', 'ls', '')
499 520 >>> split_user_input(',ls')
500 521 ('', ',', 'ls', '')
501 522 >>> split_user_input(';ls')
502 523 ('', ';', 'ls', '')
503 524 >>> split_user_input(' ;ls')
504 525 (' ', ';', 'ls', '')
505 526 >>> split_user_input('f.g(x)')
506 527 ('', '', 'f.g(x)', '')
507 528 >>> split_user_input('f.g (x)')
508 529 ('', '', 'f.g', '(x)')
509 530 """
510 531 match = line_split.match(line)
511 532 if match:
512 533 lspace, esc, fpart, rest = match.groups()
513 534 else:
514 535 # print "match failed for line '%s'" % line
515 536 try:
516 fpart, rest = line.split(None,1)
537 fpart, rest = line.split(None, 1)
517 538 except ValueError:
518 539 # print "split failed for line '%s'" % line
519 540 fpart, rest = line,''
520 lspace = re.match('^(\s*)(.*)',line).groups()[0]
541 lspace = re.match('^(\s*)(.*)', line).groups()[0]
521 542 esc = ''
522 543
523 544 # fpart has to be a valid python identifier, so it better be only pure
524 545 # ascii, no unicode:
525 546 try:
526 547 fpart = fpart.encode('ascii')
527 548 except UnicodeEncodeError:
528 549 lspace = unicode(lspace)
529 550 rest = fpart + u' ' + rest
530 551 fpart = u''
531 552
532 553 #print 'line:<%s>' % line # dbg
533 554 #print 'esc <%s> fpart <%s> rest <%s>' % (esc,fpart.strip(),rest) # dbg
534 555 return lspace, esc, fpart.strip(), rest.lstrip()
535 556
536 557
537 558 # The escaped translators ALL receive a line where their own escape has been
538 559 # stripped. Only '?' is valid at the end of the line, all others can only be
539 560 # placed at the start.
540 561
541 562 class LineInfo(object):
542 563 """A single line of input and associated info.
543 564
544 565 This is a utility class that mostly wraps the output of
545 566 :func:`split_user_input` into a convenient object to be passed around
546 567 during input transformations.
547 568
548 569 Includes the following as properties:
549 570
550 571 line
551 572 The original, raw line
552 573
553 574 lspace
554 575 Any early whitespace before actual text starts.
555 576
556 577 esc
557 578 The initial esc character (or characters, for double-char escapes like
558 579 '??' or '!!').
559 580
560 pre_char
561 The escape character(s) in esc or the empty string if there isn't one.
562
563 581 fpart
564 582 The 'function part', which is basically the maximal initial sequence
565 583 of valid python identifiers and the '.' character. This is what is
566 584 checked for alias and magic transformations, used for auto-calling,
567 585 etc.
568 586
569 587 rest
570 588 Everything else on the line.
571 589 """
572 590 def __init__(self, line):
573 591 self.line = line
574 592 self.lspace, self.esc, self.fpart, self.rest = \
575 593 split_user_input(line)
576 594
577 595 def __str__(self):
578 596 return "LineInfo [%s|%s|%s|%s]" % (self.lspace, self.esc,
579 597 self.fpart, self.rest)
580 598
581 599
582 600 # Transformations of the special syntaxes that don't rely on an explicit escape
583 601 # character but instead on patterns on the input line
584 602
585 603 # The core transformations are implemented as standalone functions that can be
586 604 # tested and validated in isolation. Each of these uses a regexp, we
587 605 # pre-compile these and keep them close to each function definition for clarity
588 606
589 607 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
590 608 r'\s*=\s*!\s*(?P<cmd>.*)')
591 609
592 610 def transform_assign_system(line):
593 611 """Handle the `files = !ls` syntax."""
594 612 # FIXME: This transforms the line to use %sc, but we've listed that magic
595 613 # as deprecated. We should then implement this functionality in a
596 614 # standalone api that we can transform to, without going through a
597 615 # deprecated magic.
598 616 m = _assign_system_re.match(line)
599 617 if m is not None:
600 618 cmd = m.group('cmd')
601 619 lhs = m.group('lhs')
602 620 expr = make_quoted_expr("sc -l = %s" % cmd)
603 621 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
604 622 return new_line
605 623 return line
606 624
607 625
608 626 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
609 627 r'\s*=\s*%\s*(?P<cmd>.*)')
610 628
611 629 def transform_assign_magic(line):
612 630 """Handle the `a = %who` syntax."""
613 631 m = _assign_magic_re.match(line)
614 632 if m is not None:
615 633 cmd = m.group('cmd')
616 634 lhs = m.group('lhs')
617 635 expr = make_quoted_expr(cmd)
618 636 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
619 637 return new_line
620 638 return line
621 639
622 640
623 641 _classic_prompt_re = re.compile(r'^([ \t]*>>> |^[ \t]*\.\.\. )')
624 642
625 643 def transform_classic_prompt(line):
626 644 """Handle inputs that start with '>>> ' syntax."""
627 645
628 646 if not line or line.isspace():
629 647 return line
630 648 m = _classic_prompt_re.match(line)
631 649 if m:
632 650 return line[len(m.group(0)):]
633 651 else:
634 652 return line
635 653
636 654
637 655 _ipy_prompt_re = re.compile(r'^([ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
638 656
639 657 def transform_ipy_prompt(line):
640 658 """Handle inputs that start classic IPython prompt syntax."""
641 659
642 660 if not line or line.isspace():
643 661 return line
644 662 m = _ipy_prompt_re.match(line)
645 663 if m:
646 664 return line[len(m.group(0)):]
647 665 else:
648 666 return line
649 667
650 668
651 def transform_unescaped(line):
652 """Transform lines that are explicitly escaped out.
653
654 This calls to the above transform_* functions for the actual line
655 translations.
669 class EscapedTransformer(object):
670 """Class to transform lines that are explicitly escaped out."""
656 671
657 Parameters
658 ----------
659 line : str
660 A single line of input to be transformed.
661
662 Returns
663 -------
664 new_line : str
665 Transformed line, which may be identical to the original."""
672 def __init__(self):
673 tr = { ESC_SHELL : self.tr_system,
674 ESC_SH_CAP : self.tr_system2,
675 ESC_HELP : self.tr_help,
676 ESC_HELP2 : self.tr_help,
677 ESC_MAGIC : self.tr_magic,
678 ESC_QUOTE : self.tr_quote,
679 ESC_QUOTE2 : self.tr_quote2,
680 ESC_PAREN : self.tr_paren }
681 self.tr = tr
682
683 # Support for syntax transformations that use explicit escapes typed by the
684 # user at the beginning of a line
685 @staticmethod
686 def tr_system(line_info):
687 "Translate lines escaped with: !"
688 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
689 return '%sget_ipython().system(%s)' % (line_info.lspace,
690 make_quoted_expr(cmd))
691
692 @staticmethod
693 def tr_system2(line_info):
694 "Translate lines escaped with: !!"
695 cmd = line_info.line.lstrip()[2:]
696 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
697 make_quoted_expr(cmd))
698
699 @staticmethod
700 def tr_help(line_info):
701 "Translate lines escaped with: ?/??"
702 # A naked help line should just fire the intro help screen
703 if not line_info.line[1:]:
704 return 'get_ipython().show_usage()'
705
706 # There may be one or two '?' at the end, move them to the front so that
707 # the rest of the logic can assume escapes are at the start
708 line = line_info.line
709 if line.endswith('?'):
710 line = line[-1] + line[:-1]
711 if line.endswith('?'):
712 line = line[-1] + line[:-1]
713 line_info = LineInfo(line)
714
715 # From here on, simply choose which level of detail to get.
716 if line_info.esc == '?':
717 pinfo = 'pinfo'
718 elif line_info.esc == '??':
719 pinfo = 'pinfo2'
720
721 tpl = '%sget_ipython().magic("%s %s")'
722 return tpl % (line_info.lspace, pinfo,
723 ' '.join([line_info.fpart, line_info.rest]).strip())
724
725 @staticmethod
726 def tr_magic(line_info):
727 "Translate lines escaped with: %"
728 tpl = '%sget_ipython().magic(%s)'
729 cmd = make_quoted_expr(' '.join([line_info.fpart,
730 line_info.rest])).strip()
731 return tpl % (line_info.lspace, cmd)
732
733 @staticmethod
734 def tr_quote(line_info):
735 "Translate lines escaped with: ,"
736 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
737 '", "'.join(line_info.rest.split()) )
738
739 @staticmethod
740 def tr_quote2(line_info):
741 "Translate lines escaped with: ;"
742 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
743 line_info.rest)
744
745 @staticmethod
746 def tr_paren(line_info):
747 "Translate lines escaped with: /"
748 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
749 ", ".join(line_info.rest.split()))
750
751 def __call__(self, line):
752 """Class to transform lines that are explicitly escaped out.
753
754 This calls the above tr_* static methods for the actual line
755 translations."""
756
757 # Empty lines just get returned unmodified
758 if not line or line.isspace():
759 return line
666 760
667 if not line or line.isspace():
668 return line
761 # Get line endpoints, where the escapes can be
762 line_info = LineInfo(line)
669 763
670 new_line = line
671 for f in [transform_assign_system, transform_assign_magic,
672 transform_classic_prompt, transform_ipy_prompt ] :
673 new_line = f(new_line)
674 return new_line
675
676 # Support for syntax transformations that use explicit escapes typed by the
677 # user at the beginning of a line
678
679 def tr_system(line_info):
680 "Translate lines escaped with: !"
681 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
682 return '%sget_ipython().system(%s)' % (line_info.lspace,
683 make_quoted_expr(cmd))
684
685
686 def tr_system2(line_info):
687 "Translate lines escaped with: !!"
688 cmd = line_info.line.lstrip()[2:]
689 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
690 make_quoted_expr(cmd))
691
692
693 def tr_help(line_info):
694 "Translate lines escaped with: ?/??"
695 # A naked help line should just fire the intro help screen
696 if not line_info.line[1:]:
697 return 'get_ipython().show_usage()'
698
699 # There may be one or two '?' at the end, move them to the front so that
700 # the rest of the logic can assume escapes are at the start
701 line = line_info.line
702 if line.endswith('?'):
703 line = line[-1] + line[:-1]
704 if line.endswith('?'):
705 line = line[-1] + line[:-1]
706 line_info = LineInfo(line)
707
708 # From here on, simply choose which level of detail to get.
709 if line_info.esc == '?':
710 pinfo = 'pinfo'
711 elif line_info.esc == '??':
712 pinfo = 'pinfo2'
713
714 tpl = '%sget_ipython().magic("%s %s")'
715 return tpl % (line_info.lspace, pinfo,
716 ' '.join([line_info.fpart, line_info.rest]).strip())
717
718
719 def tr_magic(line_info):
720 "Translate lines escaped with: %"
721 tpl = '%sget_ipython().magic(%s)'
722 cmd = make_quoted_expr(' '.join([line_info.fpart,
723 line_info.rest])).strip()
724 return tpl % (line_info.lspace, cmd)
725
726
727 def tr_quote(line_info):
728 "Translate lines escaped with: ,"
729 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
730 '", "'.join(line_info.rest.split()) )
731
732
733 def tr_quote2(line_info):
734 "Translate lines escaped with: ;"
735 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
736 line_info.rest)
737
738
739 def tr_paren(line_info):
740 "Translate lines escaped with: /"
741 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
742 ", ".join(line_info.rest.split()))
743
744
745 def transform_escaped(line):
746 """Transform lines that are explicitly escaped out.
747
748 This calls to the above tr_* functions for the actual line translations."""
749
750 tr = { ESC_SHELL : tr_system,
751 ESC_SH_CAP : tr_system2,
752 ESC_HELP : tr_help,
753 ESC_HELP2 : tr_help,
754 ESC_MAGIC : tr_magic,
755 ESC_QUOTE : tr_quote,
756 ESC_QUOTE2 : tr_quote2,
757 ESC_PAREN : tr_paren }
758
759 # Empty lines just get returned unmodified
760 if not line or line.isspace():
761 return line
764 # If the escape is not at the start, only '?' needs to be special-cased.
765 # All other escapes are only valid at the start
766 if not line_info.esc in self.tr:
767 if line.endswith(ESC_HELP):
768 return self.tr_help(line_info)
769 else:
770 # If we don't recognize the escape, don't modify the line
771 return line
762 772
763 # Get line endpoints, where the escapes can be
764 line_info = LineInfo(line)
773 return self.tr[line_info.esc](line_info)
765 774
766 # If the escape is not at the start, only '?' needs to be special-cased.
767 # All other escapes are only valid at the start
768 if not line_info.esc in tr:
769 if line.endswith(ESC_HELP):
770 return tr_help(line_info)
771 else:
772 # If we don't recognize the escape, don't modify the line
773 return line
774
775 return tr[line_info.esc](line_info)
775 # A function-looking object to be used by the rest of the code. The purpose of
776 # the class in this case is to organize related functionality, more than to
777 # manage state.
778 transform_escaped = EscapedTransformer()
776 779
777 780
778 781 class IPythonInputSplitter(InputSplitter):
779 782 """An input splitter that recognizes all of IPython's special syntax."""
780 783
781 784 def push(self, lines):
782 785 """Push one or more lines of IPython input.
783 786 """
787 if not lines:
788 return super(IPythonInputSplitter, self).push(lines)
789
790 lines_list = lines.splitlines()
791
792 transforms = [transform_escaped, transform_assign_system,
793 transform_assign_magic, transform_ipy_prompt,
794 transform_classic_prompt]
795
796 # Transform logic
797 #
784 798 # We only apply the line transformers to the input if we have either no
785 # input yet, or complete input. This prevents the accidental
799 # input yet, or complete input, or if the last line of the buffer ends
800 # with ':' (opening an indented block). This prevents the accidental
786 801 # transformation of escapes inside multiline expressions like
787 802 # triple-quoted strings or parenthesized expressions.
788 lines_list = lines.splitlines()
789 if self._is_complete or not self._buffer:
803 #
804 # The last heuristic, while ugly, ensures that the first line of an
805 # indented block is correctly transformed.
806 #
807 # FIXME: try to find a cleaner approach for this last bit.
808
809 for line in lines_list:
810 if self._is_complete or not self._buffer or \
811 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
812 for f in transforms:
813 line = f(line)
790 814
791 new_list = map(transform_escaped, lines_list)
792 else:
793 new_list = lines_list
815 out = super(IPythonInputSplitter, self).push(line)
794 816
795 # Now apply the unescaped transformations to each input line
796 new_list = map(transform_unescaped, new_list)
797 newlines = '\n'.join(new_list)
798 return super(IPythonInputSplitter, self).push(newlines)
817 return out
@@ -1,623 +1,621 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the inputsplitter module.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2010 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14 # stdlib
15 15 import unittest
16 16 import sys
17 17
18 18 # Third party
19 19 import nose.tools as nt
20 20
21 21 # Our own
22 22 from IPython.core import inputsplitter as isp
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Semi-complete examples (also used as tests)
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Note: at the bottom, there's a slightly more complete version of this that
29 29 # can be useful during development of code here.
30 30
31 31 def mini_interactive_loop(raw_input):
32 32 """Minimal example of the logic of an interactive interpreter loop.
33 33
34 34 This serves as an example, and it is used by the test system with a fake
35 35 raw_input that simulates interactive input."""
36 36
37 37 from IPython.core.inputsplitter import InputSplitter
38 38
39 39 isp = InputSplitter()
40 40 # In practice, this input loop would be wrapped in an outside loop to read
41 41 # input indefinitely, until some exit/quit command was issued. Here we
42 42 # only illustrate the basic inner loop.
43 43 while isp.push_accepts_more():
44 44 indent = ' '*isp.indent_spaces
45 45 prompt = '>>> ' + indent
46 46 line = indent + raw_input(prompt)
47 47 isp.push(line)
48 48
49 49 # Here we just return input so we can use it in a test suite, but a real
50 50 # interpreter would instead send it for execution somewhere.
51 51 src = isp.source_reset()
52 52 #print 'Input source was:\n', src # dbg
53 53 return src
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Test utilities, just for local use
57 57 #-----------------------------------------------------------------------------
58 58
59 59 def assemble(block):
60 60 """Assemble a block into multi-line sub-blocks."""
61 61 return ['\n'.join(sub_block)+'\n' for sub_block in block]
62 62
63 63
64 64 def pseudo_input(lines):
65 65 """Return a function that acts like raw_input but feeds the input list."""
66 66 ilines = iter(lines)
67 67 def raw_in(prompt):
68 68 try:
69 69 return next(ilines)
70 70 except StopIteration:
71 71 return ''
72 72 return raw_in
73 73
74 74 #-----------------------------------------------------------------------------
75 75 # Tests
76 76 #-----------------------------------------------------------------------------
77 77 def test_spaces():
78 78 tests = [('', 0),
79 79 (' ', 1),
80 80 ('\n', 0),
81 81 (' \n', 1),
82 82 ('x', 0),
83 83 (' x', 1),
84 84 (' x',2),
85 85 (' x',4),
86 86 # Note: tabs are counted as a single whitespace!
87 87 ('\tx', 1),
88 88 ('\t x', 2),
89 89 ]
90 90
91 91 for s, nsp in tests:
92 92 nt.assert_equal(isp.num_ini_spaces(s), nsp)
93 93
94 94
95 95 def test_remove_comments():
96 96 tests = [('text', 'text'),
97 97 ('text # comment', 'text '),
98 98 ('text # comment\n', 'text \n'),
99 99 ('text # comment \n', 'text \n'),
100 100 ('line # c \nline\n','line \nline\n'),
101 101 ('line # c \nline#c2 \nline\nline #c\n\n',
102 102 'line \nline\nline\nline \n\n'),
103 103 ]
104 104
105 105 for inp, out in tests:
106 106 nt.assert_equal(isp.remove_comments(inp), out)
107 107
108 108
109 109 def test_get_input_encoding():
110 110 encoding = isp.get_input_encoding()
111 111 nt.assert_true(isinstance(encoding, basestring))
112 112 # simple-minded check that at least encoding a simple string works with the
113 113 # encoding we got.
114 114 nt.assert_equal('test'.encode(encoding), 'test')
115 115
116 116
117 117 class NoInputEncodingTestCase(unittest.TestCase):
118 118 def setUp(self):
119 119 self.old_stdin = sys.stdin
120 120 class X: pass
121 121 fake_stdin = X()
122 122 sys.stdin = fake_stdin
123 123
124 124 def test(self):
125 125 # Verify that if sys.stdin has no 'encoding' attribute we do the right
126 126 # thing
127 127 enc = isp.get_input_encoding()
128 128 self.assertEqual(enc, 'ascii')
129 129
130 130 def tearDown(self):
131 131 sys.stdin = self.old_stdin
132 132
133 133
134 134 class InputSplitterTestCase(unittest.TestCase):
135 135 def setUp(self):
136 136 self.isp = isp.InputSplitter()
137 137
138 138 def test_reset(self):
139 139 isp = self.isp
140 140 isp.push('x=1')
141 141 isp.reset()
142 142 self.assertEqual(isp._buffer, [])
143 143 self.assertEqual(isp.indent_spaces, 0)
144 144 self.assertEqual(isp.source, '')
145 145 self.assertEqual(isp.code, None)
146 146 self.assertEqual(isp._is_complete, False)
147 147
148 148 def test_source(self):
149 149 self.isp._store('1')
150 150 self.isp._store('2')
151 151 self.assertEqual(self.isp.source, '1\n2\n')
152 152 self.assertTrue(len(self.isp._buffer)>0)
153 153 self.assertEqual(self.isp.source_reset(), '1\n2\n')
154 154 self.assertEqual(self.isp._buffer, [])
155 155 self.assertEqual(self.isp.source, '')
156 156
157 157 def test_indent(self):
158 158 isp = self.isp # shorthand
159 159 isp.push('x=1')
160 160 self.assertEqual(isp.indent_spaces, 0)
161 161 isp.push('if 1:\n x=1')
162 162 self.assertEqual(isp.indent_spaces, 4)
163 163 isp.push('y=2\n')
164 164 self.assertEqual(isp.indent_spaces, 0)
165 165 isp.push('if 1:')
166 166 self.assertEqual(isp.indent_spaces, 4)
167 167 isp.push(' x=1')
168 168 self.assertEqual(isp.indent_spaces, 4)
169 169 # Blank lines shouldn't change the indent level
170 170 isp.push(' '*2)
171 171 self.assertEqual(isp.indent_spaces, 4)
172 172
173 173 def test_indent2(self):
174 174 isp = self.isp
175 175 # When a multiline statement contains parens or multiline strings, we
176 176 # shouldn't get confused.
177 177 isp.push("if 1:")
178 178 isp.push(" x = (1+\n 2)")
179 179 self.assertEqual(isp.indent_spaces, 4)
180 180
181 181 def test_dedent(self):
182 182 isp = self.isp # shorthand
183 183 isp.push('if 1:')
184 184 self.assertEqual(isp.indent_spaces, 4)
185 185 isp.push(' pass')
186 186 self.assertEqual(isp.indent_spaces, 0)
187 187
188 188 def test_push(self):
189 189 isp = self.isp
190 190 self.assertTrue(isp.push('x=1'))
191 191
192 192 def test_push2(self):
193 193 isp = self.isp
194 194 self.assertFalse(isp.push('if 1:'))
195 195 for line in [' x=1', '# a comment', ' y=2']:
196 196 self.assertTrue(isp.push(line))
197 197
198 198 def test_push3(self):
199 199 """Test input with leading whitespace"""
200 200 isp = self.isp
201 201 isp.push(' x=1')
202 202 isp.push(' y=2')
203 203 self.assertEqual(isp.source, 'if 1:\n x=1\n y=2\n')
204 204
205 205 def test_replace_mode(self):
206 206 isp = self.isp
207 207 isp.input_mode = 'replace'
208 208 isp.push('x=1')
209 209 self.assertEqual(isp.source, 'x=1\n')
210 210 isp.push('x=2')
211 211 self.assertEqual(isp.source, 'x=2\n')
212 212
213 213 def test_push_accepts_more(self):
214 214 isp = self.isp
215 215 isp.push('x=1')
216 216 self.assertFalse(isp.push_accepts_more())
217 217
218 218 def test_push_accepts_more2(self):
219 219 isp = self.isp
220 220 isp.push('if 1:')
221 221 self.assertTrue(isp.push_accepts_more())
222 222 isp.push(' x=1')
223 223 self.assertTrue(isp.push_accepts_more())
224 224 isp.push('')
225 225 self.assertFalse(isp.push_accepts_more())
226 226
227 227 def test_push_accepts_more3(self):
228 228 isp = self.isp
229 229 isp.push("x = (2+\n3)")
230 230 self.assertFalse(isp.push_accepts_more())
231 231
232 232 def test_push_accepts_more4(self):
233 233 isp = self.isp
234 234 # When a multiline statement contains parens or multiline strings, we
235 235 # shouldn't get confused.
236 236 # FIXME: we should be able to better handle de-dents in statements like
237 237 # multiline strings and multiline expressions (continued with \ or
238 238 # parens). Right now we aren't handling the indentation tracking quite
239 239 # correctly with this, though in practice it may not be too much of a
240 240 # problem. We'll need to see.
241 241 isp.push("if 1:")
242 242 isp.push(" x = (2+")
243 243 isp.push(" 3)")
244 244 self.assertTrue(isp.push_accepts_more())
245 245 isp.push(" y = 3")
246 246 self.assertTrue(isp.push_accepts_more())
247 247 isp.push('')
248 248 self.assertFalse(isp.push_accepts_more())
249 249
250 250 def test_syntax_error(self):
251 251 isp = self.isp
252 252 # Syntax errors immediately produce a 'ready' block, so the invalid
253 253 # Python can be sent to the kernel for evaluation with possible ipython
254 254 # special-syntax conversion.
255 255 isp.push('run foo')
256 256 self.assertFalse(isp.push_accepts_more())
257 257
258 258 def check_split(self, block_lines, compile=True):
259 259 blocks = assemble(block_lines)
260 260 lines = ''.join(blocks)
261 261 oblock = self.isp.split_blocks(lines)
262 262 self.assertEqual(oblock, blocks)
263 263 if compile:
264 264 for block in blocks:
265 265 self.isp._compile(block)
266 266
267 267 def test_split(self):
268 268 # All blocks of input we want to test in a list. The format for each
269 269 # block is a list of lists, with each inner lists consisting of all the
270 270 # lines (as single-lines) that should make up a sub-block.
271 271
272 272 # Note: do NOT put here sub-blocks that don't compile, as the
273 273 # check_split() routine makes a final verification pass to check that
274 274 # each sub_block, as returned by split_blocks(), does compile
275 275 # correctly.
276 276 all_blocks = [ [['x=1']],
277 277
278 278 [['x=1'],
279 279 ['y=2']],
280 280
281 281 [['x=1'],
282 282 ['# a comment'],
283 283 ['y=11']],
284 284
285 285 [['if 1:',
286 286 ' x=1'],
287 287 ['y=3']],
288 288
289 289 [['def f(x):',
290 290 ' return x'],
291 291 ['x=1']],
292 292
293 293 [['def f(x):',
294 294 ' x+=1',
295 295 ' ',
296 296 ' return x'],
297 297 ['x=1']],
298 298
299 299 [['def f(x):',
300 300 ' if x>0:',
301 301 ' y=1',
302 302 ' # a comment',
303 303 ' else:',
304 304 ' y=4',
305 305 ' ',
306 306 ' return y'],
307 307 ['x=1'],
308 308 ['if 1:',
309 309 ' y=11'] ],
310 310
311 311 [['for i in range(10):'
312 312 ' x=i**2']],
313 313
314 314 [['for i in range(10):'
315 315 ' x=i**2'],
316 316 ['z = 1']],
317 317 ]
318 318 for block_lines in all_blocks:
319 319 self.check_split(block_lines)
320 320
321 321 def test_split_syntax_errors(self):
322 322 # Block splitting with invalid syntax
323 323 all_blocks = [ [['a syntax error']],
324 324
325 325 [['x=1'],
326 326 ['a syntax error']],
327 327
328 328 [['for i in range(10):'
329 329 ' an error']],
330 330
331 331 ]
332 332 for block_lines in all_blocks:
333 333 self.check_split(block_lines, compile=False)
334 334
335 335
336 336 class InteractiveLoopTestCase(unittest.TestCase):
337 337 """Tests for an interactive loop like a python shell.
338 338 """
339 339 def check_ns(self, lines, ns):
340 340 """Validate that the given input lines produce the resulting namespace.
341 341
342 342 Note: the input lines are given exactly as they would be typed in an
343 343 auto-indenting environment, as mini_interactive_loop above already does
344 344 auto-indenting and prepends spaces to the input.
345 345 """
346 346 src = mini_interactive_loop(pseudo_input(lines))
347 347 test_ns = {}
348 348 exec src in test_ns
349 349 # We can't check that the provided ns is identical to the test_ns,
350 350 # because Python fills test_ns with extra keys (copyright, etc). But
351 351 # we can check that the given dict is *contained* in test_ns
352 352 for k,v in ns.items():
353 353 self.assertEqual(test_ns[k], v)
354 354
355 355 def test_simple(self):
356 356 self.check_ns(['x=1'], dict(x=1))
357 357
358 358 def test_simple2(self):
359 359 self.check_ns(['if 1:', 'x=2'], dict(x=2))
360 360
361 361 def test_xy(self):
362 362 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
363 363
364 364 def test_abc(self):
365 365 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
366 366
367 367 def test_multi(self):
368 368 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
369 369
370 370
371 371 def test_LineInfo():
372 372 """Simple test for LineInfo construction and str()"""
373 373 linfo = isp.LineInfo(' %cd /home')
374 374 nt.assert_equals(str(linfo), 'LineInfo [ |%|cd|/home]')
375 375
376 376
377 377 def test_split_user_input():
378 378 """Unicode test - split_user_input already has good doctests"""
379 379 line = u"PΓ©rez Fernando"
380 380 parts = isp.split_user_input(line)
381 381 parts_expected = (u'', u'', u'', line)
382 382 nt.assert_equal(parts, parts_expected)
383 383
384 384
385 385 # Transformer tests
386 386 def transform_checker(tests, func):
387 387 """Utility to loop over test inputs"""
388 388 for inp, tr in tests:
389 389 nt.assert_equals(func(inp), tr)
390 390
391 391 # Data for all the syntax tests in the form of lists of pairs of
392 392 # raw/transformed input. We store it here as a global dict so that we can use
393 393 # it both within single-function tests and also to validate the behavior of the
394 394 # larger objects
395 395
396 396 syntax = \
397 397 dict(assign_system =
398 398 [('a =! ls', 'a = get_ipython().magic("sc -l = ls")'),
399 399 ('b = !ls', 'b = get_ipython().magic("sc -l = ls")'),
400 400 ('x=1', 'x=1'), # normal input is unmodified
401 401 (' ',' '), # blank lines are kept intact
402 402 ],
403 403
404 404 assign_magic =
405 405 [('a =% who', 'a = get_ipython().magic("who")'),
406 406 ('b = %who', 'b = get_ipython().magic("who")'),
407 407 ('x=1', 'x=1'), # normal input is unmodified
408 408 (' ',' '), # blank lines are kept intact
409 409 ],
410 410
411 411 classic_prompt =
412 412 [('>>> x=1', 'x=1'),
413 413 ('x=1', 'x=1'), # normal input is unmodified
414 414 (' ',' '), # blank lines are kept intact
415 415 ],
416 416
417 417 ipy_prompt =
418 418 [('In [1]: x=1', 'x=1'),
419 419 ('x=1', 'x=1'), # normal input is unmodified
420 420 (' ',' '), # blank lines are kept intact
421 421 ],
422 422
423 423 # Tests for the escape transformer to leave normal code alone
424 424 escaped_noesc =
425 425 [ (' ', ' '),
426 426 ('x=1', 'x=1'),
427 427 ],
428 428
429 429 # System calls
430 430 escaped_shell =
431 431 [ ('!ls', 'get_ipython().system("ls")'),
432 432 # Double-escape shell, this means to capture the output of the
433 433 # subprocess and return it
434 434 ('!!ls', 'get_ipython().getoutput("ls")'),
435 435 ],
436 436
437 437 # Help/object info
438 438 escaped_help =
439 439 [ ('?', 'get_ipython().show_usage()'),
440 440 ('?x1', 'get_ipython().magic("pinfo x1")'),
441 441 ('??x2', 'get_ipython().magic("pinfo2 x2")'),
442 442 ('x3?', 'get_ipython().magic("pinfo x3")'),
443 443 ('x4??', 'get_ipython().magic("pinfo2 x4")'),
444 444 ],
445 445
446 446 # Explicit magic calls
447 447 escaped_magic =
448 448 [ ('%cd', 'get_ipython().magic("cd")'),
449 449 ('%cd /home', 'get_ipython().magic("cd /home")'),
450 450 (' %magic', ' get_ipython().magic("magic")'),
451 451 ],
452 452
453 453 # Quoting with separate arguments
454 454 escaped_quote =
455 455 [ (',f', 'f("")'),
456 456 (',f x', 'f("x")'),
457 457 (' ,f y', ' f("y")'),
458 458 (',f a b', 'f("a", "b")'),
459 459 ],
460 460
461 461 # Quoting with single argument
462 462 escaped_quote2 =
463 463 [ (';f', 'f("")'),
464 464 (';f x', 'f("x")'),
465 465 (' ;f y', ' f("y")'),
466 466 (';f a b', 'f("a b")'),
467 467 ],
468 468
469 469 # Simply apply parens
470 470 escaped_paren =
471 471 [ ('/f', 'f()'),
472 472 ('/f x', 'f(x)'),
473 473 (' /f y', ' f(y)'),
474 474 ('/f a b', 'f(a, b)'),
475 475 ],
476 476
477 477 # More complex multiline tests
478 478 ## escaped_multiline =
479 479 ## [()],
480 480 )
481 481
482 482 # multiline syntax examples. Each of these should be a list of lists, with
483 483 # each entry itself having pairs of raw/transformed input. The union (with
484 484 # '\n'.join() of the transformed inputs is what the splitter should produce
485 485 # when fed the raw lines one at a time via push.
486 486 syntax_ml = \
487 487 dict(classic_prompt =
488 488 [ [('>>> for i in range(10):','for i in range(10):'),
489 489 ('... print i',' print i'),
490 490 ('... ', ''),
491 491 ],
492 492 ],
493 493
494 494 ipy_prompt =
495 495 [ [('In [24]: for i in range(10):','for i in range(10):'),
496 496 (' ....: print i',' print i'),
497 497 (' ....: ', ''),
498 498 ],
499 499 ],
500 500 )
501 501
502 502
503 503 def test_assign_system():
504 504 transform_checker(syntax['assign_system'], isp.transform_assign_system)
505 505
506 506
507 507 def test_assign_magic():
508 508 transform_checker(syntax['assign_magic'], isp.transform_assign_magic)
509 509
510 510
511 511 def test_classic_prompt():
512 512 transform_checker(syntax['classic_prompt'], isp.transform_classic_prompt)
513 513 for example in syntax_ml['classic_prompt']:
514 514 transform_checker(example, isp.transform_classic_prompt)
515 515
516 516
517 517 def test_ipy_prompt():
518 518 transform_checker(syntax['ipy_prompt'], isp.transform_ipy_prompt)
519 519 for example in syntax_ml['ipy_prompt']:
520 520 transform_checker(example, isp.transform_ipy_prompt)
521 521
522 522
523 523 def test_escaped_noesc():
524 524 transform_checker(syntax['escaped_noesc'], isp.transform_escaped)
525 525
526 526
527 527 def test_escaped_shell():
528 528 transform_checker(syntax['escaped_shell'], isp.transform_escaped)
529 529
530 530
531 531 def test_escaped_help():
532 532 transform_checker(syntax['escaped_help'], isp.transform_escaped)
533 533
534 534
535 535 def test_escaped_magic():
536 536 transform_checker(syntax['escaped_magic'], isp.transform_escaped)
537 537
538 538
539 539 def test_escaped_quote():
540 540 transform_checker(syntax['escaped_quote'], isp.transform_escaped)
541 541
542 542
543 543 def test_escaped_quote2():
544 544 transform_checker(syntax['escaped_quote2'], isp.transform_escaped)
545 545
546 546
547 547 def test_escaped_paren():
548 548 transform_checker(syntax['escaped_paren'], isp.transform_escaped)
549 549
550 550
551 551 class IPythonInputTestCase(InputSplitterTestCase):
552 552 """By just creating a new class whose .isp is a different instance, we
553 553 re-run the same test battery on the new input splitter.
554 554
555 555 In addition, this runs the tests over the syntax and syntax_ml dicts that
556 556 were tested by individual functions, as part of the OO interface.
557 557 """
558 558 def setUp(self):
559 559 self.isp = isp.IPythonInputSplitter()
560 560
561 561 def test_syntax(self):
562 562 """Call all single-line syntax tests from the main object"""
563 563 isp = self.isp
564 564 for example in syntax.itervalues():
565 565 for raw, out_t in example:
566 566 if raw.startswith(' '):
567 567 continue
568 568
569 569 isp.push(raw)
570 570 out = isp.source_reset().rstrip()
571 571 self.assertEqual(out, out_t)
572 572
573 573 def test_syntax_multiline(self):
574 574 isp = self.isp
575 575 for example in syntax_ml.itervalues():
576 576 out_t_parts = []
577 577 for line_pairs in example:
578 578 for raw, out_t_part in line_pairs:
579 579 isp.push(raw)
580 580 out_t_parts.append(out_t_part)
581 581
582 582 out = isp.source_reset().rstrip()
583 583 out_t = '\n'.join(out_t_parts).rstrip()
584 584 self.assertEqual(out, out_t)
585 585
586 586
587 587 #-----------------------------------------------------------------------------
588 588 # Main - use as a script
589 589 #-----------------------------------------------------------------------------
590 590
591 591 if __name__ == '__main__':
592 592 # A simple demo for interactive experimentation. This code will not get
593 593 # picked up by any test suite. Useful mostly for illustration and during
594 594 # development.
595 595 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
596
596
597 # configure here the syntax to use, prompt and whether to autoindent
597 598 #isp, start_prompt = InputSplitter(), '>>> '
598 599 isp, start_prompt = IPythonInputSplitter(), 'In> '
599 600
600 601 autoindent = True
601 602 #autoindent = False
602 603
603 # In practice, this input loop would be wrapped in an outside loop to read
604 # input indefinitely, until some exit/quit command was issued. Here we
605 # only illustrate the basic inner loop.
606 604 try:
607 605 while True:
608 606 prompt = start_prompt
609 607 while isp.push_accepts_more():
610 608 indent = ' '*isp.indent_spaces
611 609 if autoindent:
612 610 line = indent + raw_input(prompt+indent)
613 611 else:
614 612 line = raw_input(prompt)
615 613 isp.push(line)
616 614 prompt = '... '
617 615
618 616 # Here we just return input so we can use it in a test suite, but a
619 617 # real interpreter would instead send it for execution somewhere.
620 618 src = isp.source_reset()
621 print 'Input source was:\n', src # dbg
619 print 'Input source was:\n', src
622 620 except EOFError:
623 621 print 'Bye'
General Comments 0
You need to be logged in to leave comments. Login now