##// END OF EJS Templates
Fix bug with IPythonInputSplitter in block input mode.
Fernando Perez -
Show More
@@ -1,839 +1,858 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 8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
9 9 with full support for the extended IPython syntax (magics, system calls, etc).
10 10
11 11 For more details, see the class docstring below.
12 12
13 13 Syntax Transformations
14 14 ----------------------
15 15
16 16 One of the main jobs of the code in this file is to apply all syntax
17 17 transformations that make up 'the IPython language', i.e. magics, shell
18 18 escapes, etc. All transformations should be implemented as *fully stateless*
19 19 entities, that simply take one line as their input and return a line.
20 20 Internally for implementation purposes they may be a normal function or a
21 21 callable object, but the only input they receive will be a single line and they
22 22 should only return a line, without holding any data-dependent state between
23 23 calls.
24 24
25 25 As an example, the EscapedTransformer is a class so we can more clearly group
26 26 together the functionality of dispatching to individual functions based on the
27 27 starting escape character, but the only method for public use is its call
28 28 method.
29 29
30 30
31 31 ToDo
32 32 ----
33 33
34 34 - Should we make push() actually raise an exception once push_accepts_more()
35 35 returns False?
36 36
37 37 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
38 38 at least just attributes of a class so not really very exposed.
39 39
40 40 - Think about the best way to support dynamic things: automagic, autocall,
41 41 macros, etc.
42 42
43 43 - Think of a better heuristic for the application of the transforms in
44 44 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
45 45 track indentation change events (indent, dedent, nothing) and apply them only
46 46 if the indentation went up, but not otherwise.
47 47
48 48 - Think of the cleanest way for supporting user-specified transformations (the
49 49 user prefilters we had before).
50 50
51 51 Authors
52 52 -------
53 53
54 54 * Fernando Perez
55 55 * Brian Granger
56 56 """
57 57 #-----------------------------------------------------------------------------
58 58 # Copyright (C) 2010 The IPython Development Team
59 59 #
60 60 # Distributed under the terms of the BSD License. The full license is in
61 61 # the file COPYING, distributed as part of this software.
62 62 #-----------------------------------------------------------------------------
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Imports
66 66 #-----------------------------------------------------------------------------
67 67 # stdlib
68 68 import codeop
69 69 import re
70 70 import sys
71 71
72 72 # IPython modules
73 73 from IPython.utils.text import make_quoted_expr
74
75 74 #-----------------------------------------------------------------------------
76 75 # Globals
77 76 #-----------------------------------------------------------------------------
78 77
79 78 # The escape sequences that define the syntax transformations IPython will
80 79 # apply to user input. These can NOT be just changed here: many regular
81 80 # expressions and other parts of the code may use their hardcoded values, and
82 81 # for all intents and purposes they constitute the 'IPython syntax', so they
83 82 # should be considered fixed.
84 83
85 84 ESC_SHELL = '!'
86 85 ESC_SH_CAP = '!!'
87 86 ESC_HELP = '?'
88 87 ESC_HELP2 = '??'
89 88 ESC_MAGIC = '%'
90 89 ESC_QUOTE = ','
91 90 ESC_QUOTE2 = ';'
92 91 ESC_PAREN = '/'
93 92
94 93 #-----------------------------------------------------------------------------
95 94 # Utilities
96 95 #-----------------------------------------------------------------------------
97 96
98 97 # FIXME: These are general-purpose utilities that later can be moved to the
99 98 # general ward. Kept here for now because we're being very strict about test
100 99 # coverage with this code, and this lets us ensure that we keep 100% coverage
101 100 # while developing.
102 101
103 102 # compiled regexps for autoindent management
104 103 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
105 104 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
106 105
107 106
108 107 def num_ini_spaces(s):
109 108 """Return the number of initial spaces in a string.
110 109
111 110 Note that tabs are counted as a single space. For now, we do *not* support
112 111 mixing of tabs and spaces in the user's input.
113 112
114 113 Parameters
115 114 ----------
116 115 s : string
117 116
118 117 Returns
119 118 -------
120 119 n : int
121 120 """
122 121
123 122 ini_spaces = ini_spaces_re.match(s)
124 123 if ini_spaces:
125 124 return ini_spaces.end()
126 125 else:
127 126 return 0
128 127
129 128
130 129 def remove_comments(src):
131 130 """Remove all comments from input source.
132 131
133 132 Note: comments are NOT recognized inside of strings!
134 133
135 134 Parameters
136 135 ----------
137 136 src : string
138 137 A single or multiline input string.
139 138
140 139 Returns
141 140 -------
142 141 String with all Python comments removed.
143 142 """
144 143
145 144 return re.sub('#.*', '', src)
146 145
147 146
148 147 def get_input_encoding():
149 148 """Return the default standard input encoding.
150 149
151 150 If sys.stdin has no encoding, 'ascii' is returned."""
152 151 # There are strange environments for which sys.stdin.encoding is None. We
153 152 # ensure that a valid encoding is returned.
154 153 encoding = getattr(sys.stdin, 'encoding', None)
155 154 if encoding is None:
156 155 encoding = 'ascii'
157 156 return encoding
158 157
159 158 #-----------------------------------------------------------------------------
160 159 # Classes and functions for normal Python syntax handling
161 160 #-----------------------------------------------------------------------------
162 161
163 162 class InputSplitter(object):
164 163 """An object that can split Python source input in executable blocks.
165 164
166 165 This object is designed to be used in one of two basic modes:
167 166
168 167 1. By feeding it python source line-by-line, using :meth:`push`. In this
169 168 mode, it will return on each push whether the currently pushed code
170 169 could be executed already. In addition, it provides a method called
171 170 :meth:`push_accepts_more` that can be used to query whether more input
172 171 can be pushed into a single interactive block.
173 172
174 173 2. By calling :meth:`split_blocks` with a single, multiline Python string,
175 174 that is then split into blocks each of which can be executed
176 175 interactively as a single statement.
177 176
178 177 This is a simple example of how an interactive terminal-based client can use
179 178 this tool::
180 179
181 180 isp = InputSplitter()
182 181 while isp.push_accepts_more():
183 182 indent = ' '*isp.indent_spaces
184 183 prompt = '>>> ' + indent
185 184 line = indent + raw_input(prompt)
186 185 isp.push(line)
187 186 print 'Input source was:\n', isp.source_reset(),
188 187 """
189 188 # Number of spaces of indentation computed from input that has been pushed
190 189 # so far. This is the attributes callers should query to get the current
191 190 # indentation level, in order to provide auto-indent facilities.
192 191 indent_spaces = 0
193 192 # String, indicating the default input encoding. It is computed by default
194 193 # at initialization time via get_input_encoding(), but it can be reset by a
195 194 # client with specific knowledge of the encoding.
196 195 encoding = ''
197 196 # String where the current full source input is stored, properly encoded.
198 197 # Reading this attribute is the normal way of querying the currently pushed
199 198 # source code, that has been properly encoded.
200 199 source = ''
201 200 # Code object corresponding to the current source. It is automatically
202 201 # synced to the source, so it can be queried at any time to obtain the code
203 202 # object; it will be None if the source doesn't compile to valid Python.
204 203 code = None
205 204 # Input mode
206 205 input_mode = 'append'
207 206
208 207 # Private attributes
209 208
210 209 # List with lines of input accumulated so far
211 210 _buffer = None
212 211 # Command compiler
213 212 _compile = None
214 213 # Mark when input has changed indentation all the way back to flush-left
215 214 _full_dedent = False
216 215 # Boolean indicating whether the current block is complete
217 216 _is_complete = None
218 217
219 218 def __init__(self, input_mode=None):
220 219 """Create a new InputSplitter instance.
221 220
222 221 Parameters
223 222 ----------
224 223 input_mode : str
225 224
226 One of 'append', 'replace', default is 'append'. This controls how
225 One of ['append', 'replace']; default is 'append'. This controls how
227 226 new inputs are used: in 'append' mode, they are appended to the
228 227 existing buffer and the whole buffer is compiled; in 'replace' mode,
229 228 each new input completely replaces all prior inputs. Replace mode is
230 229 thus equivalent to prepending a full reset() to every push() call.
231 230
232 231 In practice, line-oriented clients likely want to use 'append' mode
233 232 while block-oriented ones will want to use 'replace'.
234 233 """
235 234 self._buffer = []
236 235 self._compile = codeop.CommandCompiler()
237 236 self.encoding = get_input_encoding()
238 237 self.input_mode = InputSplitter.input_mode if input_mode is None \
239 238 else input_mode
240 239
241 240 def reset(self):
242 241 """Reset the input buffer and associated state."""
243 242 self.indent_spaces = 0
244 243 self._buffer[:] = []
245 244 self.source = ''
246 245 self.code = None
247 246 self._is_complete = False
248 247 self._full_dedent = False
249 248
250 249 def source_reset(self):
251 250 """Return the input source and perform a full reset.
252 251 """
253 252 out = self.source
254 253 self.reset()
255 254 return out
256 255
257 256 def push(self, lines):
258 257 """Push one ore more lines of input.
259 258
260 259 This stores the given lines and returns a status code indicating
261 260 whether the code forms a complete Python block or not.
262 261
263 262 Any exceptions generated in compilation are swallowed, but if an
264 263 exception was produced, the method returns True.
265 264
266 265 Parameters
267 266 ----------
268 267 lines : string
269 268 One or more lines of Python input.
270 269
271 270 Returns
272 271 -------
273 272 is_complete : boolean
274 273 True if the current input source (the result of the current input
275 274 plus prior inputs) forms a complete Python execution block. Note that
276 275 this value is also stored as a private attribute (_is_complete), so it
277 276 can be queried at any time.
278 277 """
279 278 if self.input_mode == 'replace':
280 279 self.reset()
281 280
282 281 # If the source code has leading blanks, add 'if 1:\n' to it
283 282 # this allows execution of indented pasted code. It is tempting
284 283 # to add '\n' at the end of source to run commands like ' a=1'
285 284 # directly, but this fails for more complicated scenarios
286 285 if not self._buffer and lines[:1] in [' ', '\t']:
287 286 lines = 'if 1:\n%s' % lines
288 287
289 288 self._store(lines)
290 289 source = self.source
291 290
292 291 # Before calling _compile(), reset the code object to None so that if an
293 292 # exception is raised in compilation, we don't mislead by having
294 293 # inconsistent code/source attributes.
295 294 self.code, self._is_complete = None, None
296 295
297 296 self._update_indent(lines)
298 297 try:
299 298 self.code = self._compile(source)
300 299 # Invalid syntax can produce any of a number of different errors from
301 300 # inside the compiler, so we have to catch them all. Syntax errors
302 301 # immediately produce a 'ready' block, so the invalid Python can be
303 302 # sent to the kernel for evaluation with possible ipython
304 303 # special-syntax conversion.
305 304 except (SyntaxError, OverflowError, ValueError, TypeError,
306 305 MemoryError):
307 306 self._is_complete = True
308 307 else:
309 308 # Compilation didn't produce any exceptions (though it may not have
310 309 # given a complete code object)
311 310 self._is_complete = self.code is not None
312 311
313 312 return self._is_complete
314 313
315 314 def push_accepts_more(self):
316 315 """Return whether a block of interactive input can accept more input.
317 316
318 317 This method is meant to be used by line-oriented frontends, who need to
319 318 guess whether a block is complete or not based solely on prior and
320 319 current input lines. The InputSplitter considers it has a complete
321 320 interactive block and will not accept more input only when either a
322 321 SyntaxError is raised, or *all* of the following are true:
323 322
324 323 1. The input compiles to a complete statement.
325 324
326 325 2. The indentation level is flush-left (because if we are indented,
327 326 like inside a function definition or for loop, we need to keep
328 327 reading new input).
329 328
330 329 3. There is one extra line consisting only of whitespace.
331 330
332 331 Because of condition #3, this method should be used only by
333 332 *line-oriented* frontends, since it means that intermediate blank lines
334 333 are not allowed in function definitions (or any other indented block).
335 334
336 335 Block-oriented frontends that have a separate keyboard event to
337 336 indicate execution should use the :meth:`split_blocks` method instead.
338 337
339 338 If the current input produces a syntax error, this method immediately
340 339 returns False but does *not* raise the syntax error exception, as
341 340 typically clients will want to send invalid syntax to an execution
342 341 backend which might convert the invalid syntax into valid Python via
343 342 one of the dynamic IPython mechanisms.
344 343 """
345 344
346 345 if not self._is_complete:
347 346 return True
348 347
349 348 if self.indent_spaces==0:
350 349 return False
351 350
352 351 last_line = self.source.splitlines()[-1]
353 352 return bool(last_line and not last_line.isspace())
354 353
355 354 def split_blocks(self, lines):
356 355 """Split a multiline string into multiple input blocks.
357 356
358 357 Note: this method starts by performing a full reset().
359 358
360 359 Parameters
361 360 ----------
362 361 lines : str
363 362 A possibly multiline string.
364 363
365 364 Returns
366 365 -------
367 366 blocks : list
368 367 A list of strings, each possibly multiline. Each string corresponds
369 368 to a single block that can be compiled in 'single' mode (unless it
370 369 has a syntax error)."""
371 370
372 371 # This code is fairly delicate. If you make any changes here, make
373 372 # absolutely sure that you do run the full test suite and ALL tests
374 373 # pass.
375 374
376 375 self.reset()
377 376 blocks = []
378 377
379 378 # Reversed copy so we can use pop() efficiently and consume the input
380 379 # as a stack
381 380 lines = lines.splitlines()[::-1]
382 381 # Outer loop over all input
383 382 while lines:
384 383 # Inner loop to build each block
385 384 while True:
386 385 # Safety exit from inner loop
387 386 if not lines:
388 387 break
389 388 # Grab next line but don't push it yet
390 389 next_line = lines.pop()
391 390 # Blank/empty lines are pushed as-is
392 391 if not next_line or next_line.isspace():
393 392 self.push(next_line)
394 393 continue
395 394
396 395 # Check indentation changes caused by the *next* line
397 396 indent_spaces, _full_dedent = self._find_indent(next_line)
398 397
399 398 # If the next line causes a dedent, it can be for two differnt
400 399 # reasons: either an explicit de-dent by the user or a
401 400 # return/raise/pass statement. These MUST be handled
402 401 # separately:
403 402 #
404 403 # 1. the first case is only detected when the actual explicit
405 404 # dedent happens, and that would be the *first* line of a *new*
406 405 # block. Thus, we must put the line back into the input buffer
407 406 # so that it starts a new block on the next pass.
408 407 #
409 408 # 2. the second case is detected in the line before the actual
410 409 # dedent happens, so , we consume the line and we can break out
411 410 # to start a new block.
412 411
413 412 # Case 1, explicit dedent causes a break
414 413 if _full_dedent and not next_line.startswith(' '):
415 414 lines.append(next_line)
416 415 break
417 416
418 417 # Otherwise any line is pushed
419 418 self.push(next_line)
420 419
421 420 # Case 2, full dedent with full block ready:
422 421 if _full_dedent or \
423 422 self.indent_spaces==0 and not self.push_accepts_more():
424 423 break
425 424 # Form the new block with the current source input
426 425 blocks.append(self.source_reset())
427 426
428 427 return blocks
429 428
430 429 #------------------------------------------------------------------------
431 430 # Private interface
432 431 #------------------------------------------------------------------------
433 432
434 433 def _find_indent(self, line):
435 434 """Compute the new indentation level for a single line.
436 435
437 436 Parameters
438 437 ----------
439 438 line : str
440 439 A single new line of non-whitespace, non-comment Python input.
441 440
442 441 Returns
443 442 -------
444 443 indent_spaces : int
445 444 New value for the indent level (it may be equal to self.indent_spaces
446 445 if indentation doesn't change.
447 446
448 447 full_dedent : boolean
449 448 Whether the new line causes a full flush-left dedent.
450 449 """
451 450 indent_spaces = self.indent_spaces
452 451 full_dedent = self._full_dedent
453 452
454 453 inisp = num_ini_spaces(line)
455 454 if inisp < indent_spaces:
456 455 indent_spaces = inisp
457 456 if indent_spaces <= 0:
458 457 #print 'Full dedent in text',self.source # dbg
459 458 full_dedent = True
460 459
461 460 if line[-1] == ':':
462 461 indent_spaces += 4
463 462 elif dedent_re.match(line):
464 463 indent_spaces -= 4
465 464 if indent_spaces <= 0:
466 465 full_dedent = True
467 466
468 467 # Safety
469 468 if indent_spaces < 0:
470 469 indent_spaces = 0
471 470 #print 'safety' # dbg
472 471
473 472 return indent_spaces, full_dedent
474 473
475 474 def _update_indent(self, lines):
476 475 for line in remove_comments(lines).splitlines():
477 476 if line and not line.isspace():
478 477 self.indent_spaces, self._full_dedent = self._find_indent(line)
479 478
480 479 def _store(self, lines):
481 480 """Store one or more lines of input.
482 481
483 482 If input lines are not newline-terminated, a newline is automatically
484 483 appended."""
485 484
486 485 if lines.endswith('\n'):
487 486 self._buffer.append(lines)
488 487 else:
489 488 self._buffer.append(lines+'\n')
490 489 self._set_source()
491 490
492 491 def _set_source(self):
493 492 self.source = ''.join(self._buffer).encode(self.encoding)
494 493
495 494
496 495 #-----------------------------------------------------------------------------
497 496 # Functions and classes for IPython-specific syntactic support
498 497 #-----------------------------------------------------------------------------
499 498
500 499 # RegExp for splitting line contents into pre-char//first word-method//rest.
501 500 # For clarity, each group in on one line.
502 501
503 502 line_split = re.compile("""
504 503 ^(\s*) # any leading space
505 504 ([,;/%]|!!?|\?\??) # escape character or characters
506 505 \s*([\w\.]*) # function/method part (mix of \w and '.')
507 506 (\s+.*$|$) # rest of line
508 507 """, re.VERBOSE)
509 508
510 509
511 510 def split_user_input(line):
512 511 """Split user input into early whitespace, esc-char, function part and rest.
513 512
514 513 This is currently handles lines with '=' in them in a very inconsistent
515 514 manner.
516 515
517 516 Examples
518 517 ========
519 518 >>> split_user_input('x=1')
520 519 ('', '', 'x=1', '')
521 520 >>> split_user_input('?')
522 521 ('', '?', '', '')
523 522 >>> split_user_input('??')
524 523 ('', '??', '', '')
525 524 >>> split_user_input(' ?')
526 525 (' ', '?', '', '')
527 526 >>> split_user_input(' ??')
528 527 (' ', '??', '', '')
529 528 >>> split_user_input('??x')
530 529 ('', '??', 'x', '')
531 530 >>> split_user_input('?x=1')
532 531 ('', '', '?x=1', '')
533 532 >>> split_user_input('!ls')
534 533 ('', '!', 'ls', '')
535 534 >>> split_user_input(' !ls')
536 535 (' ', '!', 'ls', '')
537 536 >>> split_user_input('!!ls')
538 537 ('', '!!', 'ls', '')
539 538 >>> split_user_input(' !!ls')
540 539 (' ', '!!', 'ls', '')
541 540 >>> split_user_input(',ls')
542 541 ('', ',', 'ls', '')
543 542 >>> split_user_input(';ls')
544 543 ('', ';', 'ls', '')
545 544 >>> split_user_input(' ;ls')
546 545 (' ', ';', 'ls', '')
547 546 >>> split_user_input('f.g(x)')
548 547 ('', '', 'f.g(x)', '')
549 548 >>> split_user_input('f.g (x)')
550 549 ('', '', 'f.g', '(x)')
551 550 """
552 551 match = line_split.match(line)
553 552 if match:
554 553 lspace, esc, fpart, rest = match.groups()
555 554 else:
556 555 # print "match failed for line '%s'" % line
557 556 try:
558 557 fpart, rest = line.split(None, 1)
559 558 except ValueError:
560 559 # print "split failed for line '%s'" % line
561 560 fpart, rest = line,''
562 561 lspace = re.match('^(\s*)(.*)', line).groups()[0]
563 562 esc = ''
564 563
565 564 # fpart has to be a valid python identifier, so it better be only pure
566 565 # ascii, no unicode:
567 566 try:
568 567 fpart = fpart.encode('ascii')
569 568 except UnicodeEncodeError:
570 569 lspace = unicode(lspace)
571 570 rest = fpart + u' ' + rest
572 571 fpart = u''
573 572
574 573 #print 'line:<%s>' % line # dbg
575 574 #print 'esc <%s> fpart <%s> rest <%s>' % (esc,fpart.strip(),rest) # dbg
576 575 return lspace, esc, fpart.strip(), rest.lstrip()
577 576
578 577
579 578 # The escaped translators ALL receive a line where their own escape has been
580 579 # stripped. Only '?' is valid at the end of the line, all others can only be
581 580 # placed at the start.
582 581
583 582 class LineInfo(object):
584 583 """A single line of input and associated info.
585 584
586 585 This is a utility class that mostly wraps the output of
587 586 :func:`split_user_input` into a convenient object to be passed around
588 587 during input transformations.
589 588
590 589 Includes the following as properties:
591 590
592 591 line
593 592 The original, raw line
594 593
595 594 lspace
596 595 Any early whitespace before actual text starts.
597 596
598 597 esc
599 598 The initial esc character (or characters, for double-char escapes like
600 599 '??' or '!!').
601 600
602 601 fpart
603 602 The 'function part', which is basically the maximal initial sequence
604 603 of valid python identifiers and the '.' character. This is what is
605 604 checked for alias and magic transformations, used for auto-calling,
606 605 etc.
607 606
608 607 rest
609 608 Everything else on the line.
610 609 """
611 610 def __init__(self, line):
612 611 self.line = line
613 612 self.lspace, self.esc, self.fpart, self.rest = \
614 613 split_user_input(line)
615 614
616 615 def __str__(self):
617 616 return "LineInfo [%s|%s|%s|%s]" % (self.lspace, self.esc,
618 617 self.fpart, self.rest)
619 618
620 619
621 620 # Transformations of the special syntaxes that don't rely on an explicit escape
622 621 # character but instead on patterns on the input line
623 622
624 623 # The core transformations are implemented as standalone functions that can be
625 624 # tested and validated in isolation. Each of these uses a regexp, we
626 625 # pre-compile these and keep them close to each function definition for clarity
627 626
628 627 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
629 628 r'\s*=\s*!\s*(?P<cmd>.*)')
630 629
631 630 def transform_assign_system(line):
632 631 """Handle the `files = !ls` syntax."""
633 632 # FIXME: This transforms the line to use %sc, but we've listed that magic
634 633 # as deprecated. We should then implement this functionality in a
635 634 # standalone api that we can transform to, without going through a
636 635 # deprecated magic.
637 636 m = _assign_system_re.match(line)
638 637 if m is not None:
639 638 cmd = m.group('cmd')
640 639 lhs = m.group('lhs')
641 640 expr = make_quoted_expr("sc -l = %s" % cmd)
642 641 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
643 642 return new_line
644 643 return line
645 644
646 645
647 646 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
648 647 r'\s*=\s*%\s*(?P<cmd>.*)')
649 648
650 649 def transform_assign_magic(line):
651 650 """Handle the `a = %who` syntax."""
652 651 m = _assign_magic_re.match(line)
653 652 if m is not None:
654 653 cmd = m.group('cmd')
655 654 lhs = m.group('lhs')
656 655 expr = make_quoted_expr(cmd)
657 656 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
658 657 return new_line
659 658 return line
660 659
661 660
662 661 _classic_prompt_re = re.compile(r'^([ \t]*>>> |^[ \t]*\.\.\. )')
663 662
664 663 def transform_classic_prompt(line):
665 664 """Handle inputs that start with '>>> ' syntax."""
666 665
667 666 if not line or line.isspace():
668 667 return line
669 668 m = _classic_prompt_re.match(line)
670 669 if m:
671 670 return line[len(m.group(0)):]
672 671 else:
673 672 return line
674 673
675 674
676 675 _ipy_prompt_re = re.compile(r'^([ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
677 676
678 677 def transform_ipy_prompt(line):
679 678 """Handle inputs that start classic IPython prompt syntax."""
680 679
681 680 if not line or line.isspace():
682 681 return line
682 #print 'LINE: %r' % line # dbg
683 683 m = _ipy_prompt_re.match(line)
684 684 if m:
685 #print 'MATCH! %r -> %r' % (line, line[len(m.group(0)):]) # dbg
685 686 return line[len(m.group(0)):]
686 687 else:
687 688 return line
688 689
689 690
690 691 class EscapedTransformer(object):
691 692 """Class to transform lines that are explicitly escaped out."""
692 693
693 694 def __init__(self):
694 695 tr = { ESC_SHELL : self._tr_system,
695 696 ESC_SH_CAP : self._tr_system2,
696 697 ESC_HELP : self._tr_help,
697 698 ESC_HELP2 : self._tr_help,
698 699 ESC_MAGIC : self._tr_magic,
699 700 ESC_QUOTE : self._tr_quote,
700 701 ESC_QUOTE2 : self._tr_quote2,
701 702 ESC_PAREN : self._tr_paren }
702 703 self.tr = tr
703 704
704 705 # Support for syntax transformations that use explicit escapes typed by the
705 706 # user at the beginning of a line
706 707 @staticmethod
707 708 def _tr_system(line_info):
708 709 "Translate lines escaped with: !"
709 710 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
710 711 return '%sget_ipython().system(%s)' % (line_info.lspace,
711 712 make_quoted_expr(cmd))
712 713
713 714 @staticmethod
714 715 def _tr_system2(line_info):
715 716 "Translate lines escaped with: !!"
716 717 cmd = line_info.line.lstrip()[2:]
717 718 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
718 719 make_quoted_expr(cmd))
719 720
720 721 @staticmethod
721 722 def _tr_help(line_info):
722 723 "Translate lines escaped with: ?/??"
723 724 # A naked help line should just fire the intro help screen
724 725 if not line_info.line[1:]:
725 726 return 'get_ipython().show_usage()'
726 727
727 728 # There may be one or two '?' at the end, move them to the front so that
728 729 # the rest of the logic can assume escapes are at the start
729 730 line = line_info.line
730 731 if line.endswith('?'):
731 732 line = line[-1] + line[:-1]
732 733 if line.endswith('?'):
733 734 line = line[-1] + line[:-1]
734 735 line_info = LineInfo(line)
735 736
736 737 # From here on, simply choose which level of detail to get.
737 738 if line_info.esc == '?':
738 739 pinfo = 'pinfo'
739 740 elif line_info.esc == '??':
740 741 pinfo = 'pinfo2'
741 742
742 743 tpl = '%sget_ipython().magic("%s %s")'
743 744 return tpl % (line_info.lspace, pinfo,
744 745 ' '.join([line_info.fpart, line_info.rest]).strip())
745 746
746 747 @staticmethod
747 748 def _tr_magic(line_info):
748 749 "Translate lines escaped with: %"
749 750 tpl = '%sget_ipython().magic(%s)'
750 751 cmd = make_quoted_expr(' '.join([line_info.fpart,
751 752 line_info.rest]).strip())
752 753 return tpl % (line_info.lspace, cmd)
753 754
754 755 @staticmethod
755 756 def _tr_quote(line_info):
756 757 "Translate lines escaped with: ,"
757 758 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
758 759 '", "'.join(line_info.rest.split()) )
759 760
760 761 @staticmethod
761 762 def _tr_quote2(line_info):
762 763 "Translate lines escaped with: ;"
763 764 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
764 765 line_info.rest)
765 766
766 767 @staticmethod
767 768 def _tr_paren(line_info):
768 769 "Translate lines escaped with: /"
769 770 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
770 771 ", ".join(line_info.rest.split()))
771 772
772 773 def __call__(self, line):
773 774 """Class to transform lines that are explicitly escaped out.
774 775
775 776 This calls the above _tr_* static methods for the actual line
776 777 translations."""
777 778
778 779 # Empty lines just get returned unmodified
779 780 if not line or line.isspace():
780 781 return line
781 782
782 783 # Get line endpoints, where the escapes can be
783 784 line_info = LineInfo(line)
784 785
785 786 # If the escape is not at the start, only '?' needs to be special-cased.
786 787 # All other escapes are only valid at the start
787 788 if not line_info.esc in self.tr:
788 789 if line.endswith(ESC_HELP):
789 790 return self._tr_help(line_info)
790 791 else:
791 792 # If we don't recognize the escape, don't modify the line
792 793 return line
793 794
794 795 return self.tr[line_info.esc](line_info)
795 796
796 797
797 798 # A function-looking object to be used by the rest of the code. The purpose of
798 799 # the class in this case is to organize related functionality, more than to
799 800 # manage state.
800 801 transform_escaped = EscapedTransformer()
801 802
802 803
803 804 class IPythonInputSplitter(InputSplitter):
804 805 """An input splitter that recognizes all of IPython's special syntax."""
805 806
806 807 def push(self, lines):
807 808 """Push one or more lines of IPython input.
808 809 """
809 810 if not lines:
810 811 return super(IPythonInputSplitter, self).push(lines)
811 812
812 813 lines_list = lines.splitlines()
813 814
814 815 transforms = [transform_escaped, transform_assign_system,
815 816 transform_assign_magic, transform_ipy_prompt,
816 817 transform_classic_prompt]
817 818
818 819 # Transform logic
819 820 #
820 821 # We only apply the line transformers to the input if we have either no
821 822 # input yet, or complete input, or if the last line of the buffer ends
822 823 # with ':' (opening an indented block). This prevents the accidental
823 824 # transformation of escapes inside multiline expressions like
824 825 # triple-quoted strings or parenthesized expressions.
825 826 #
826 827 # The last heuristic, while ugly, ensures that the first line of an
827 828 # indented block is correctly transformed.
828 829 #
829 830 # FIXME: try to find a cleaner approach for this last bit.
830 831
831 for line in lines_list:
832 if self._is_complete or not self._buffer or \
833 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
834 for f in transforms:
835 line = f(line)
836
837 out = super(IPythonInputSplitter, self).push(line)
832 # If we were in 'replace' mode, since we're going to pump the parent
833 # class by hand line by line, we need to temporarily switch out to
834 # 'append' mode, do a single manual reset and then feed the lines one
835 # by one. Note that this only matters if the input has more than one
836 # line.
837 changed_input_mode = False
838
839 if len(lines_list)>1 and self.input_mode == 'replace':
840 self.reset()
841 changed_input_mode = True
842 saved_input_mode = 'replace'
843 self.input_mode = 'append'
838 844
845 try:
846 push = super(IPythonInputSplitter, self).push
847 for line in lines_list:
848 if self._is_complete or not self._buffer or \
849 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
850 for f in transforms:
851 line = f(line)
852
853 out = push(line)
854 finally:
855 if changed_input_mode:
856 self.input_mode = saved_input_mode
857
839 858 return out
@@ -1,622 +1,648 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 (' ',' '), # blank lines are kept intact
414 (' ', ' '), # blank lines are kept intact
415 ('... ', ''), # continuation prompts
415 416 ],
416 417
417 418 ipy_prompt =
418 419 [('In [1]: x=1', 'x=1'),
419 420 ('x=1', 'x=1'), # normal input is unmodified
420 421 (' ',' '), # blank lines are kept intact
422 (' ....: ', ''), # continuation prompts
421 423 ],
422 424
423 425 # Tests for the escape transformer to leave normal code alone
424 426 escaped_noesc =
425 427 [ (' ', ' '),
426 428 ('x=1', 'x=1'),
427 429 ],
428 430
429 431 # System calls
430 432 escaped_shell =
431 433 [ ('!ls', 'get_ipython().system("ls")'),
432 434 # Double-escape shell, this means to capture the output of the
433 435 # subprocess and return it
434 436 ('!!ls', 'get_ipython().getoutput("ls")'),
435 437 ],
436 438
437 439 # Help/object info
438 440 escaped_help =
439 441 [ ('?', 'get_ipython().show_usage()'),
440 442 ('?x1', 'get_ipython().magic("pinfo x1")'),
441 443 ('??x2', 'get_ipython().magic("pinfo2 x2")'),
442 444 ('x3?', 'get_ipython().magic("pinfo x3")'),
443 445 ('x4??', 'get_ipython().magic("pinfo2 x4")'),
444 446 ],
445 447
446 448 # Explicit magic calls
447 449 escaped_magic =
448 450 [ ('%cd', 'get_ipython().magic("cd")'),
449 451 ('%cd /home', 'get_ipython().magic("cd /home")'),
450 452 (' %magic', ' get_ipython().magic("magic")'),
451 453 ],
452 454
453 455 # Quoting with separate arguments
454 456 escaped_quote =
455 457 [ (',f', 'f("")'),
456 458 (',f x', 'f("x")'),
457 459 (' ,f y', ' f("y")'),
458 460 (',f a b', 'f("a", "b")'),
459 461 ],
460 462
461 463 # Quoting with single argument
462 464 escaped_quote2 =
463 465 [ (';f', 'f("")'),
464 466 (';f x', 'f("x")'),
465 467 (' ;f y', ' f("y")'),
466 468 (';f a b', 'f("a b")'),
467 469 ],
468 470
469 471 # Simply apply parens
470 472 escaped_paren =
471 473 [ ('/f', 'f()'),
472 474 ('/f x', 'f(x)'),
473 475 (' /f y', ' f(y)'),
474 476 ('/f a b', 'f(a, b)'),
475 477 ],
476 478
477 # More complex multiline tests
478 ## escaped_multiline =
479 ## [()],
480 479 )
481 480
482 481 # multiline syntax examples. Each of these should be a list of lists, with
483 482 # each entry itself having pairs of raw/transformed input. The union (with
484 483 # '\n'.join() of the transformed inputs is what the splitter should produce
485 484 # when fed the raw lines one at a time via push.
486 485 syntax_ml = \
487 486 dict(classic_prompt =
488 487 [ [('>>> for i in range(10):','for i in range(10):'),
489 488 ('... print i',' print i'),
490 489 ('... ', ''),
491 490 ],
492 491 ],
493 492
494 493 ipy_prompt =
495 494 [ [('In [24]: for i in range(10):','for i in range(10):'),
496 495 (' ....: print i',' print i'),
497 496 (' ....: ', ''),
498 497 ],
499 498 ],
500 499 )
501 500
502 501
503 502 def test_assign_system():
504 503 transform_checker(syntax['assign_system'], isp.transform_assign_system)
505 504
506 505
507 506 def test_assign_magic():
508 507 transform_checker(syntax['assign_magic'], isp.transform_assign_magic)
509 508
510 509
511 510 def test_classic_prompt():
512 511 transform_checker(syntax['classic_prompt'], isp.transform_classic_prompt)
513 512 for example in syntax_ml['classic_prompt']:
514 513 transform_checker(example, isp.transform_classic_prompt)
515 514
516 515
517 516 def test_ipy_prompt():
518 517 transform_checker(syntax['ipy_prompt'], isp.transform_ipy_prompt)
519 518 for example in syntax_ml['ipy_prompt']:
520 519 transform_checker(example, isp.transform_ipy_prompt)
521 520
522 521
523 522 def test_escaped_noesc():
524 523 transform_checker(syntax['escaped_noesc'], isp.transform_escaped)
525 524
526 525
527 526 def test_escaped_shell():
528 527 transform_checker(syntax['escaped_shell'], isp.transform_escaped)
529 528
530 529
531 530 def test_escaped_help():
532 531 transform_checker(syntax['escaped_help'], isp.transform_escaped)
533 532
534 533
535 534 def test_escaped_magic():
536 535 transform_checker(syntax['escaped_magic'], isp.transform_escaped)
537 536
538 537
539 538 def test_escaped_quote():
540 539 transform_checker(syntax['escaped_quote'], isp.transform_escaped)
541 540
542 541
543 542 def test_escaped_quote2():
544 543 transform_checker(syntax['escaped_quote2'], isp.transform_escaped)
545 544
546 545
547 546 def test_escaped_paren():
548 547 transform_checker(syntax['escaped_paren'], isp.transform_escaped)
549 548
550 549
551 550 class IPythonInputTestCase(InputSplitterTestCase):
552 551 """By just creating a new class whose .isp is a different instance, we
553 552 re-run the same test battery on the new input splitter.
554 553
555 554 In addition, this runs the tests over the syntax and syntax_ml dicts that
556 555 were tested by individual functions, as part of the OO interface.
557 556 """
557
558 558 def setUp(self):
559 self.isp = isp.IPythonInputSplitter()
559 self.isp = isp.IPythonInputSplitter(input_mode='append')
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 class BlockIPythonInputTestCase(IPythonInputTestCase):
588
589 # Deactivate tests that don't make sense for the block mode
590 test_push3 = test_split = lambda s: None
591
592 def setUp(self):
593 self.isp = isp.IPythonInputSplitter(input_mode='replace')
594
595 def test_syntax_multiline(self):
596 isp = self.isp
597 for example in syntax_ml.itervalues():
598 raw_parts = []
599 out_t_parts = []
600 for line_pairs in example:
601 for raw, out_t_part in line_pairs:
602 raw_parts.append(raw)
603 out_t_parts.append(out_t_part)
604
605 raw = '\n'.join(raw_parts)
606 out_t = '\n'.join(out_t_parts)
607
608 isp.push(raw)
609 out = isp.source_reset()
610 # Match ignoring trailing whitespace
611 self.assertEqual(out.rstrip(), out_t.rstrip())
612
613
587 614 #-----------------------------------------------------------------------------
588 # Main - use as a script
615 # Main - use as a script, mostly for developer experiments
589 616 #-----------------------------------------------------------------------------
590 617
591 618 if __name__ == '__main__':
592 619 # A simple demo for interactive experimentation. This code will not get
593 # picked up by any test suite. Useful mostly for illustration and during
594 # development.
620 # picked up by any test suite.
595 621 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
596 622
597 623 # configure here the syntax to use, prompt and whether to autoindent
598 624 #isp, start_prompt = InputSplitter(), '>>> '
599 625 isp, start_prompt = IPythonInputSplitter(), 'In> '
600 626
601 627 autoindent = True
602 628 #autoindent = False
603 629
604 630 try:
605 631 while True:
606 632 prompt = start_prompt
607 633 while isp.push_accepts_more():
608 634 indent = ' '*isp.indent_spaces
609 635 if autoindent:
610 636 line = indent + raw_input(prompt+indent)
611 637 else:
612 638 line = raw_input(prompt)
613 639 isp.push(line)
614 640 prompt = '... '
615 641
616 642 # Here we just return input so we can use it in a test suite, but a
617 643 # real interpreter would instead send it for execution somewhere.
618 644 #src = isp.source; raise EOFError # dbg
619 645 src = isp.source_reset()
620 646 print 'Input source was:\n', src
621 647 except EOFError:
622 648 print 'Bye'
@@ -1,343 +1,343 b''
1 1 """ A FrontendWidget that emulates the interface of the console IPython and
2 2 supports the additional functionality provided by the IPython kernel.
3 3
4 4 TODO: Add support for retrieving the system default editor. Requires code
5 5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 6 Linux (use the xdg system).
7 7 """
8 8
9 9 # Standard library imports
10 10 from subprocess import Popen
11 11
12 12 # System library imports
13 13 from PyQt4 import QtCore, QtGui
14 14
15 15 # Local imports
16 16 from IPython.core.inputsplitter import IPythonInputSplitter
17 17 from IPython.core.usage import default_banner
18 18 from frontend_widget import FrontendWidget
19 19
20 20
21 21 class IPythonPromptBlock(object):
22 22 """ An internal storage object for IPythonWidget.
23 23 """
24 24 def __init__(self, block, length, number):
25 25 self.block = block
26 26 self.length = length
27 27 self.number = number
28 28
29 29
30 30 class IPythonWidget(FrontendWidget):
31 31 """ A FrontendWidget for an IPython kernel.
32 32 """
33 33
34 34 # Signal emitted when an editor is needed for a file and the editor has been
35 35 # specified as 'custom'. See 'set_editor' for more information.
36 36 custom_edit_requested = QtCore.pyqtSignal(object, object)
37 37
38 38 # The default stylesheet: black text on a white background.
39 39 default_stylesheet = """
40 40 .error { color: red; }
41 41 .in-prompt { color: navy; }
42 42 .in-prompt-number { font-weight: bold; }
43 43 .out-prompt { color: darkred; }
44 44 .out-prompt-number { font-weight: bold; }
45 45 """
46 46
47 47 # A dark stylesheet: white text on a black background.
48 48 dark_stylesheet = """
49 49 QPlainTextEdit, QTextEdit { background-color: black; color: white }
50 50 QFrame { border: 1px solid grey; }
51 51 .error { color: red; }
52 52 .in-prompt { color: lime; }
53 53 .in-prompt-number { color: lime; font-weight: bold; }
54 54 .out-prompt { color: red; }
55 55 .out-prompt-number { color: red; font-weight: bold; }
56 56 """
57 57
58 58 # Default prompts.
59 59 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
60 60 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
61 61
62 62 # FrontendWidget protected class variables.
63 #_input_splitter_class = IPythonInputSplitter
63 _input_splitter_class = IPythonInputSplitter
64 64
65 65 # IPythonWidget protected class variables.
66 66 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
67 67 _payload_source_page = 'IPython.zmq.page.page'
68 68
69 69 #---------------------------------------------------------------------------
70 70 # 'object' interface
71 71 #---------------------------------------------------------------------------
72 72
73 73 def __init__(self, *args, **kw):
74 74 super(IPythonWidget, self).__init__(*args, **kw)
75 75
76 76 # IPythonWidget protected variables.
77 77 self._previous_prompt_obj = None
78 78
79 79 # Set a default editor and stylesheet.
80 80 self.set_editor('default')
81 81 self.reset_styling()
82 82
83 83 #---------------------------------------------------------------------------
84 84 # 'BaseFrontendMixin' abstract interface
85 85 #---------------------------------------------------------------------------
86 86
87 87 def _handle_history_reply(self, msg):
88 88 """ Implemented to handle history replies, which are only supported by
89 89 the IPython kernel.
90 90 """
91 91 history_dict = msg['content']['history']
92 92 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
93 93 self._set_history(items)
94 94
95 95 def _handle_prompt_reply(self, msg):
96 96 """ Implemented to handle prompt number replies, which are only
97 97 supported by the IPython kernel.
98 98 """
99 99 content = msg['content']
100 100 self._show_interpreter_prompt(content['prompt_number'],
101 101 content['input_sep'])
102 102
103 103 def _handle_pyout(self, msg):
104 104 """ Reimplemented for IPython-style "display hook".
105 105 """
106 106 if not self._hidden and self._is_from_this_session(msg):
107 107 content = msg['content']
108 108 prompt_number = content['prompt_number']
109 109 self._append_plain_text(content['output_sep'])
110 110 self._append_html(self._make_out_prompt(prompt_number))
111 111 self._append_plain_text(content['data'] + '\n' +
112 112 content['output_sep2'])
113 113
114 114 def _started_channels(self):
115 115 """ Reimplemented to make a history request.
116 116 """
117 117 super(IPythonWidget, self)._started_channels()
118 118 # FIXME: Disabled until history requests are properly implemented.
119 119 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
120 120
121 121 #---------------------------------------------------------------------------
122 122 # 'FrontendWidget' interface
123 123 #---------------------------------------------------------------------------
124 124
125 125 def execute_file(self, path, hidden=False):
126 126 """ Reimplemented to use the 'run' magic.
127 127 """
128 128 self.execute('%%run %s' % path, hidden=hidden)
129 129
130 130 #---------------------------------------------------------------------------
131 131 # 'FrontendWidget' protected interface
132 132 #---------------------------------------------------------------------------
133 133
134 134 def _get_banner(self):
135 135 """ Reimplemented to return IPython's default banner.
136 136 """
137 137 return default_banner + '\n'
138 138
139 139 def _process_execute_error(self, msg):
140 140 """ Reimplemented for IPython-style traceback formatting.
141 141 """
142 142 content = msg['content']
143 143 traceback = '\n'.join(content['traceback']) + '\n'
144 144 if False:
145 145 # FIXME: For now, tracebacks come as plain text, so we can't use
146 146 # the html renderer yet. Once we refactor ultratb to produce
147 147 # properly styled tracebacks, this branch should be the default
148 148 traceback = traceback.replace(' ', '&nbsp;')
149 149 traceback = traceback.replace('\n', '<br/>')
150 150
151 151 ename = content['ename']
152 152 ename_styled = '<span class="error">%s</span>' % ename
153 153 traceback = traceback.replace(ename, ename_styled)
154 154
155 155 self._append_html(traceback)
156 156 else:
157 157 # This is the fallback for now, using plain text with ansi escapes
158 158 self._append_plain_text(traceback)
159 159
160 160 def _process_execute_payload(self, item):
161 161 """ Reimplemented to handle %edit and paging payloads.
162 162 """
163 163 if item['source'] == self._payload_source_edit:
164 164 self._edit(item['filename'], item['line_number'])
165 165 return True
166 166 elif item['source'] == self._payload_source_page:
167 167 self._page(item['data'])
168 168 return True
169 169 else:
170 170 return False
171 171
172 172 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
173 173 """ Reimplemented for IPython-style prompts.
174 174 """
175 175 # If a number was not specified, make a prompt number request.
176 176 if number is None:
177 177 self.kernel_manager.xreq_channel.prompt()
178 178 return
179 179
180 180 # Show a new prompt and save information about it so that it can be
181 181 # updated later if the prompt number turns out to be wrong.
182 182 self._append_plain_text(input_sep)
183 183 self._show_prompt(self._make_in_prompt(number), html=True)
184 184 block = self._control.document().lastBlock()
185 185 length = len(self._prompt)
186 186 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
187 187
188 188 # Update continuation prompt to reflect (possibly) new prompt length.
189 189 self._set_continuation_prompt(
190 190 self._make_continuation_prompt(self._prompt), html=True)
191 191
192 192 def _show_interpreter_prompt_for_reply(self, msg):
193 193 """ Reimplemented for IPython-style prompts.
194 194 """
195 195 # Update the old prompt number if necessary.
196 196 content = msg['content']
197 197 previous_prompt_number = content['prompt_number']
198 198 if self._previous_prompt_obj and \
199 199 self._previous_prompt_obj.number != previous_prompt_number:
200 200 block = self._previous_prompt_obj.block
201 201
202 202 # Make sure the prompt block has not been erased.
203 203 if block.isValid() and not block.text().isEmpty():
204 204
205 205 # Remove the old prompt and insert a new prompt.
206 206 cursor = QtGui.QTextCursor(block)
207 207 cursor.movePosition(QtGui.QTextCursor.Right,
208 208 QtGui.QTextCursor.KeepAnchor,
209 209 self._previous_prompt_obj.length)
210 210 prompt = self._make_in_prompt(previous_prompt_number)
211 211 self._prompt = self._insert_html_fetching_plain_text(
212 212 cursor, prompt)
213 213
214 214 # When the HTML is inserted, Qt blows away the syntax
215 215 # highlighting for the line, so we need to rehighlight it.
216 216 self._highlighter.rehighlightBlock(cursor.block())
217 217
218 218 self._previous_prompt_obj = None
219 219
220 220 # Show a new prompt with the kernel's estimated prompt number.
221 221 next_prompt = content['next_prompt']
222 222 self._show_interpreter_prompt(next_prompt['prompt_number'],
223 223 next_prompt['input_sep'])
224 224
225 225 #---------------------------------------------------------------------------
226 226 # 'IPythonWidget' interface
227 227 #---------------------------------------------------------------------------
228 228
229 229 def reset_styling(self):
230 230 """ Restores the default IPythonWidget styling.
231 231 """
232 232 self.set_styling(self.default_stylesheet, syntax_style='default')
233 233 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
234 234
235 235 def set_editor(self, editor, line_editor=None):
236 236 """ Sets the editor to use with the %edit magic.
237 237
238 238 Parameters:
239 239 -----------
240 240 editor : str
241 241 A command for invoking a system text editor. If the string contains
242 242 a {filename} format specifier, it will be used. Otherwise, the
243 243 filename will be appended to the end the command.
244 244
245 245 This parameter also takes a special value:
246 246 'custom' : Emit a 'custom_edit_requested(str, int)' signal
247 247 instead of opening an editor.
248 248
249 249 line_editor : str, optional
250 250 The editor command to use when a specific line number is
251 251 requested. The string should contain two format specifiers: {line}
252 252 and {filename}. If this parameter is not specified, the line number
253 253 option to the %edit magic will be ignored.
254 254 """
255 255 self._editor = editor
256 256 self._editor_line = line_editor
257 257
258 258 def set_styling(self, stylesheet, syntax_style=None):
259 259 """ Sets the IPythonWidget styling.
260 260
261 261 Parameters:
262 262 -----------
263 263 stylesheet : str
264 264 A CSS stylesheet. The stylesheet can contain classes for:
265 265 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
266 266 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
267 267 3. IPython: .error, .in-prompt, .out-prompt, etc.
268 268
269 269 syntax_style : str or None [default None]
270 270 If specified, use the Pygments style with given name. Otherwise,
271 271 the stylesheet is queried for Pygments style information.
272 272 """
273 273 self.setStyleSheet(stylesheet)
274 274 self._control.document().setDefaultStyleSheet(stylesheet)
275 275 if self._page_control:
276 276 self._page_control.document().setDefaultStyleSheet(stylesheet)
277 277
278 278 if syntax_style is None:
279 279 self._highlighter.set_style_sheet(stylesheet)
280 280 else:
281 281 self._highlighter.set_style(syntax_style)
282 282
283 283 #---------------------------------------------------------------------------
284 284 # 'IPythonWidget' protected interface
285 285 #---------------------------------------------------------------------------
286 286
287 287 def _edit(self, filename, line=None):
288 288 """ Opens a Python script for editing.
289 289
290 290 Parameters:
291 291 -----------
292 292 filename : str
293 293 A path to a local system file.
294 294
295 295 line : int, optional
296 296 A line of interest in the file.
297 297 """
298 298 if self._editor == 'custom':
299 299 self.custom_edit_requested.emit(filename, line)
300 300 elif self._editor == 'default':
301 301 self._append_plain_text('No default editor available.\n')
302 302 else:
303 303 try:
304 304 filename = '"%s"' % filename
305 305 if line and self._editor_line:
306 306 command = self._editor_line.format(filename=filename,
307 307 line=line)
308 308 else:
309 309 try:
310 310 command = self._editor.format()
311 311 except KeyError:
312 312 command = self._editor.format(filename=filename)
313 313 else:
314 314 command += ' ' + filename
315 315 except KeyError:
316 316 self._append_plain_text('Invalid editor command.\n')
317 317 else:
318 318 try:
319 319 Popen(command, shell=True)
320 320 except OSError:
321 321 msg = 'Opening editor with command "%s" failed.\n'
322 322 self._append_plain_text(msg % command)
323 323
324 324 def _make_in_prompt(self, number):
325 325 """ Given a prompt number, returns an HTML In prompt.
326 326 """
327 327 body = self.in_prompt % number
328 328 return '<span class="in-prompt">%s</span>' % body
329 329
330 330 def _make_continuation_prompt(self, prompt):
331 331 """ Given a plain text version of an In prompt, returns an HTML
332 332 continuation prompt.
333 333 """
334 334 end_chars = '...: '
335 335 space_count = len(prompt.lstrip('\n')) - len(end_chars)
336 336 body = '&nbsp;' * space_count + end_chars
337 337 return '<span class="in-prompt">%s</span>' % body
338 338
339 339 def _make_out_prompt(self, number):
340 340 """ Given a prompt number, returns an HTML Out prompt.
341 341 """
342 342 body = self.out_prompt % number
343 343 return '<span class="out-prompt">%s</span>' % body
General Comments 0
You need to be logged in to leave comments. Login now