diff --git a/IPython/core/alias.py b/IPython/core/alias.py new file mode 100644 index 0000000..0b2ec60 --- /dev/null +++ b/IPython/core/alias.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +IPython's alias component + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# 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 keyword +import os +import sys + +from IPython.core.component import Component + +from IPython.utils.traitlets import CBool, List, Instance +from IPython.utils.genutils import error + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- + +def default_aliases(): + # Make some aliases automatically + # Prepare list of shell aliases to auto-define + if os.name == 'posix': + auto_alias = ('mkdir mkdir', 'rmdir rmdir', + 'mv mv -i','rm rm -i','cp cp -i', + 'cat cat','less less','clear clear', + # a better ls + 'ls ls -F', + # long ls + 'll ls -lF') + # Extra ls aliases with color, which need special treatment on BSD + # variants + ls_extra = ( # color ls + 'lc ls -F -o --color', + # ls normal files only + 'lf ls -F -o --color %l | grep ^-', + # ls symbolic links + 'lk ls -F -o --color %l | grep ^l', + # directories or links to directories, + 'ldir ls -F -o --color %l | grep /$', + # things which are executable + 'lx ls -F -o --color %l | grep ^-..x', + ) + # The BSDs don't ship GNU ls, so they don't understand the + # --color switch out of the box + if 'bsd' in sys.platform: + ls_extra = ( # ls normal files only + 'lf ls -lF | grep ^-', + # ls symbolic links + 'lk ls -lF | grep ^l', + # directories or links to directories, + 'ldir ls -lF | grep /$', + # things which are executable + 'lx ls -lF | grep ^-..x', + ) + auto_alias = auto_alias + ls_extra + elif os.name in ['nt','dos']: + auto_alias = ('ls dir /on', + 'ddir dir /ad /on', 'ldir dir /ad /on', + 'mkdir mkdir','rmdir rmdir','echo echo', + 'ren ren','cls cls','copy copy') + else: + auto_alias = () + return [s.split(None,1) for s in auto_alias] + + +class AliasError(Exception): + pass + + +class InvalidAliasError(AliasError): + pass + + +class AliasManager(Component): + + auto_alias = List(default_aliases()) + user_alias = List(default_value=[], config_key='USER_ALIAS') + + def __init__(self, parent, config=None): + super(AliasManager, self).__init__(parent, config=config) + self.shell = Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell' + )[0] + self.alias_table = {} + self.exclude_aliases() + self.init_aliases() + + def __contains__(self, name): + if name in self.alias_table: + return True + else: + return False + + @property + def aliases(self): + return [(item[0], item[1][1]) for item in self.alias_table.iteritems()] + + def exclude_aliases(self): + # set of things NOT to alias (keywords, builtins and some magics) + no_alias = set(['cd','popd','pushd','dhist','alias','unalias']) + no_alias.update(set(keyword.kwlist)) + no_alias.update(set(__builtin__.__dict__.keys())) + self.no_alias = no_alias + + def init_aliases(self): + # Load default aliases + for name, cmd in self.auto_alias: + self.soft_define_alias(name, cmd) + + # Load user aliases + for name, cmd in self.user_alias: + self.soft_define_alias(name, cmd) + + def soft_define_alias(self, name, cmd): + """Define an alias, but don't raise on an AliasError.""" + try: + self.define_alias(name, cmd) + except AliasError, e: + error("Invalid alias: %s" % e) + + def define_alias(self, name, cmd): + """Define a new alias after validating it. + + This will raise an :exc:`AliasError` if there are validation + problems. + """ + nargs = self.validate_alias(name, cmd) + self.alias_table[name] = (nargs, cmd) + + def validate_alias(self, name, cmd): + """Validate an alias and return the its number of arguments.""" + if name in self.no_alias: + raise InvalidAliasError("The name %s can't be aliased " + "because it is a keyword or builtin." % name) + if not (isinstance(cmd, basestring)): + raise InvalidAliasError("An alias command must be a string, " + "got: %r" % name) + nargs = cmd.count('%s') + if nargs>0 and cmd.find('%l')>=0: + raise InvalidAliasError('The %s and %l specifiers are mutually ' + 'exclusive in alias definitions.') + return nargs + + def call_alias(self, alias, rest=''): + """Call an alias given its name and the rest of the line.""" + cmd = self.transform_alias(alias, rest) + try: + self.shell.system(cmd) + except: + self.shell.showtraceback() + + def transform_alias(self, alias,rest=''): + """Transform alias to system command string.""" + nargs, cmd = self.alias_table[alias] + + if ' ' in cmd and os.path.isfile(cmd): + cmd = '"%s"' % cmd + + # Expand the %l special to be the user's input line + if cmd.find('%l') >= 0: + cmd = cmd.replace('%l', rest) + rest = '' + if nargs==0: + # Simple, argument-less aliases + cmd = '%s %s' % (cmd, rest) + else: + # Handle aliases with positional arguments + args = rest.split(None, nargs) + if len(args) < nargs: + raise AliasError('Alias <%s> requires %s arguments, %s given.' % + (alias, nargs, len(args))) + cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:])) + return cmd diff --git a/IPython/core/application.py b/IPython/core/application.py index 5c528ff..9378f9e 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -228,6 +228,7 @@ class Application(object): self.print_traceback() self.abort() elif action == 'exit': + self.print_traceback() self.exit() def print_traceback(self): diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py index 28996e5..a80e5c8 100644 --- a/IPython/core/builtin_trap.py +++ b/IPython/core/builtin_trap.py @@ -36,13 +36,14 @@ BuiltinUndefined = BuiltinUndefined() class BuiltinTrap(Component): - shell = Instance('IPython.core.iplib.InteractiveShell') def __init__(self, parent): super(BuiltinTrap, self).__init__(parent, None, None) # Don't just grab parent!!! - self.shell = Component.get_instances(root=self.root, - klass='IPython.core.iplib.InteractiveShell')[0] + self.shell = Component.get_instances( + root=self.root, + klass='IPython.core.iplib.InteractiveShell' + )[0] self._orig_builtins = {} def __enter__(self): diff --git a/IPython/core/component.py b/IPython/core/component.py index 5565098..26458a7 100644 --- a/IPython/core/component.py +++ b/IPython/core/component.py @@ -48,22 +48,55 @@ class MetaComponentTracker(type): cls.__numcreated = 0 def __call__(cls, *args, **kw): - """Called when *class* is called (instantiated)!!! + """Called when a class is called (instantiated)!!! When a Component or subclass is instantiated, this is called and the instance is saved in a WeakValueDictionary for tracking. """ instance = cls.__new__(cls, *args, **kw) - # Do this before __init__ is called so get_instances works inside - # __init__ methods! + + # Register the instance before __init__ is called so get_instances + # works inside __init__ methods! + indices = cls.register_instance(instance) + + # This is in a try/except because of the __init__ method fails, the + # instance is discarded and shouldn't be tracked. + try: + if isinstance(instance, cls): + cls.__init__(instance, *args, **kw) + except: + # Unregister the instance because __init__ failed! + cls.unregister_instances(indices) + raise + else: + return instance + + def register_instance(cls, instance): + """Register instance with cls and its subclasses.""" + # indices is a list of the keys used to register the instance + # with. This list is needed if the instance needs to be unregistered. + indices = [] for c in cls.__mro__: if issubclass(cls, c) and issubclass(c, Component): c.__numcreated += 1 + indices.append(c.__numcreated) c.__instance_refs[c.__numcreated] = instance - if isinstance(instance, cls): - cls.__init__(instance, *args, **kw) + else: + break + return indices + + def unregister_instances(cls, indices): + """Unregister instance with cls and its subclasses.""" + for c, index in zip(cls.__mro__, indices): + try: + del c.__instance_refs[index] + except KeyError: + pass - return instance + def clear_instances(cls): + """Clear all instances tracked by cls.""" + cls.__instance_refs.clear() + cls.__numcreated = 0 def get_instances(cls, name=None, root=None, klass=None): """Get all instances of cls and its subclasses. @@ -82,6 +115,9 @@ class MetaComponentTracker(type): if klass is not None: if isinstance(klass, basestring): klass = import_item(klass) + # Limit search to instances of klass for performance + if issubclass(klass, Component): + return klass.get_instances(name=name, root=root) instances = cls.__instance_refs.values() if name is not None: instances = [i for i in instances if i.name == name] @@ -101,6 +137,26 @@ class MetaComponentTracker(type): return [i for i in cls.get_instances(name, root, klass) if call(i)] +def masquerade_as(instance, cls): + """Let instance masquerade as an instance of cls. + + Sometimes, such as in testing code, it is useful to let a class + masquerade as another. Python, being duck typed, allows this by + default. But, instances of components are tracked by their class type. + + After calling this, cls.get_instances() will return ``instance``. This + does not, however, cause isinstance(instance, cls) to return ``True``. + + Parameters + ---------- + instance : an instance of a Component or Component subclass + The instance that will pretend to be a cls. + cls : subclass of Component + The Component subclass that instance will pretend to be. + """ + cls.register_instance(instance) + + class ComponentNameGenerator(object): """A Singleton to generate unique component names.""" @@ -245,4 +301,5 @@ class Component(HasTraitlets): self._children.append(child) def __repr__(self): - return "" % self.name + return "<%s('%s')>" % (self.__class__.__name__, "DummyName") + # return "" % self.name diff --git a/IPython/core/ipapp.py b/IPython/core/ipapp.py index 155d36f..6988fb1 100644 --- a/IPython/core/ipapp.py +++ b/IPython/core/ipapp.py @@ -307,7 +307,8 @@ class IPythonApp(Application): parent=None, config=self.master_config ) - + print self.shell + def start_app(self): self.shell.mainloop() diff --git a/IPython/core/iplib.py b/IPython/core/iplib.py index e186a66..abab486 100644 --- a/IPython/core/iplib.py +++ b/IPython/core/iplib.py @@ -40,6 +40,7 @@ from IPython.core import debugger, oinspect from IPython.core import shadowns from IPython.core import history as ipcorehist from IPython.core import prefilter +from IPython.core.alias import AliasManager from IPython.core.autocall import IPyAutocall from IPython.core.builtin_trap import BuiltinTrap from IPython.core.display_trap import DisplayTrap @@ -62,6 +63,9 @@ from IPython.utils.genutils import * from IPython.utils.strdispatch import StrDispatch from IPython.utils.platutils import toggle_set_term_title, set_term_title +from IPython.utils import growl +growl.start("IPython") + from IPython.utils.traitlets import ( Int, Float, Str, CBool, CaselessStrEnum, Enum, List, Unicode ) @@ -303,7 +307,7 @@ class InteractiveShell(Component, Magic): self.init_traceback_handlers(custom_exceptions) self.init_user_ns() self.init_logger() - self.init_aliases() + self.init_alias() self.init_builtins() # pre_config_initialization @@ -1660,129 +1664,8 @@ class InteractiveShell(Component, Magic): # Things related to aliases #------------------------------------------------------------------------- - def init_aliases(self): - # dict of things NOT to alias (keywords, builtins and some magics) - no_alias = {} - no_alias_magics = ['cd','popd','pushd','dhist','alias','unalias'] - for key in keyword.kwlist + no_alias_magics: - no_alias[key] = 1 - no_alias.update(__builtin__.__dict__) - self.no_alias = no_alias - - # Make some aliases automatically - # Prepare list of shell aliases to auto-define - if os.name == 'posix': - auto_alias = ('mkdir mkdir', 'rmdir rmdir', - 'mv mv -i','rm rm -i','cp cp -i', - 'cat cat','less less','clear clear', - # a better ls - 'ls ls -F', - # long ls - 'll ls -lF') - # Extra ls aliases with color, which need special treatment on BSD - # variants - ls_extra = ( # color ls - 'lc ls -F -o --color', - # ls normal files only - 'lf ls -F -o --color %l | grep ^-', - # ls symbolic links - 'lk ls -F -o --color %l | grep ^l', - # directories or links to directories, - 'ldir ls -F -o --color %l | grep /$', - # things which are executable - 'lx ls -F -o --color %l | grep ^-..x', - ) - # The BSDs don't ship GNU ls, so they don't understand the - # --color switch out of the box - if 'bsd' in sys.platform: - ls_extra = ( # ls normal files only - 'lf ls -lF | grep ^-', - # ls symbolic links - 'lk ls -lF | grep ^l', - # directories or links to directories, - 'ldir ls -lF | grep /$', - # things which are executable - 'lx ls -lF | grep ^-..x', - ) - auto_alias = auto_alias + ls_extra - elif os.name in ['nt','dos']: - auto_alias = ('ls dir /on', - 'ddir dir /ad /on', 'ldir dir /ad /on', - 'mkdir mkdir','rmdir rmdir','echo echo', - 'ren ren','cls cls','copy copy') - else: - auto_alias = () - self.auto_alias = [s.split(None,1) for s in auto_alias] - - # Load default aliases - for alias, cmd in self.auto_alias: - self.define_alias(alias,cmd) - - # Load user aliases - for alias in self.alias: - self.magic_alias(alias) - - def call_alias(self,alias,rest=''): - """Call an alias given its name and the rest of the line. - - This is only used to provide backwards compatibility for users of - ipalias(), use of which is not recommended for anymore.""" - - # Now call the macro, evaluating in the user's namespace - cmd = self.transform_alias(alias, rest) - try: - self.system(cmd) - except: - self.showtraceback() - - def define_alias(self, name, cmd): - """ Define a new alias.""" - - if callable(cmd): - self.alias_table[name] = cmd - from IPython.core import shadowns - setattr(shadowns, name, cmd) - return - - if isinstance(cmd, basestring): - nargs = cmd.count('%s') - if nargs>0 and cmd.find('%l')>=0: - raise Exception('The %s and %l specifiers are mutually ' - 'exclusive in alias definitions.') - - self.alias_table[name] = (nargs,cmd) - return - - self.alias_table[name] = cmd - - def ipalias(self,arg_s): - """Call an alias by name. - - Input: a string containing the name of the alias to call and any - additional arguments to be passed to the magic. - - ipalias('name -opt foo bar') is equivalent to typing at the ipython - prompt: - - In[1]: name -opt foo bar - - To call an alias without arguments, simply use ipalias('name'). - - This provides a proper Python function to call IPython's aliases in any - valid Python code you can type at the interpreter, including loops and - compound statements. It is added by IPython to the Python builtin - namespace upon initialization.""" - - args = arg_s.split(' ',1) - alias_name = args[0] - try: - alias_args = args[1] - except IndexError: - alias_args = '' - if alias_name in self.alias_table: - self.call_alias(alias_name,alias_args) - else: - error("Alias `%s` not found." % alias_name) + def init_alias(self): + self.alias_manager = AliasManager(self, config=self.config) def expand_alias(self, line): """ Expand an alias in the command line @@ -1817,81 +1700,25 @@ class InteractiveShell(Component, Magic): while 1: pre,fn,rest = prefilter.splitUserInput(line, prefilter.shell_line_split) - if fn in self.alias_table: + if fn in self.alias_manager.alias_table: if fn in done: warn("Cyclic alias definition, repeated '%s'" % fn) return "" done.add(fn) - l2 = self.transform_alias(fn,rest) - # dir -> dir - # print "alias",line, "->",l2 #dbg + l2 = self.alias_manager.transform_alias(fn, rest) if l2 == line: break # ls -> ls -F should not recurse forever if l2.split(None,1)[0] == line.split(None,1)[0]: line = l2 break - line=l2 - - - # print "al expand to",line #dbg else: break return line - def transform_alias(self, alias,rest=''): - """ Transform alias to system command string. - """ - trg = self.alias_table[alias] - - nargs,cmd = trg - # print trg #dbg - if ' ' in cmd and os.path.isfile(cmd): - cmd = '"%s"' % cmd - - # Expand the %l special to be the user's input line - if cmd.find('%l') >= 0: - cmd = cmd.replace('%l',rest) - rest = '' - if nargs==0: - # Simple, argument-less aliases - cmd = '%s %s' % (cmd,rest) - else: - # Handle aliases with positional arguments - args = rest.split(None,nargs) - if len(args)< nargs: - error('Alias <%s> requires %s arguments, %s given.' % - (alias,nargs,len(args))) - return None - cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:])) - # Now call the macro, evaluating in the user's namespace - #print 'new command: <%r>' % cmd # dbg - return cmd - - def init_auto_alias(self): - """Define some aliases automatically. - - These are ALL parameter-less aliases""" - - for alias,cmd in self.auto_alias: - self.define_alias(alias,cmd) - - def alias_table_validate(self,verbose=0): - """Update information about the alias table. - - In particular, make sure no Python keywords/builtins are in it.""" - - no_alias = self.no_alias - for k in self.alias_table.keys(): - if k in no_alias: - del self.alias_table[k] - if verbose: - print ("Deleting alias <%s>, it's a Python " - "keyword or builtin." % k) - #------------------------------------------------------------------------- # Things related to the running of code #------------------------------------------------------------------------- @@ -2513,9 +2340,10 @@ class InteractiveShell(Component, Magic): - continue_prompt(False): whether this line is the first one or a continuation in a sequence of inputs. """ - + growl.notify("raw_input: ", "prompt = %r\ncontinue_prompt = %s" % (prompt, continue_prompt)) # Code run by the user may have modified the readline completer state. # We must ensure that our completer is back in place. + if self.has_readline: self.set_completer() @@ -2659,6 +2487,8 @@ class InteractiveShell(Component, Magic): # save the line away in case we crash, so the post-mortem handler can # record it + growl.notify("_prefilter: ", "line = %s\ncontinue_prompt = %s" % (line, continue_prompt)) + self._last_input_line = line #print '***line: <%s>' % line # dbg @@ -2715,15 +2545,17 @@ class InteractiveShell(Component, Magic): entry and presses enter. """ + growl.notify("multiline_prefilter: ", "%s\n%s" % (line, continue_prompt)) out = [] for l in line.rstrip('\n').split('\n'): out.append(self._prefilter(l, continue_prompt)) + growl.notify("multiline_prefilter return: ", '\n'.join(out)) return '\n'.join(out) # Set the default prefilter() function (this can be user-overridden) prefilter = multiline_prefilter - def handle_normal(self,line_info): + def handle_normal(self, line_info): """Handle normal input lines. Use as a template for handlers.""" # With autoindent on, we need some way to exit the input loop, and I @@ -2742,10 +2574,9 @@ class InteractiveShell(Component, Magic): self.log(line,line,continue_prompt) return line - def handle_alias(self,line_info): + def handle_alias(self, line_info): """Handle alias input lines. """ - tgt = self.alias_table[line_info.iFun] - # print "=>",tgt #dbg + tgt = self.alias_manager.alias_table[line_info.iFun] if callable(tgt): if '$' in line_info.line: call_meth = '(_ip, _ip.var_expand(%s))' diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py index 0fb4524..4ae5818 100644 --- a/IPython/core/prefilter.py +++ b/IPython/core/prefilter.py @@ -251,8 +251,8 @@ def checkAlias(l_info,ip): # Note: aliases can not contain '.' head = l_info.iFun.split('.',1)[0] - if l_info.iFun not in ip.alias_table \ - or head not in ip.alias_table \ + if l_info.iFun not in ip.alias_manager \ + or head not in ip.alias_manager \ or isShadowed(head,ip): return None diff --git a/IPython/utils/growl.py b/IPython/utils/growl.py index cc4613c..fa11d96 100644 --- a/IPython/utils/growl.py +++ b/IPython/utils/growl.py @@ -18,7 +18,7 @@ class Notifier(object): def _notify(self, title, msg): if self.g_notifier is not None: - self.g_notifier.notify('kernel', title, msg) + self.g_notifier.notify('core', title, msg) def notify(self, title, msg): self._notify(title, msg)