prefilter.py
1025 lines
| 36.0 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2244 | #!/usr/bin/env python | ||
# encoding: utf-8 | ||||
dan.milstein
|
r657 | """ | ||
Brian Granger
|
r2244 | Prefiltering components. | ||
Brian Granger
|
r2273 | Prefilters transform user input before it is exec'd by Python. These | ||
transforms are used to implement additional syntax such as !ls and %magic. | ||||
Brian Granger
|
r2244 | Authors: | ||
* Brian Granger | ||||
* Fernando Perez | ||||
* Dan Milstein | ||||
Brian Granger
|
r2273 | * Ville Vainio | ||
dan.milstein
|
r657 | """ | ||
Brian Granger
|
r2244 | #----------------------------------------------------------------------------- | ||
# Copyright (C) 2008-2009 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
import __builtin__ | ||||
import codeop | ||||
dan.milstein
|
r657 | import re | ||
Brian Granger
|
r2244 | |||
from IPython.core.alias import AliasManager | ||||
Brian Granger
|
r2205 | from IPython.core.autocall import IPyAutocall | ||
Brian Granger
|
r2731 | from IPython.config.configurable import Configurable | ||
Thomas Kluyver
|
r3406 | from IPython.core.macro import Macro | ||
Brian Granger
|
r2244 | from IPython.core.splitinput import split_user_input | ||
Brian Granger
|
r2830 | from IPython.core import page | ||
Brian Granger
|
r2244 | |||
Brian Granger
|
r2731 | from IPython.utils.traitlets import List, Int, Any, Str, CBool, Bool, Instance | ||
Brian Granger
|
r2775 | import IPython.utils.io | ||
Brian Granger
|
r2498 | from IPython.utils.text import make_quoted_expr | ||
Brian Granger
|
r2244 | from IPython.utils.autoattr import auto_attr | ||
#----------------------------------------------------------------------------- | ||||
# Global utilities, errors and constants | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2256 | # Warning, these cannot be changed unless various regular expressions | ||
# are updated in a number of places. Not great, but at least we told you. | ||||
Brian Granger
|
r2244 | ESC_SHELL = '!' | ||
ESC_SH_CAP = '!!' | ||||
ESC_HELP = '?' | ||||
ESC_MAGIC = '%' | ||||
ESC_QUOTE = ',' | ||||
ESC_QUOTE2 = ';' | ||||
ESC_PAREN = '/' | ||||
class PrefilterError(Exception): | ||||
pass | ||||
# RegExp to identify potential function names | ||||
re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$') | ||||
# RegExp to exclude strings with this start from autocalling. In | ||||
# particular, all binary operators should be excluded, so that if foo is | ||||
# callable, foo OP bar doesn't become foo(OP bar), which is invalid. The | ||||
# characters '!=()' don't need to be checked for, as the checkPythonChars | ||||
# routine explicitely does so, to catch direct calls and rebindings of | ||||
# existing names. | ||||
# Warning: the '-' HAS TO BE AT THE END of the first group, otherwise | ||||
# it affects the rest of the group in square brackets. | ||||
re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]' | ||||
r'|^is |^not |^in |^and |^or ') | ||||
# try to catch also methods for stuff in lists/tuples/dicts: off | ||||
# (experimental). For this to work, the line_split regexp would need | ||||
# to be modified so it wouldn't break things at '['. That line is | ||||
# nasty enough that I shouldn't change it until I can test it _well_. | ||||
#self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$') | ||||
# Handler Check Utilities | ||||
def is_shadowed(identifier, ip): | ||||
"""Is the given identifier defined in one of the namespaces which shadow | ||||
the alias and magic namespaces? Note that an identifier is different | ||||
than ifun, because it can not contain a '.' character.""" | ||||
# This is much safer than calling ofind, which can change state | ||||
return (identifier in ip.user_ns \ | ||||
or identifier in ip.internal_ns \ | ||||
or identifier in ip.ns_table['builtin']) | ||||
#----------------------------------------------------------------------------- | ||||
# The LineInfo class used throughout | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2205 | |||
dan.milstein
|
r657 | |||
class LineInfo(object): | ||||
"""A single line of input and associated info. | ||||
Includes the following as properties: | ||||
line | ||||
The original, raw line | ||||
continue_prompt | ||||
Is this line a continuation in a sequence of multiline input? | ||||
pre | ||||
The initial esc character or whitespace. | ||||
Brian Granger
|
r2244 | pre_char | ||
dan.milstein
|
r657 | The escape character(s) in pre or the empty string if there isn't one. | ||
Brian Granger
|
r2244 | Note that '!!' is a possible value for pre_char. Otherwise it will | ||
dan.milstein
|
r657 | always be a single character. | ||
Brian Granger
|
r2244 | pre_whitespace | ||
The leading whitespace from pre if it exists. If there is a pre_char, | ||||
dan.milstein
|
r657 | this is just ''. | ||
Brian Granger
|
r2244 | ifun | ||
dan.milstein
|
r657 | The 'function part', which is basically the maximal initial sequence | ||
of valid python identifiers and the '.' character. This is what is | ||||
checked for alias and magic transformations, used for auto-calling, | ||||
etc. | ||||
Brian Granger
|
r2244 | the_rest | ||
dan.milstein
|
r657 | Everything else on the line. | ||
""" | ||||
def __init__(self, line, continue_prompt): | ||||
self.line = line | ||||
self.continue_prompt = continue_prompt | ||||
Brian Granger
|
r2244 | self.pre, self.ifun, self.the_rest = split_user_input(line) | ||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | self.pre_char = self.pre.strip() | ||
if self.pre_char: | ||||
self.pre_whitespace = '' # No whitespace allowd before esc chars | ||||
vivainio
|
r775 | else: | ||
Brian Granger
|
r2244 | self.pre_whitespace = self.pre | ||
dan.milstein
|
r657 | |||
self._oinfo = None | ||||
def ofind(self, ip): | ||||
Brian Granger
|
r2244 | """Do a full, attribute-walking lookup of the ifun in the various | ||
dan.milstein
|
r657 | namespaces for the given IPython InteractiveShell instance. | ||
Return a dict with keys: found,obj,ospace,ismagic | ||||
Note: can cause state changes because of calling getattr, but should | ||||
only be run if autocall is on and if the line hasn't matched any | ||||
other, less dangerous handlers. | ||||
Does cache the results of the call, so can be called multiple times | ||||
without worrying about *further* damaging state. | ||||
""" | ||||
if not self._oinfo: | ||||
Brian Granger
|
r2498 | # ip.shell._ofind is actually on the Magic class! | ||
Fernando Perez
|
r2371 | self._oinfo = ip.shell._ofind(self.ifun) | ||
dan.milstein
|
r657 | return self._oinfo | ||
Brian Granger
|
r2244 | |||
vivainio
|
r776 | def __str__(self): | ||
Brian Granger
|
r2498 | return "Lineinfo [%s|%s|%s]" %(self.pre, self.ifun, self.the_rest) | ||
Brian Granger
|
r2244 | |||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | #----------------------------------------------------------------------------- | ||
# Main Prefilter manager | ||||
#----------------------------------------------------------------------------- | ||||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | |||
Brian Granger
|
r2731 | class PrefilterManager(Configurable): | ||
Brian Granger
|
r2244 | """Main prefilter component. | ||
The IPython prefilter is run on all user input before it is run. The | ||||
prefilter consumes lines of input and produces transformed lines of | ||||
Brian Granger
|
r2273 | input. | ||
The iplementation consists of two phases: | ||||
1. Transformers | ||||
2. Checkers and handlers | ||||
Over time, we plan on deprecating the checkers and handlers and doing | ||||
everything in the transformers. | ||||
The transformers are instances of :class:`PrefilterTransformer` and have | ||||
a single method :meth:`transform` that takes a line and returns a | ||||
transformed line. The transformation can be accomplished using any | ||||
tool, but our current ones use regular expressions for speed. We also | ||||
ship :mod:`pyparsing` in :mod:`IPython.external` for use in transformers. | ||||
After all the transformers have been run, the line is fed to the checkers, | ||||
which are instances of :class:`PrefilterChecker`. The line is passed to | ||||
the :meth:`check` method, which either returns `None` or a | ||||
:class:`PrefilterHandler` instance. If `None` is returned, the other | ||||
checkers are tried. If an :class:`PrefilterHandler` instance is returned, | ||||
the line is passed to the :meth:`handle` method of the returned | ||||
handler and no further checkers are tried. | ||||
Both transformers and checkers have a `priority` attribute, that determines | ||||
the order in which they are called. Smaller priorities are tried first. | ||||
Both transformers and checkers also have `enabled` attribute, which is | ||||
a boolean that determines if the instance is used. | ||||
Users or developers can change the priority or enabled attribute of | ||||
transformers or checkers, but they must call the :meth:`sort_checkers` | ||||
Brian Granger
|
r2277 | or :meth:`sort_transformers` method after changing the priority. | ||
dan.milstein
|
r657 | """ | ||
Brian Granger
|
r2244 | |||
Brian Granger
|
r2245 | multi_line_specials = CBool(True, config=True) | ||
Brian Granger
|
r2760 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | ||
Brian Granger
|
r2244 | |||
Brian Granger
|
r2740 | def __init__(self, shell=None, config=None): | ||
super(PrefilterManager, self).__init__(shell=shell, config=config) | ||||
Brian Granger
|
r2731 | self.shell = shell | ||
Brian Granger
|
r2273 | self.init_transformers() | ||
Brian Granger
|
r2244 | self.init_handlers() | ||
self.init_checkers() | ||||
Brian Granger
|
r2273 | #------------------------------------------------------------------------- | ||
# API for managing transformers | ||||
#------------------------------------------------------------------------- | ||||
def init_transformers(self): | ||||
"""Create the default transformers.""" | ||||
self._transformers = [] | ||||
for transformer_cls in _default_transformers: | ||||
Brian Granger
|
r2740 | transformer_cls( | ||
shell=self.shell, prefilter_manager=self, config=self.config | ||||
) | ||||
Brian Granger
|
r2273 | |||
def sort_transformers(self): | ||||
"""Sort the transformers by priority. | ||||
This must be called after the priority of a transformer is changed. | ||||
The :meth:`register_transformer` method calls this automatically. | ||||
""" | ||||
Thomas Kluyver
|
r3108 | self._transformers.sort(key=lambda x: x.priority) | ||
Brian Granger
|
r2273 | |||
@property | ||||
def transformers(self): | ||||
"""Return a list of checkers, sorted by priority.""" | ||||
return self._transformers | ||||
def register_transformer(self, transformer): | ||||
"""Register a transformer instance.""" | ||||
if transformer not in self._transformers: | ||||
self._transformers.append(transformer) | ||||
self.sort_transformers() | ||||
def unregister_transformer(self, transformer): | ||||
"""Unregister a transformer instance.""" | ||||
if transformer in self._transformers: | ||||
self._transformers.remove(transformer) | ||||
#------------------------------------------------------------------------- | ||||
# API for managing checkers | ||||
#------------------------------------------------------------------------- | ||||
Brian Granger
|
r2244 | def init_checkers(self): | ||
Brian Granger
|
r2273 | """Create the default checkers.""" | ||
Brian Granger
|
r2244 | self._checkers = [] | ||
for checker in _default_checkers: | ||||
Brian Granger
|
r2740 | checker( | ||
shell=self.shell, prefilter_manager=self, config=self.config | ||||
) | ||||
Brian Granger
|
r2273 | |||
def sort_checkers(self): | ||||
"""Sort the checkers by priority. | ||||
This must be called after the priority of a checker is changed. | ||||
The :meth:`register_checker` method calls this automatically. | ||||
""" | ||||
Thomas Kluyver
|
r3108 | self._checkers.sort(key=lambda x: x.priority) | ||
Brian Granger
|
r2273 | |||
@property | ||||
def checkers(self): | ||||
"""Return a list of checkers, sorted by priority.""" | ||||
return self._checkers | ||||
def register_checker(self, checker): | ||||
"""Register a checker instance.""" | ||||
if checker not in self._checkers: | ||||
self._checkers.append(checker) | ||||
self.sort_checkers() | ||||
def unregister_checker(self, checker): | ||||
"""Unregister a checker instance.""" | ||||
if checker in self._checkers: | ||||
self._checkers.remove(checker) | ||||
#------------------------------------------------------------------------- | ||||
# API for managing checkers | ||||
#------------------------------------------------------------------------- | ||||
Brian Granger
|
r2244 | |||
def init_handlers(self): | ||||
Brian Granger
|
r2273 | """Create the default handlers.""" | ||
Brian Granger
|
r2244 | self._handlers = {} | ||
self._esc_handlers = {} | ||||
for handler in _default_handlers: | ||||
Brian Granger
|
r2740 | handler( | ||
shell=self.shell, prefilter_manager=self, config=self.config | ||||
) | ||||
Brian Granger
|
r2244 | |||
@property | ||||
Brian Granger
|
r2273 | def handlers(self): | ||
"""Return a dict of all the handlers.""" | ||||
return self._handlers | ||||
Brian Granger
|
r2244 | |||
def register_handler(self, name, handler, esc_strings): | ||||
"""Register a handler instance by name with esc_strings.""" | ||||
self._handlers[name] = handler | ||||
for esc_str in esc_strings: | ||||
self._esc_handlers[esc_str] = handler | ||||
def unregister_handler(self, name, handler, esc_strings): | ||||
"""Unregister a handler instance by name with esc_strings.""" | ||||
dan.milstein
|
r657 | try: | ||
Brian Granger
|
r2244 | del self._handlers[name] | ||
except KeyError: | ||||
pass | ||||
for esc_str in esc_strings: | ||||
h = self._esc_handlers.get(esc_str) | ||||
if h is handler: | ||||
del self._esc_handlers[esc_str] | ||||
def get_handler_by_name(self, name): | ||||
"""Get a handler by its name.""" | ||||
return self._handlers.get(name) | ||||
def get_handler_by_esc(self, esc_str): | ||||
"""Get a handler by its escape string.""" | ||||
return self._esc_handlers.get(esc_str) | ||||
Brian Granger
|
r2273 | #------------------------------------------------------------------------- | ||
# Main prefiltering API | ||||
#------------------------------------------------------------------------- | ||||
Brian Granger
|
r2244 | def prefilter_line_info(self, line_info): | ||
Brian Granger
|
r2273 | """Prefilter a line that has been converted to a LineInfo object. | ||
This implements the checker/handler part of the prefilter pipe. | ||||
""" | ||||
Brian Granger
|
r2256 | # print "prefilter_line_info: ", line_info | ||
Brian Granger
|
r2244 | handler = self.find_handler(line_info) | ||
return handler.handle(line_info) | ||||
def find_handler(self, line_info): | ||||
"""Find a handler for the line_info by trying checkers.""" | ||||
Brian Granger
|
r2273 | for checker in self.checkers: | ||
if checker.enabled: | ||||
handler = checker.check(line_info) | ||||
if handler: | ||||
return handler | ||||
Brian Granger
|
r2244 | return self.get_handler_by_name('normal') | ||
Brian Granger
|
r2273 | def transform_line(self, line, continue_prompt): | ||
"""Calls the enabled transformers in order of increasing priority.""" | ||||
for transformer in self.transformers: | ||||
if transformer.enabled: | ||||
line = transformer.transform(line, continue_prompt) | ||||
return line | ||||
Fernando Perez
|
r2426 | def prefilter_line(self, line, continue_prompt=False): | ||
Brian Granger
|
r2273 | """Prefilter a single input line as text. | ||
This method prefilters a single line of text by calling the | ||||
transformers and then the checkers/handlers. | ||||
""" | ||||
Brian Granger
|
r2244 | |||
Brian Granger
|
r2256 | # print "prefilter_line: ", line, continue_prompt | ||
Brian Granger
|
r2244 | # All handlers *must* return a value, even if it's blank (''). | ||
# save the line away in case we crash, so the post-mortem handler can | ||||
Brian Granger
|
r2256 | # record it | ||
Brian Granger
|
r2244 | self.shell._last_input_line = line | ||
if not line: | ||||
# Return immediately on purely empty lines, so that if the user | ||||
# previously typed some whitespace that started a continuation | ||||
# prompt, he can break out of that loop with just an empty line. | ||||
# This is how the default python prompt works. | ||||
# Only return if the accumulated input buffer was just whitespace! | ||||
if ''.join(self.shell.buffer).isspace(): | ||||
self.shell.buffer[:] = [] | ||||
return '' | ||||
Brian Granger
|
r2273 | |||
# At this point, we invoke our transformers. | ||||
if not continue_prompt or (continue_prompt and self.multi_line_specials): | ||||
line = self.transform_line(line, continue_prompt) | ||||
# Now we compute line_info for the checkers and handlers | ||||
Brian Granger
|
r2244 | line_info = LineInfo(line, continue_prompt) | ||
# the input history needs to track even empty lines | ||||
stripped = line.strip() | ||||
dan.milstein
|
r657 | |||
Brian Granger
|
r2273 | normal_handler = self.get_handler_by_name('normal') | ||
Brian Granger
|
r2244 | if not stripped: | ||
if not continue_prompt: | ||||
Brian Granger
|
r2781 | self.shell.displayhook.prompt_count -= 1 | ||
dan.milstein
|
r657 | |||
Brian Granger
|
r2245 | return normal_handler.handle(line_info) | ||
dan.milstein
|
r670 | |||
Brian Granger
|
r2244 | # special handlers are only allowed for single line statements | ||
if continue_prompt and not self.multi_line_specials: | ||||
Brian Granger
|
r2245 | return normal_handler.handle(line_info) | ||
dan.milstein
|
r670 | |||
Brian Granger
|
r2256 | prefiltered = self.prefilter_line_info(line_info) | ||
# print "prefiltered line: %r" % prefiltered | ||||
return prefiltered | ||||
dan.milstein
|
r657 | |||
Fernando Perez
|
r2414 | def prefilter_lines(self, lines, continue_prompt=False): | ||
Brian Granger
|
r2244 | """Prefilter multiple input lines of text. | ||
dan.milstein
|
r670 | |||
Brian Granger
|
r2273 | This is the main entry point for prefiltering multiple lines of | ||
input. This simply calls :meth:`prefilter_line` for each line of | ||||
input. | ||||
This covers cases where there are multiple lines in the user entry, | ||||
Brian Granger
|
r2244 | which is the case when the user goes back to a multiline history | ||
entry and presses enter. | ||||
""" | ||||
Fernando Perez
|
r2435 | llines = lines.rstrip('\n').split('\n') | ||
# We can get multiple lines in one shot, where multiline input 'blends' | ||||
# into one line, in cases like recalling from the readline history | ||||
# buffer. We need to make sure that in such cases, we correctly | ||||
# communicate downstream which line is first and which are continuation | ||||
# ones. | ||||
if len(llines) > 1: | ||||
out = '\n'.join([self.prefilter_line(line, lnum>0) | ||||
for lnum, line in enumerate(llines) ]) | ||||
else: | ||||
out = self.prefilter_line(llines[0], continue_prompt) | ||||
return out | ||||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | #----------------------------------------------------------------------------- | ||
Brian Granger
|
r2273 | # Prefilter transformers | ||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2731 | class PrefilterTransformer(Configurable): | ||
Brian Granger
|
r2273 | """Transform a line of user input.""" | ||
priority = Int(100, config=True) | ||||
Brian Granger
|
r2731 | # Transformers don't currently use shell or prefilter_manager, but as we | ||
# move away from checkers and handlers, they will need them. | ||||
Brian Granger
|
r2760 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | ||
Brian Granger
|
r2731 | prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager') | ||
Brian Granger
|
r2273 | enabled = Bool(True, config=True) | ||
Brian Granger
|
r2740 | def __init__(self, shell=None, prefilter_manager=None, config=None): | ||
super(PrefilterTransformer, self).__init__( | ||||
shell=shell, prefilter_manager=prefilter_manager, config=config | ||||
) | ||||
Brian Granger
|
r2273 | self.prefilter_manager.register_transformer(self) | ||
def transform(self, line, continue_prompt): | ||||
"""Transform a line, returning the new one.""" | ||||
return None | ||||
def __repr__(self): | ||||
return "<%s(priority=%r, enabled=%r)>" % ( | ||||
self.__class__.__name__, self.priority, self.enabled) | ||||
_assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))' | ||||
r'\s*=\s*!(?P<cmd>.*)') | ||||
class AssignSystemTransformer(PrefilterTransformer): | ||||
"""Handle the `files = !ls` syntax.""" | ||||
priority = Int(100, config=True) | ||||
def transform(self, line, continue_prompt): | ||||
m = _assign_system_re.match(line) | ||||
if m is not None: | ||||
cmd = m.group('cmd') | ||||
lhs = m.group('lhs') | ||||
Fernando Perez
|
r3002 | expr = make_quoted_expr("sc =%s" % cmd) | ||
Brian Granger
|
r2273 | new_line = '%s = get_ipython().magic(%s)' % (lhs, expr) | ||
return new_line | ||||
return line | ||||
_assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))' | ||||
r'\s*=\s*%(?P<cmd>.*)') | ||||
class AssignMagicTransformer(PrefilterTransformer): | ||||
"""Handle the `a = %who` syntax.""" | ||||
priority = Int(200, config=True) | ||||
def transform(self, line, continue_prompt): | ||||
m = _assign_magic_re.match(line) | ||||
if m is not None: | ||||
cmd = m.group('cmd') | ||||
lhs = m.group('lhs') | ||||
expr = make_quoted_expr(cmd) | ||||
new_line = '%s = get_ipython().magic(%s)' % (lhs, expr) | ||||
return new_line | ||||
return line | ||||
Fernando Perez
|
r2426 | _classic_prompt_re = re.compile(r'(^[ \t]*>>> |^[ \t]*\.\.\. )') | ||
class PyPromptTransformer(PrefilterTransformer): | ||||
"""Handle inputs that start with '>>> ' syntax.""" | ||||
priority = Int(50, config=True) | ||||
def transform(self, line, continue_prompt): | ||||
if not line or line.isspace() or line.strip() == '...': | ||||
# This allows us to recognize multiple input prompts separated by | ||||
# blank lines and pasted in a single chunk, very common when | ||||
# pasting doctests or long tutorial passages. | ||||
return '' | ||||
m = _classic_prompt_re.match(line) | ||||
if m: | ||||
return line[len(m.group(0)):] | ||||
else: | ||||
return line | ||||
_ipy_prompt_re = re.compile(r'(^[ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )') | ||||
class IPyPromptTransformer(PrefilterTransformer): | ||||
"""Handle inputs that start classic IPython prompt syntax.""" | ||||
priority = Int(50, config=True) | ||||
def transform(self, line, continue_prompt): | ||||
if not line or line.isspace() or line.strip() == '...': | ||||
# This allows us to recognize multiple input prompts separated by | ||||
# blank lines and pasted in a single chunk, very common when | ||||
# pasting doctests or long tutorial passages. | ||||
return '' | ||||
m = _ipy_prompt_re.match(line) | ||||
if m: | ||||
return line[len(m.group(0)):] | ||||
else: | ||||
return line | ||||
Brian Granger
|
r2273 | #----------------------------------------------------------------------------- | ||
Brian Granger
|
r2244 | # Prefilter checkers | ||
#----------------------------------------------------------------------------- | ||||
dan.milstein
|
r657 | |||
Brian Granger
|
r2731 | class PrefilterChecker(Configurable): | ||
Brian Granger
|
r2244 | """Inspect an input line and return a handler for that line.""" | ||
dan.milstein
|
r657 | |||
Brian Granger
|
r2245 | priority = Int(100, config=True) | ||
Brian Granger
|
r2760 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | ||
Brian Granger
|
r2731 | prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager') | ||
Brian Granger
|
r2273 | enabled = Bool(True, config=True) | ||
dan.milstein
|
r657 | |||
Brian Granger
|
r2740 | def __init__(self, shell=None, prefilter_manager=None, config=None): | ||
super(PrefilterChecker, self).__init__( | ||||
shell=shell, prefilter_manager=prefilter_manager, config=config | ||||
) | ||||
Brian Granger
|
r2273 | self.prefilter_manager.register_checker(self) | ||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | def check(self, line_info): | ||
Brian Granger
|
r2273 | """Inspect line_info and return a handler instance or None.""" | ||
dan.milstein
|
r657 | return None | ||
Brian Granger
|
r2273 | def __repr__(self): | ||
return "<%s(priority=%r, enabled=%r)>" % ( | ||||
self.__class__.__name__, self.priority, self.enabled) | ||||
Brian Granger
|
r2244 | |||
class EmacsChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(100, config=True) | ||
Brian Granger
|
r2273 | enabled = Bool(False, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"Emacs ipython-mode tags certain input lines." | ||||
if line_info.line.endswith('# PYTHON-MODE'): | ||||
return self.prefilter_manager.get_handler_by_name('emacs') | ||||
else: | ||||
return None | ||||
class ShellEscapeChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(200, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
if line_info.line.lstrip().startswith(ESC_SHELL): | ||||
return self.prefilter_manager.get_handler_by_name('shell') | ||||
Thomas Kluyver
|
r3406 | class MacroChecker(PrefilterChecker): | ||
priority = Int(250, config=True) | ||||
def check(self, line_info): | ||||
obj = self.shell.user_ns.get(line_info.ifun) | ||||
if isinstance(obj, Macro): | ||||
return self.prefilter_manager.get_handler_by_name('macro') | ||||
else: | ||||
return None | ||||
Brian Granger
|
r2244 | class IPyAutocallChecker(PrefilterChecker): | ||
Brian Granger
|
r2245 | priority = Int(300, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"Instances of IPyAutocall in user_ns get autocalled immediately" | ||||
obj = self.shell.user_ns.get(line_info.ifun, None) | ||||
if isinstance(obj, IPyAutocall): | ||||
obj.set_ip(self.shell) | ||||
return self.prefilter_manager.get_handler_by_name('auto') | ||||
else: | ||||
return None | ||||
class MultiLineMagicChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(400, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"Allow ! and !! in multi-line statements if multi_line_specials is on" | ||||
# Note that this one of the only places we check the first character of | ||||
# ifun and *not* the pre_char. Also note that the below test matches | ||||
# both ! and !!. | ||||
if line_info.continue_prompt \ | ||||
and self.prefilter_manager.multi_line_specials: | ||||
if line_info.ifun.startswith(ESC_MAGIC): | ||||
return self.prefilter_manager.get_handler_by_name('magic') | ||||
else: | ||||
return None | ||||
class EscCharsChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(500, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"""Check for escape character and return either a handler to handle it, | ||||
or None if there is no escape char.""" | ||||
if line_info.line[-1] == ESC_HELP \ | ||||
and line_info.pre_char != ESC_SHELL \ | ||||
and line_info.pre_char != ESC_SH_CAP: | ||||
# the ? can be at the end, but *not* for either kind of shell escape, | ||||
# because a ? can be a vaild final char in a shell cmd | ||||
return self.prefilter_manager.get_handler_by_name('help') | ||||
else: | ||||
# This returns None like it should if no handler exists | ||||
return self.prefilter_manager.get_handler_by_esc(line_info.pre_char) | ||||
class AssignmentChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(600, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"""Check to see if user is assigning to a var for the first time, in | ||||
which case we want to avoid any sort of automagic / autocall games. | ||||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | This allows users to assign to either alias or magic names true python | ||
variables (the magic/alias systems always take second seat to true | ||||
python code). E.g. ls='hi', or ls,that=1,2""" | ||||
Brian Granger
|
r2256 | if line_info.the_rest: | ||
if line_info.the_rest[0] in '=,': | ||||
return self.prefilter_manager.get_handler_by_name('normal') | ||||
Brian Granger
|
r2244 | else: | ||
return None | ||||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | class AutoMagicChecker(PrefilterChecker): | ||
dan.milstein
|
r657 | |||
Brian Granger
|
r2245 | priority = Int(700, config=True) | ||
dan.milstein
|
r657 | |||
Brian Granger
|
r2244 | def check(self, line_info): | ||
"""If the ifun is magic, and automagic is on, run it. Note: normal, | ||||
non-auto magic would already have been triggered via '%' in | ||||
check_esc_chars. This just checks for automagic. Also, before | ||||
triggering the magic handler, make sure that there is nothing in the | ||||
user namespace which could shadow it.""" | ||||
if not self.shell.automagic or not hasattr(self.shell,'magic_'+line_info.ifun): | ||||
return None | ||||
# We have a likely magic method. Make sure we should actually call it. | ||||
Paul Ivanov
|
r2619 | if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials: | ||
Brian Granger
|
r2244 | return None | ||
head = line_info.ifun.split('.',1)[0] | ||||
if is_shadowed(head, self.shell): | ||||
return None | ||||
return self.prefilter_manager.get_handler_by_name('magic') | ||||
class AliasChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(800, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"Check if the initital identifier on the line is an alias." | ||||
# Note: aliases can not contain '.' | ||||
head = line_info.ifun.split('.',1)[0] | ||||
Brian Granger
|
r2731 | if line_info.ifun not in self.shell.alias_manager \ | ||
or head not in self.shell.alias_manager \ | ||||
Brian Granger
|
r2244 | or is_shadowed(head, self.shell): | ||
return None | ||||
return self.prefilter_manager.get_handler_by_name('alias') | ||||
class PythonOpsChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(900, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"""If the 'rest' of the line begins with a function call or pretty much | ||||
any python operator, we should simply execute the line (regardless of | ||||
whether or not there's a possible autocall expansion). This avoids | ||||
spurious (and very confusing) geattr() accesses.""" | ||||
if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|': | ||||
return self.prefilter_manager.get_handler_by_name('normal') | ||||
else: | ||||
return None | ||||
class AutocallChecker(PrefilterChecker): | ||||
Brian Granger
|
r2245 | priority = Int(1000, config=True) | ||
Brian Granger
|
r2244 | |||
def check(self, line_info): | ||||
"Check if the initial word/function is callable and autocall is on." | ||||
if not self.shell.autocall: | ||||
return None | ||||
oinfo = line_info.ofind(self.shell) # This can mutate state via getattr | ||||
if not oinfo['found']: | ||||
return None | ||||
if callable(oinfo['obj']) \ | ||||
and (not re_exclude_auto.match(line_info.the_rest)) \ | ||||
and re_fun_name.match(line_info.ifun): | ||||
return self.prefilter_manager.get_handler_by_name('auto') | ||||
else: | ||||
return None | ||||
#----------------------------------------------------------------------------- | ||||
# Prefilter handlers | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2731 | class PrefilterHandler(Configurable): | ||
Brian Granger
|
r2244 | |||
handler_name = Str('normal') | ||||
esc_strings = List([]) | ||||
Brian Granger
|
r2760 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | ||
Brian Granger
|
r2731 | prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager') | ||
Brian Granger
|
r2244 | |||
Brian Granger
|
r2740 | def __init__(self, shell=None, prefilter_manager=None, config=None): | ||
super(PrefilterHandler, self).__init__( | ||||
shell=shell, prefilter_manager=prefilter_manager, config=config | ||||
) | ||||
Brian Granger
|
r2244 | self.prefilter_manager.register_handler( | ||
self.handler_name, | ||||
self, | ||||
self.esc_strings | ||||
) | ||||
def handle(self, line_info): | ||||
Brian Granger
|
r2256 | # print "normal: ", line_info | ||
Brian Granger
|
r2244 | """Handle normal input lines. Use as a template for handlers.""" | ||
# With autoindent on, we need some way to exit the input loop, and I | ||||
# don't want to force the user to have to backspace all the way to | ||||
# clear the line. The rule will be in this case, that either two | ||||
# lines of pure whitespace in a row, or a line of pure whitespace but | ||||
# of a size different to the indent level, will exit the input loop. | ||||
line = line_info.line | ||||
continue_prompt = line_info.continue_prompt | ||||
Fernando Perez
|
r2419 | if (continue_prompt and | ||
self.shell.autoindent and | ||||
line.isspace() and | ||||
(0 < abs(len(line) - self.shell.indent_current_nsp) <= 2 | ||||
or | ||||
not self.shell.buffer | ||||
or | ||||
(self.shell.buffer[-1]).isspace() | ||||
) | ||||
): | ||||
Brian Granger
|
r2244 | line = '' | ||
return line | ||||
Brian Granger
|
r2256 | def __str__(self): | ||
return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name) | ||||
Brian Granger
|
r2244 | |||
class AliasHandler(PrefilterHandler): | ||||
handler_name = Str('alias') | ||||
def handle(self, line_info): | ||||
"""Handle alias input lines. """ | ||||
Brian Granger
|
r2731 | transformed = self.shell.alias_manager.expand_aliases(line_info.ifun,line_info.the_rest) | ||
Brian Granger
|
r2244 | # pre is needed, because it carries the leading whitespace. Otherwise | ||
# aliases won't work in indented sections. | ||||
Brian Granger
|
r2245 | line_out = '%sget_ipython().system(%s)' % (line_info.pre_whitespace, | ||
Brian Granger
|
r2244 | make_quoted_expr(transformed)) | ||
return line_out | ||||
class ShellEscapeHandler(PrefilterHandler): | ||||
handler_name = Str('shell') | ||||
esc_strings = List([ESC_SHELL, ESC_SH_CAP]) | ||||
def handle(self, line_info): | ||||
"""Execute the line in a shell, empty return value""" | ||||
magic_handler = self.prefilter_manager.get_handler_by_name('magic') | ||||
line = line_info.line | ||||
if line.lstrip().startswith(ESC_SH_CAP): | ||||
# rewrite LineInfo's line, ifun and the_rest to properly hold the | ||||
# call to %sx and the actual command to be executed, so | ||||
# handle_magic can work correctly. Note that this works even if | ||||
# the line is indented, so it handles multi_line_specials | ||||
# properly. | ||||
new_rest = line.lstrip()[2:] | ||||
line_info.line = '%ssx %s' % (ESC_MAGIC, new_rest) | ||||
line_info.ifun = 'sx' | ||||
line_info.the_rest = new_rest | ||||
return magic_handler.handle(line_info) | ||||
else: | ||||
cmd = line.lstrip().lstrip(ESC_SHELL) | ||||
Brian Granger
|
r2245 | line_out = '%sget_ipython().system(%s)' % (line_info.pre_whitespace, | ||
Brian Granger
|
r2244 | make_quoted_expr(cmd)) | ||
return line_out | ||||
Thomas Kluyver
|
r3406 | class MacroHandler(PrefilterHandler): | ||
handler_name = Str("macro") | ||||
def handle(self, line_info): | ||||
obj = self.shell.user_ns.get(line_info.ifun) | ||||
pre_space = line_info.pre_whitespace | ||||
line_sep = "\n" + pre_space | ||||
return pre_space + line_sep.join(obj.value.splitlines()) | ||||
Brian Granger
|
r2244 | class MagicHandler(PrefilterHandler): | ||
handler_name = Str('magic') | ||||
Brian Granger
|
r2245 | esc_strings = List([ESC_MAGIC]) | ||
Brian Granger
|
r2244 | |||
def handle(self, line_info): | ||||
"""Execute magic functions.""" | ||||
ifun = line_info.ifun | ||||
the_rest = line_info.the_rest | ||||
Brian Granger
|
r2245 | cmd = '%sget_ipython().magic(%s)' % (line_info.pre_whitespace, | ||
Brian Granger
|
r2244 | make_quoted_expr(ifun + " " + the_rest)) | ||
return cmd | ||||
class AutoHandler(PrefilterHandler): | ||||
handler_name = Str('auto') | ||||
esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2]) | ||||
def handle(self, line_info): | ||||
Brian Granger
|
r2731 | """Handle lines which can be auto-executed, quoting if requested.""" | ||
Brian Granger
|
r2244 | line = line_info.line | ||
ifun = line_info.ifun | ||||
the_rest = line_info.the_rest | ||||
pre = line_info.pre | ||||
continue_prompt = line_info.continue_prompt | ||||
obj = line_info.ofind(self)['obj'] | ||||
#print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun,the_rest) # dbg | ||||
# This should only be active for single-line input! | ||||
if continue_prompt: | ||||
return line | ||||
force_auto = isinstance(obj, IPyAutocall) | ||||
auto_rewrite = True | ||||
if pre == ESC_QUOTE: | ||||
# Auto-quote splitting on whitespace | ||||
newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) ) | ||||
elif pre == ESC_QUOTE2: | ||||
# Auto-quote whole string | ||||
newcmd = '%s("%s")' % (ifun,the_rest) | ||||
elif pre == ESC_PAREN: | ||||
newcmd = '%s(%s)' % (ifun,",".join(the_rest.split())) | ||||
else: | ||||
# Auto-paren. | ||||
# We only apply it to argument-less calls if the autocall | ||||
# parameter is set to 2. We only need to check that autocall is < | ||||
# 2, since this function isn't called unless it's at least 1. | ||||
Brian Granger
|
r2245 | if not the_rest and (self.shell.autocall < 2) and not force_auto: | ||
Brian Granger
|
r2244 | newcmd = '%s %s' % (ifun,the_rest) | ||
auto_rewrite = False | ||||
else: | ||||
if not force_auto and the_rest.startswith('['): | ||||
if hasattr(obj,'__getitem__'): | ||||
# Don't autocall in this case: item access for an object | ||||
# which is BOTH callable and implements __getitem__. | ||||
newcmd = '%s %s' % (ifun,the_rest) | ||||
auto_rewrite = False | ||||
else: | ||||
# if the object doesn't support [] access, go ahead and | ||||
# autocall | ||||
newcmd = '%s(%s)' % (ifun.rstrip(),the_rest) | ||||
elif the_rest.endswith(';'): | ||||
newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1]) | ||||
else: | ||||
newcmd = '%s(%s)' % (ifun.rstrip(), the_rest) | ||||
if auto_rewrite: | ||||
Fernando Perez
|
r2951 | self.shell.auto_rewrite_input(newcmd) | ||
Brian Granger
|
r2244 | |||
return newcmd | ||||
class HelpHandler(PrefilterHandler): | ||||
handler_name = Str('help') | ||||
esc_strings = List([ESC_HELP]) | ||||
def handle(self, line_info): | ||||
"""Try to get some help for the object. | ||||
obj? or ?obj -> basic information. | ||||
obj?? or ??obj -> more details. | ||||
""" | ||||
normal_handler = self.prefilter_manager.get_handler_by_name('normal') | ||||
line = line_info.line | ||||
# We need to make sure that we don't process lines which would be | ||||
# otherwise valid python, such as "x=1 # what?" | ||||
try: | ||||
codeop.compile_command(line) | ||||
except SyntaxError: | ||||
# We should only handle as help stuff which is NOT valid syntax | ||||
if line[0]==ESC_HELP: | ||||
line = line[1:] | ||||
elif line[-1]==ESC_HELP: | ||||
line = line[:-1] | ||||
if line: | ||||
#print 'line:<%r>' % line # dbg | ||||
self.shell.magic_pinfo(line) | ||||
else: | ||||
Fernando Perez
|
r2876 | self.shell.show_usage() | ||
Brian Granger
|
r2244 | return '' # Empty string is needed here! | ||
except: | ||||
raise | ||||
# Pass any other exceptions through to the normal handler | ||||
return normal_handler.handle(line_info) | ||||
else: | ||||
# If the code compiles ok, we should handle it normally | ||||
return normal_handler.handle(line_info) | ||||
class EmacsHandler(PrefilterHandler): | ||||
handler_name = Str('emacs') | ||||
esc_strings = List([]) | ||||
def handle(self, line_info): | ||||
"""Handle input lines marked by python-mode.""" | ||||
# Currently, nothing is done. Later more functionality can be added | ||||
# here if needed. | ||||
# The input cache shouldn't be updated | ||||
return line_info.line | ||||
#----------------------------------------------------------------------------- | ||||
# Defaults | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2273 | _default_transformers = [ | ||
AssignSystemTransformer, | ||||
Fernando Perez
|
r2426 | AssignMagicTransformer, | ||
PyPromptTransformer, | ||||
IPyPromptTransformer, | ||||
Brian Granger
|
r2273 | ] | ||
Brian Granger
|
r2244 | _default_checkers = [ | ||
EmacsChecker, | ||||
ShellEscapeChecker, | ||||
Thomas Kluyver
|
r3406 | MacroChecker, | ||
Brian Granger
|
r2244 | IPyAutocallChecker, | ||
MultiLineMagicChecker, | ||||
EscCharsChecker, | ||||
AssignmentChecker, | ||||
AutoMagicChecker, | ||||
AliasChecker, | ||||
PythonOpsChecker, | ||||
AutocallChecker | ||||
] | ||||
_default_handlers = [ | ||||
PrefilterHandler, | ||||
AliasHandler, | ||||
ShellEscapeHandler, | ||||
Thomas Kluyver
|
r3406 | MacroHandler, | ||
Brian Granger
|
r2244 | MagicHandler, | ||
AutoHandler, | ||||
HelpHandler, | ||||
EmacsHandler | ||||
] | ||||