##// END OF EJS Templates
Transformers have been added to the prefilter....
Brian Granger -
Show More
@@ -3,11 +3,15 b''
3 3 """
4 4 Prefiltering components.
5 5
6 Prefilters transform user input before it is exec'd by Python. These
7 transforms are used to implement additional syntax such as !ls and %magic.
8
6 9 Authors:
7 10
8 11 * Brian Granger
9 12 * Fernando Perez
10 13 * Dan Milstein
14 * Ville Vainio
11 15 """
12 16
13 17 #-----------------------------------------------------------------------------
@@ -34,7 +38,7 b' from IPython.core.component import Component'
34 38 from IPython.core.splitinput import split_user_input
35 39 from IPython.core.page import page
36 40
37 from IPython.utils.traitlets import List, Int, Any, Str, CBool
41 from IPython.utils.traitlets import List, Int, Any, Str, CBool, Bool
38 42 from IPython.utils.genutils import make_quoted_expr
39 43 from IPython.utils.autoattr import auto_attr
40 44
@@ -171,15 +175,46 b' class PrefilterManager(Component):'
171 175
172 176 The IPython prefilter is run on all user input before it is run. The
173 177 prefilter consumes lines of input and produces transformed lines of
174 input. The implementation consists of checkers and handlers. The
175 checkers inspect the input line and select which handler will be used
176 to transform the input line.
178 input.
179
180 The iplementation consists of two phases:
181
182 1. Transformers
183 2. Checkers and handlers
184
185 Over time, we plan on deprecating the checkers and handlers and doing
186 everything in the transformers.
187
188 The transformers are instances of :class:`PrefilterTransformer` and have
189 a single method :meth:`transform` that takes a line and returns a
190 transformed line. The transformation can be accomplished using any
191 tool, but our current ones use regular expressions for speed. We also
192 ship :mod:`pyparsing` in :mod:`IPython.external` for use in transformers.
193
194 After all the transformers have been run, the line is fed to the checkers,
195 which are instances of :class:`PrefilterChecker`. The line is passed to
196 the :meth:`check` method, which either returns `None` or a
197 :class:`PrefilterHandler` instance. If `None` is returned, the other
198 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
199 the line is passed to the :meth:`handle` method of the returned
200 handler and no further checkers are tried.
201
202 Both transformers and checkers have a `priority` attribute, that determines
203 the order in which they are called. Smaller priorities are tried first.
204
205 Both transformers and checkers also have `enabled` attribute, which is
206 a boolean that determines if the instance is used.
207
208 Users or developers can change the priority or enabled attribute of
209 transformers or checkers, but they must call the :meth:`sort_checkers`
210 or :meth`sort_transformers` method after changing the priority.
177 211 """
178 212
179 213 multi_line_specials = CBool(True, config=True)
180 214
181 215 def __init__(self, parent, config=None):
182 216 super(PrefilterManager, self).__init__(parent, config=config)
217 self.init_transformers()
183 218 self.init_handlers()
184 219 self.init_checkers()
185 220
@@ -189,21 +224,89 b' class PrefilterManager(Component):'
189 224 root=self.root,
190 225 klass='IPython.core.iplib.InteractiveShell')[0]
191 226
227 #-------------------------------------------------------------------------
228 # API for managing transformers
229 #-------------------------------------------------------------------------
230
231 def init_transformers(self):
232 """Create the default transformers."""
233 self._transformers = []
234 for transformer_cls in _default_transformers:
235 transformer_cls(self, config=self.config)
236
237 def sort_transformers(self):
238 """Sort the transformers by priority.
239
240 This must be called after the priority of a transformer is changed.
241 The :meth:`register_transformer` method calls this automatically.
242 """
243 self._transformers.sort(cmp=lambda x,y: x.priority-y.priority)
244
245 @property
246 def transformers(self):
247 """Return a list of checkers, sorted by priority."""
248 return self._transformers
249
250 def register_transformer(self, transformer):
251 """Register a transformer instance."""
252 if transformer not in self._transformers:
253 self._transformers.append(transformer)
254 self.sort_transformers()
255
256 def unregister_transformer(self, transformer):
257 """Unregister a transformer instance."""
258 if transformer in self._transformers:
259 self._transformers.remove(transformer)
260
261 #-------------------------------------------------------------------------
262 # API for managing checkers
263 #-------------------------------------------------------------------------
264
192 265 def init_checkers(self):
266 """Create the default checkers."""
193 267 self._checkers = []
194 268 for checker in _default_checkers:
195 self._checkers.append(checker(self, config=self.config))
269 checker(self, config=self.config)
270
271 def sort_checkers(self):
272 """Sort the checkers by priority.
273
274 This must be called after the priority of a checker is changed.
275 The :meth:`register_checker` method calls this automatically.
276 """
277 self._checkers.sort(cmp=lambda x,y: x.priority-y.priority)
278
279 @property
280 def checkers(self):
281 """Return a list of checkers, sorted by priority."""
282 return self._checkers
283
284 def register_checker(self, checker):
285 """Register a checker instance."""
286 if checker not in self._checkers:
287 self._checkers.append(checker)
288 self.sort_checkers()
289
290 def unregister_checker(self, checker):
291 """Unregister a checker instance."""
292 if checker in self._checkers:
293 self._checkers.remove(checker)
294
295 #-------------------------------------------------------------------------
296 # API for managing checkers
297 #-------------------------------------------------------------------------
196 298
197 299 def init_handlers(self):
300 """Create the default handlers."""
198 301 self._handlers = {}
199 302 self._esc_handlers = {}
200 303 for handler in _default_handlers:
201 304 handler(self, config=self.config)
202 305
203 306 @property
204 def sorted_checkers(self):
205 """Return a list of checkers, sorted by priority."""
206 return sorted(self._checkers, cmp=lambda x,y: x.priority-y.priority)
307 def handlers(self):
308 """Return a dict of all the handlers."""
309 return self._handlers
207 310
208 311 def register_handler(self, name, handler, esc_strings):
209 312 """Register a handler instance by name with esc_strings."""
@@ -230,24 +333,41 b' class PrefilterManager(Component):'
230 333 """Get a handler by its escape string."""
231 334 return self._esc_handlers.get(esc_str)
232 335
336 #-------------------------------------------------------------------------
337 # Main prefiltering API
338 #-------------------------------------------------------------------------
339
233 340 def prefilter_line_info(self, line_info):
234 """Prefilter a line that has been converted to a LineInfo object."""
341 """Prefilter a line that has been converted to a LineInfo object.
342
343 This implements the checker/handler part of the prefilter pipe.
344 """
235 345 # print "prefilter_line_info: ", line_info
236 346 handler = self.find_handler(line_info)
237 347 return handler.handle(line_info)
238 348
239 349 def find_handler(self, line_info):
240 350 """Find a handler for the line_info by trying checkers."""
241 for checker in self.sorted_checkers:
242 handler = checker.check(line_info)
243 if handler:
244 # print "Used checker: ", checker
245 # print "Using handler: ", handler
246 return handler
351 for checker in self.checkers:
352 if checker.enabled:
353 handler = checker.check(line_info)
354 if handler:
355 return handler
247 356 return self.get_handler_by_name('normal')
248 357
358 def transform_line(self, line, continue_prompt):
359 """Calls the enabled transformers in order of increasing priority."""
360 for transformer in self.transformers:
361 if transformer.enabled:
362 line = transformer.transform(line, continue_prompt)
363 return line
364
249 365 def prefilter_line(self, line, continue_prompt):
250 """Prefilter a single input line as text."""
366 """Prefilter a single input line as text.
367
368 This method prefilters a single line of text by calling the
369 transformers and then the checkers/handlers.
370 """
251 371
252 372 # print "prefilter_line: ", line, continue_prompt
253 373 # All handlers *must* return a value, even if it's blank ('').
@@ -256,8 +376,6 b' class PrefilterManager(Component):'
256 376 # needed, update the cache AND log it (so that the input cache array
257 377 # stays synced).
258 378
259 # growl.notify("_prefilter: ", "line = %s\ncontinue_prompt = %s" % (line, continue_prompt))
260
261 379 # save the line away in case we crash, so the post-mortem handler can
262 380 # record it
263 381 self.shell._last_input_line = line
@@ -272,13 +390,18 b' class PrefilterManager(Component):'
272 390 if ''.join(self.shell.buffer).isspace():
273 391 self.shell.buffer[:] = []
274 392 return ''
275
393
394 # At this point, we invoke our transformers.
395 if not continue_prompt or (continue_prompt and self.multi_line_specials):
396 line = self.transform_line(line, continue_prompt)
397
398 # Now we compute line_info for the checkers and handlers
276 399 line_info = LineInfo(line, continue_prompt)
277 400
278 401 # the input history needs to track even empty lines
279 402 stripped = line.strip()
280 403
281 normal_handler = self.get_handler_by_name('normal')
404 normal_handler = self.get_handler_by_name('normal')
282 405 if not stripped:
283 406 if not continue_prompt:
284 407 self.shell.outputcache.prompt_count -= 1
@@ -296,19 +419,96 b' class PrefilterManager(Component):'
296 419 def prefilter_lines(self, lines, continue_prompt):
297 420 """Prefilter multiple input lines of text.
298 421
299 Covers cases where there are multiple lines in the user entry,
422 This is the main entry point for prefiltering multiple lines of
423 input. This simply calls :meth:`prefilter_line` for each line of
424 input.
425
426 This covers cases where there are multiple lines in the user entry,
300 427 which is the case when the user goes back to a multiline history
301 428 entry and presses enter.
302 429 """
303 # growl.notify("multiline_prefilter: ", "%s\n%s" % (line, continue_prompt))
304 430 out = []
305 431 for line in lines.rstrip('\n').split('\n'):
306 432 out.append(self.prefilter_line(line, continue_prompt))
307 # growl.notify("multiline_prefilter return: ", '\n'.join(out))
308 433 return '\n'.join(out)
309 434
310 435
311 436 #-----------------------------------------------------------------------------
437 # Prefilter transformers
438 #-----------------------------------------------------------------------------
439
440
441 class PrefilterTransformer(Component):
442 """Transform a line of user input."""
443
444 priority = Int(100, config=True)
445 shell = Any
446 prefilter_manager = Any
447 enabled = Bool(True, config=True)
448
449 def __init__(self, parent, config=None):
450 super(PrefilterTransformer, self).__init__(parent, config=config)
451 self.prefilter_manager.register_transformer(self)
452
453 @auto_attr
454 def shell(self):
455 return Component.get_instances(
456 root=self.root,
457 klass='IPython.core.iplib.InteractiveShell')[0]
458
459 @auto_attr
460 def prefilter_manager(self):
461 return PrefilterManager.get_instances(root=self.root)[0]
462
463 def transform(self, line, continue_prompt):
464 """Transform a line, returning the new one."""
465 return None
466
467 def __repr__(self):
468 return "<%s(priority=%r, enabled=%r)>" % (
469 self.__class__.__name__, self.priority, self.enabled)
470
471
472 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
473 r'\s*=\s*!(?P<cmd>.*)')
474
475
476 class AssignSystemTransformer(PrefilterTransformer):
477 """Handle the `files = !ls` syntax."""
478
479 priority = Int(100, config=True)
480
481 def transform(self, line, continue_prompt):
482 m = _assign_system_re.match(line)
483 if m is not None:
484 cmd = m.group('cmd')
485 lhs = m.group('lhs')
486 expr = make_quoted_expr("sc -l =%s" % cmd)
487 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
488 return new_line
489 return line
490
491
492 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
493 r'\s*=\s*%(?P<cmd>.*)')
494
495 class AssignMagicTransformer(PrefilterTransformer):
496 """Handle the `a = %who` syntax."""
497
498 priority = Int(200, config=True)
499
500 def transform(self, line, continue_prompt):
501 m = _assign_magic_re.match(line)
502 if m is not None:
503 cmd = m.group('cmd')
504 lhs = m.group('lhs')
505 expr = make_quoted_expr(cmd)
506 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
507 return new_line
508 return line
509
510
511 #-----------------------------------------------------------------------------
312 512 # Prefilter checkers
313 513 #-----------------------------------------------------------------------------
314 514
@@ -319,9 +519,11 b' class PrefilterChecker(Component):'
319 519 priority = Int(100, config=True)
320 520 shell = Any
321 521 prefilter_manager = Any
522 enabled = Bool(True, config=True)
322 523
323 524 def __init__(self, parent, config=None):
324 525 super(PrefilterChecker, self).__init__(parent, config=config)
526 self.prefilter_manager.register_checker(self)
325 527
326 528 @auto_attr
327 529 def shell(self):
@@ -334,15 +536,18 b' class PrefilterChecker(Component):'
334 536 return PrefilterManager.get_instances(root=self.root)[0]
335 537
336 538 def check(self, line_info):
337 """Inspect line_info and return a handler or None."""
539 """Inspect line_info and return a handler instance or None."""
338 540 return None
339 541
340 def __str__(self):
341 return "<%s(priority=%i)>" % (self.__class__.__name__, self.priority)
542 def __repr__(self):
543 return "<%s(priority=%r, enabled=%r)>" % (
544 self.__class__.__name__, self.priority, self.enabled)
545
342 546
343 547 class EmacsChecker(PrefilterChecker):
344 548
345 549 priority = Int(100, config=True)
550 enabled = Bool(False, config=True)
346 551
347 552 def check(self, line_info):
348 553 "Emacs ipython-mode tags certain input lines."
@@ -410,10 +615,6 b' class EscCharsChecker(PrefilterChecker):'
410 615 return self.prefilter_manager.get_handler_by_esc(line_info.pre_char)
411 616
412 617
413 _assign_system_re = re.compile('\s*=\s*!(?P<cmd>.*)')
414 _assign_magic_re = re.compile('\s*=\s*%(?P<cmd>.*)')
415
416
417 618 class AssignmentChecker(PrefilterChecker):
418 619
419 620 priority = Int(600, config=True)
@@ -427,12 +628,6 b' class AssignmentChecker(PrefilterChecker):'
427 628 python code). E.g. ls='hi', or ls,that=1,2"""
428 629 if line_info.the_rest:
429 630 if line_info.the_rest[0] in '=,':
430 # m = _assign_system_re.match(line_info.the_rest)
431 # if m is not None:
432 # return self.prefilter_manager.get_handler_by_name('assign_system')
433 # m = _assign_magic_re.match(line_info.the_rest)
434 # if m is not None:
435 # return self.prefilter_manager.get_handler_by_name('assign_magic')
436 631 return self.prefilter_manager.get_handler_by_name('normal')
437 632 else:
438 633 return None
@@ -571,45 +766,6 b' class PrefilterHandler(Component):'
571 766 def __str__(self):
572 767 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
573 768
574 class AssignSystemHandler(PrefilterHandler):
575
576 handler_name = Str('assign_system')
577
578 @auto_attr
579 def normal_handler(self):
580 return self.prefilter_manager.get_handler_by_name('normal')
581
582 def handle(self, line_info):
583 new_line = line_info.line
584 m = _assign_system_re.match(line_info.the_rest)
585 if m is not None:
586 cmd = m.group('cmd')
587 expr = make_quoted_expr("sc -l =%s" % cmd)
588 new_line = '%s%s = get_ipython().magic(%s)' % (line_info.pre_whitespace,
589 line_info.ifun, expr)
590 self.shell.log(line_info.line, new_line, line_info.continue_prompt)
591 return new_line
592
593
594 class AssignMagicHandler(PrefilterHandler):
595
596 handler_name = Str('assign_magic')
597
598 @auto_attr
599 def normal_handler(self):
600 return self.prefilter_manager.get_handler_by_name('normal')
601
602 def handle(self, line_info):
603 new_line = line_info.line
604 m = _assign_magic_re.match(line_info.the_rest)
605 if m is not None:
606 cmd = m.group('cmd')
607 expr = make_quoted_expr(cmd)
608 new_line = '%s%s = get_ipython().magic(%s)' % (line_info.pre_whitespace,
609 line_info.ifun, expr)
610 self.shell.log(line_info.line, new_line, line_info.continue_prompt)
611 return new_line
612
613 769
614 770 class AliasHandler(PrefilterHandler):
615 771
@@ -809,6 +965,11 b' class EmacsHandler(PrefilterHandler):'
809 965 #-----------------------------------------------------------------------------
810 966
811 967
968 _default_transformers = [
969 AssignSystemTransformer,
970 AssignMagicTransformer
971 ]
972
812 973 _default_checkers = [
813 974 EmacsChecker,
814 975 ShellEscapeChecker,
@@ -830,7 +991,5 b' _default_handlers = ['
830 991 AutoHandler,
831 992 HelpHandler,
832 993 EmacsHandler
833 # AssignSystemHandler,
834 # AssignMagicHandler
835 994 ]
836 995
General Comments 0
You need to be logged in to leave comments. Login now