##// END OF EJS Templates
More work on componentizing everything....
Brian Granger -
Show More
@@ -0,0 +1,191 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 IPython's alias component
5
6 Authors:
7
8 * Brian Granger
9 """
10
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2009 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
17
18 #-----------------------------------------------------------------------------
19 # Imports
20 #-----------------------------------------------------------------------------
21
22 import __builtin__
23 import keyword
24 import os
25 import sys
26
27 from IPython.core.component import Component
28
29 from IPython.utils.traitlets import CBool, List, Instance
30 from IPython.utils.genutils import error
31
32 #-----------------------------------------------------------------------------
33 # Functions and classes
34 #-----------------------------------------------------------------------------
35
36 def default_aliases():
37 # Make some aliases automatically
38 # Prepare list of shell aliases to auto-define
39 if os.name == 'posix':
40 auto_alias = ('mkdir mkdir', 'rmdir rmdir',
41 'mv mv -i','rm rm -i','cp cp -i',
42 'cat cat','less less','clear clear',
43 # a better ls
44 'ls ls -F',
45 # long ls
46 'll ls -lF')
47 # Extra ls aliases with color, which need special treatment on BSD
48 # variants
49 ls_extra = ( # color ls
50 'lc ls -F -o --color',
51 # ls normal files only
52 'lf ls -F -o --color %l | grep ^-',
53 # ls symbolic links
54 'lk ls -F -o --color %l | grep ^l',
55 # directories or links to directories,
56 'ldir ls -F -o --color %l | grep /$',
57 # things which are executable
58 'lx ls -F -o --color %l | grep ^-..x',
59 )
60 # The BSDs don't ship GNU ls, so they don't understand the
61 # --color switch out of the box
62 if 'bsd' in sys.platform:
63 ls_extra = ( # ls normal files only
64 'lf ls -lF | grep ^-',
65 # ls symbolic links
66 'lk ls -lF | grep ^l',
67 # directories or links to directories,
68 'ldir ls -lF | grep /$',
69 # things which are executable
70 'lx ls -lF | grep ^-..x',
71 )
72 auto_alias = auto_alias + ls_extra
73 elif os.name in ['nt','dos']:
74 auto_alias = ('ls dir /on',
75 'ddir dir /ad /on', 'ldir dir /ad /on',
76 'mkdir mkdir','rmdir rmdir','echo echo',
77 'ren ren','cls cls','copy copy')
78 else:
79 auto_alias = ()
80 return [s.split(None,1) for s in auto_alias]
81
82
83 class AliasError(Exception):
84 pass
85
86
87 class InvalidAliasError(AliasError):
88 pass
89
90
91 class AliasManager(Component):
92
93 auto_alias = List(default_aliases())
94 user_alias = List(default_value=[], config_key='USER_ALIAS')
95
96 def __init__(self, parent, config=None):
97 super(AliasManager, self).__init__(parent, config=config)
98 self.shell = Component.get_instances(
99 root=self.root,
100 klass='IPython.core.iplib.InteractiveShell'
101 )[0]
102 self.alias_table = {}
103 self.exclude_aliases()
104 self.init_aliases()
105
106 def __contains__(self, name):
107 if name in self.alias_table:
108 return True
109 else:
110 return False
111
112 @property
113 def aliases(self):
114 return [(item[0], item[1][1]) for item in self.alias_table.iteritems()]
115
116 def exclude_aliases(self):
117 # set of things NOT to alias (keywords, builtins and some magics)
118 no_alias = set(['cd','popd','pushd','dhist','alias','unalias'])
119 no_alias.update(set(keyword.kwlist))
120 no_alias.update(set(__builtin__.__dict__.keys()))
121 self.no_alias = no_alias
122
123 def init_aliases(self):
124 # Load default aliases
125 for name, cmd in self.auto_alias:
126 self.soft_define_alias(name, cmd)
127
128 # Load user aliases
129 for name, cmd in self.user_alias:
130 self.soft_define_alias(name, cmd)
131
132 def soft_define_alias(self, name, cmd):
133 """Define an alias, but don't raise on an AliasError."""
134 try:
135 self.define_alias(name, cmd)
136 except AliasError, e:
137 error("Invalid alias: %s" % e)
138
139 def define_alias(self, name, cmd):
140 """Define a new alias after validating it.
141
142 This will raise an :exc:`AliasError` if there are validation
143 problems.
144 """
145 nargs = self.validate_alias(name, cmd)
146 self.alias_table[name] = (nargs, cmd)
147
148 def validate_alias(self, name, cmd):
149 """Validate an alias and return the its number of arguments."""
150 if name in self.no_alias:
151 raise InvalidAliasError("The name %s can't be aliased "
152 "because it is a keyword or builtin." % name)
153 if not (isinstance(cmd, basestring)):
154 raise InvalidAliasError("An alias command must be a string, "
155 "got: %r" % name)
156 nargs = cmd.count('%s')
157 if nargs>0 and cmd.find('%l')>=0:
158 raise InvalidAliasError('The %s and %l specifiers are mutually '
159 'exclusive in alias definitions.')
160 return nargs
161
162 def call_alias(self, alias, rest=''):
163 """Call an alias given its name and the rest of the line."""
164 cmd = self.transform_alias(alias, rest)
165 try:
166 self.shell.system(cmd)
167 except:
168 self.shell.showtraceback()
169
170 def transform_alias(self, alias,rest=''):
171 """Transform alias to system command string."""
172 nargs, cmd = self.alias_table[alias]
173
174 if ' ' in cmd and os.path.isfile(cmd):
175 cmd = '"%s"' % cmd
176
177 # Expand the %l special to be the user's input line
178 if cmd.find('%l') >= 0:
179 cmd = cmd.replace('%l', rest)
180 rest = ''
181 if nargs==0:
182 # Simple, argument-less aliases
183 cmd = '%s %s' % (cmd, rest)
184 else:
185 # Handle aliases with positional arguments
186 args = rest.split(None, nargs)
187 if len(args) < nargs:
188 raise AliasError('Alias <%s> requires %s arguments, %s given.' %
189 (alias, nargs, len(args)))
190 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
191 return cmd
@@ -228,6 +228,7 b' class Application(object):'
228 228 self.print_traceback()
229 229 self.abort()
230 230 elif action == 'exit':
231 self.print_traceback()
231 232 self.exit()
232 233
233 234 def print_traceback(self):
@@ -36,13 +36,14 b' BuiltinUndefined = BuiltinUndefined()'
36 36
37 37
38 38 class BuiltinTrap(Component):
39 shell = Instance('IPython.core.iplib.InteractiveShell')
40 39
41 40 def __init__(self, parent):
42 41 super(BuiltinTrap, self).__init__(parent, None, None)
43 42 # Don't just grab parent!!!
44 self.shell = Component.get_instances(root=self.root,
45 klass='IPython.core.iplib.InteractiveShell')[0]
43 self.shell = Component.get_instances(
44 root=self.root,
45 klass='IPython.core.iplib.InteractiveShell'
46 )[0]
46 47 self._orig_builtins = {}
47 48
48 49 def __enter__(self):
@@ -48,22 +48,55 b' class MetaComponentTracker(type):'
48 48 cls.__numcreated = 0
49 49
50 50 def __call__(cls, *args, **kw):
51 """Called when *class* is called (instantiated)!!!
51 """Called when a class is called (instantiated)!!!
52 52
53 53 When a Component or subclass is instantiated, this is called and
54 54 the instance is saved in a WeakValueDictionary for tracking.
55 55 """
56 56 instance = cls.__new__(cls, *args, **kw)
57 # Do this before __init__ is called so get_instances works inside
58 # __init__ methods!
57
58 # Register the instance before __init__ is called so get_instances
59 # works inside __init__ methods!
60 indices = cls.register_instance(instance)
61
62 # This is in a try/except because of the __init__ method fails, the
63 # instance is discarded and shouldn't be tracked.
64 try:
65 if isinstance(instance, cls):
66 cls.__init__(instance, *args, **kw)
67 except:
68 # Unregister the instance because __init__ failed!
69 cls.unregister_instances(indices)
70 raise
71 else:
72 return instance
73
74 def register_instance(cls, instance):
75 """Register instance with cls and its subclasses."""
76 # indices is a list of the keys used to register the instance
77 # with. This list is needed if the instance needs to be unregistered.
78 indices = []
59 79 for c in cls.__mro__:
60 80 if issubclass(cls, c) and issubclass(c, Component):
61 81 c.__numcreated += 1
82 indices.append(c.__numcreated)
62 83 c.__instance_refs[c.__numcreated] = instance
63 if isinstance(instance, cls):
64 cls.__init__(instance, *args, **kw)
84 else:
85 break
86 return indices
65 87
66 return instance
88 def unregister_instances(cls, indices):
89 """Unregister instance with cls and its subclasses."""
90 for c, index in zip(cls.__mro__, indices):
91 try:
92 del c.__instance_refs[index]
93 except KeyError:
94 pass
95
96 def clear_instances(cls):
97 """Clear all instances tracked by cls."""
98 cls.__instance_refs.clear()
99 cls.__numcreated = 0
67 100
68 101 def get_instances(cls, name=None, root=None, klass=None):
69 102 """Get all instances of cls and its subclasses.
@@ -82,6 +115,9 b' class MetaComponentTracker(type):'
82 115 if klass is not None:
83 116 if isinstance(klass, basestring):
84 117 klass = import_item(klass)
118 # Limit search to instances of klass for performance
119 if issubclass(klass, Component):
120 return klass.get_instances(name=name, root=root)
85 121 instances = cls.__instance_refs.values()
86 122 if name is not None:
87 123 instances = [i for i in instances if i.name == name]
@@ -101,6 +137,26 b' class MetaComponentTracker(type):'
101 137 return [i for i in cls.get_instances(name, root, klass) if call(i)]
102 138
103 139
140 def masquerade_as(instance, cls):
141 """Let instance masquerade as an instance of cls.
142
143 Sometimes, such as in testing code, it is useful to let a class
144 masquerade as another. Python, being duck typed, allows this by
145 default. But, instances of components are tracked by their class type.
146
147 After calling this, cls.get_instances() will return ``instance``. This
148 does not, however, cause isinstance(instance, cls) to return ``True``.
149
150 Parameters
151 ----------
152 instance : an instance of a Component or Component subclass
153 The instance that will pretend to be a cls.
154 cls : subclass of Component
155 The Component subclass that instance will pretend to be.
156 """
157 cls.register_instance(instance)
158
159
104 160 class ComponentNameGenerator(object):
105 161 """A Singleton to generate unique component names."""
106 162
@@ -245,4 +301,5 b' class Component(HasTraitlets):'
245 301 self._children.append(child)
246 302
247 303 def __repr__(self):
248 return "<Component('%s')>" % self.name
304 return "<%s('%s')>" % (self.__class__.__name__, "DummyName")
305 # return "<Component('%s')>" % self.name
@@ -307,6 +307,7 b' class IPythonApp(Application):'
307 307 parent=None,
308 308 config=self.master_config
309 309 )
310 print self.shell
310 311
311 312 def start_app(self):
312 313 self.shell.mainloop()
@@ -40,6 +40,7 b' from IPython.core import debugger, oinspect'
40 40 from IPython.core import shadowns
41 41 from IPython.core import history as ipcorehist
42 42 from IPython.core import prefilter
43 from IPython.core.alias import AliasManager
43 44 from IPython.core.autocall import IPyAutocall
44 45 from IPython.core.builtin_trap import BuiltinTrap
45 46 from IPython.core.display_trap import DisplayTrap
@@ -62,6 +63,9 b' from IPython.utils.genutils import *'
62 63 from IPython.utils.strdispatch import StrDispatch
63 64 from IPython.utils.platutils import toggle_set_term_title, set_term_title
64 65
66 from IPython.utils import growl
67 growl.start("IPython")
68
65 69 from IPython.utils.traitlets import (
66 70 Int, Float, Str, CBool, CaselessStrEnum, Enum, List, Unicode
67 71 )
@@ -303,7 +307,7 b' class InteractiveShell(Component, Magic):'
303 307 self.init_traceback_handlers(custom_exceptions)
304 308 self.init_user_ns()
305 309 self.init_logger()
306 self.init_aliases()
310 self.init_alias()
307 311 self.init_builtins()
308 312
309 313 # pre_config_initialization
@@ -1660,129 +1664,8 b' class InteractiveShell(Component, Magic):'
1660 1664 # Things related to aliases
1661 1665 #-------------------------------------------------------------------------
1662 1666
1663 def init_aliases(self):
1664 # dict of things NOT to alias (keywords, builtins and some magics)
1665 no_alias = {}
1666 no_alias_magics = ['cd','popd','pushd','dhist','alias','unalias']
1667 for key in keyword.kwlist + no_alias_magics:
1668 no_alias[key] = 1
1669 no_alias.update(__builtin__.__dict__)
1670 self.no_alias = no_alias
1671
1672 # Make some aliases automatically
1673 # Prepare list of shell aliases to auto-define
1674 if os.name == 'posix':
1675 auto_alias = ('mkdir mkdir', 'rmdir rmdir',
1676 'mv mv -i','rm rm -i','cp cp -i',
1677 'cat cat','less less','clear clear',
1678 # a better ls
1679 'ls ls -F',
1680 # long ls
1681 'll ls -lF')
1682 # Extra ls aliases with color, which need special treatment on BSD
1683 # variants
1684 ls_extra = ( # color ls
1685 'lc ls -F -o --color',
1686 # ls normal files only
1687 'lf ls -F -o --color %l | grep ^-',
1688 # ls symbolic links
1689 'lk ls -F -o --color %l | grep ^l',
1690 # directories or links to directories,
1691 'ldir ls -F -o --color %l | grep /$',
1692 # things which are executable
1693 'lx ls -F -o --color %l | grep ^-..x',
1694 )
1695 # The BSDs don't ship GNU ls, so they don't understand the
1696 # --color switch out of the box
1697 if 'bsd' in sys.platform:
1698 ls_extra = ( # ls normal files only
1699 'lf ls -lF | grep ^-',
1700 # ls symbolic links
1701 'lk ls -lF | grep ^l',
1702 # directories or links to directories,
1703 'ldir ls -lF | grep /$',
1704 # things which are executable
1705 'lx ls -lF | grep ^-..x',
1706 )
1707 auto_alias = auto_alias + ls_extra
1708 elif os.name in ['nt','dos']:
1709 auto_alias = ('ls dir /on',
1710 'ddir dir /ad /on', 'ldir dir /ad /on',
1711 'mkdir mkdir','rmdir rmdir','echo echo',
1712 'ren ren','cls cls','copy copy')
1713 else:
1714 auto_alias = ()
1715 self.auto_alias = [s.split(None,1) for s in auto_alias]
1716
1717 # Load default aliases
1718 for alias, cmd in self.auto_alias:
1719 self.define_alias(alias,cmd)
1720
1721 # Load user aliases
1722 for alias in self.alias:
1723 self.magic_alias(alias)
1724
1725 def call_alias(self,alias,rest=''):
1726 """Call an alias given its name and the rest of the line.
1727
1728 This is only used to provide backwards compatibility for users of
1729 ipalias(), use of which is not recommended for anymore."""
1730
1731 # Now call the macro, evaluating in the user's namespace
1732 cmd = self.transform_alias(alias, rest)
1733 try:
1734 self.system(cmd)
1735 except:
1736 self.showtraceback()
1737
1738 def define_alias(self, name, cmd):
1739 """ Define a new alias."""
1740
1741 if callable(cmd):
1742 self.alias_table[name] = cmd
1743 from IPython.core import shadowns
1744 setattr(shadowns, name, cmd)
1745 return
1746
1747 if isinstance(cmd, basestring):
1748 nargs = cmd.count('%s')
1749 if nargs>0 and cmd.find('%l')>=0:
1750 raise Exception('The %s and %l specifiers are mutually '
1751 'exclusive in alias definitions.')
1752
1753 self.alias_table[name] = (nargs,cmd)
1754 return
1755
1756 self.alias_table[name] = cmd
1757
1758 def ipalias(self,arg_s):
1759 """Call an alias by name.
1760
1761 Input: a string containing the name of the alias to call and any
1762 additional arguments to be passed to the magic.
1763
1764 ipalias('name -opt foo bar') is equivalent to typing at the ipython
1765 prompt:
1766
1767 In[1]: name -opt foo bar
1768
1769 To call an alias without arguments, simply use ipalias('name').
1770
1771 This provides a proper Python function to call IPython's aliases in any
1772 valid Python code you can type at the interpreter, including loops and
1773 compound statements. It is added by IPython to the Python builtin
1774 namespace upon initialization."""
1775
1776 args = arg_s.split(' ',1)
1777 alias_name = args[0]
1778 try:
1779 alias_args = args[1]
1780 except IndexError:
1781 alias_args = ''
1782 if alias_name in self.alias_table:
1783 self.call_alias(alias_name,alias_args)
1784 else:
1785 error("Alias `%s` not found." % alias_name)
1667 def init_alias(self):
1668 self.alias_manager = AliasManager(self, config=self.config)
1786 1669
1787 1670 def expand_alias(self, line):
1788 1671 """ Expand an alias in the command line
@@ -1817,81 +1700,25 b' class InteractiveShell(Component, Magic):'
1817 1700 while 1:
1818 1701 pre,fn,rest = prefilter.splitUserInput(line,
1819 1702 prefilter.shell_line_split)
1820 if fn in self.alias_table:
1703 if fn in self.alias_manager.alias_table:
1821 1704 if fn in done:
1822 1705 warn("Cyclic alias definition, repeated '%s'" % fn)
1823 1706 return ""
1824 1707 done.add(fn)
1825 1708
1826 l2 = self.transform_alias(fn,rest)
1827 # dir -> dir
1828 # print "alias",line, "->",l2 #dbg
1709 l2 = self.alias_manager.transform_alias(fn, rest)
1829 1710 if l2 == line:
1830 1711 break
1831 1712 # ls -> ls -F should not recurse forever
1832 1713 if l2.split(None,1)[0] == line.split(None,1)[0]:
1833 1714 line = l2
1834 1715 break
1835
1836 1716 line=l2
1837
1838
1839 # print "al expand to",line #dbg
1840 1717 else:
1841 1718 break
1842 1719
1843 1720 return line
1844 1721
1845 def transform_alias(self, alias,rest=''):
1846 """ Transform alias to system command string.
1847 """
1848 trg = self.alias_table[alias]
1849
1850 nargs,cmd = trg
1851 # print trg #dbg
1852 if ' ' in cmd and os.path.isfile(cmd):
1853 cmd = '"%s"' % cmd
1854
1855 # Expand the %l special to be the user's input line
1856 if cmd.find('%l') >= 0:
1857 cmd = cmd.replace('%l',rest)
1858 rest = ''
1859 if nargs==0:
1860 # Simple, argument-less aliases
1861 cmd = '%s %s' % (cmd,rest)
1862 else:
1863 # Handle aliases with positional arguments
1864 args = rest.split(None,nargs)
1865 if len(args)< nargs:
1866 error('Alias <%s> requires %s arguments, %s given.' %
1867 (alias,nargs,len(args)))
1868 return None
1869 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
1870 # Now call the macro, evaluating in the user's namespace
1871 #print 'new command: <%r>' % cmd # dbg
1872 return cmd
1873
1874 def init_auto_alias(self):
1875 """Define some aliases automatically.
1876
1877 These are ALL parameter-less aliases"""
1878
1879 for alias,cmd in self.auto_alias:
1880 self.define_alias(alias,cmd)
1881
1882 def alias_table_validate(self,verbose=0):
1883 """Update information about the alias table.
1884
1885 In particular, make sure no Python keywords/builtins are in it."""
1886
1887 no_alias = self.no_alias
1888 for k in self.alias_table.keys():
1889 if k in no_alias:
1890 del self.alias_table[k]
1891 if verbose:
1892 print ("Deleting alias <%s>, it's a Python "
1893 "keyword or builtin." % k)
1894
1895 1722 #-------------------------------------------------------------------------
1896 1723 # Things related to the running of code
1897 1724 #-------------------------------------------------------------------------
@@ -2513,9 +2340,10 b' class InteractiveShell(Component, Magic):'
2513 2340 - continue_prompt(False): whether this line is the first one or a
2514 2341 continuation in a sequence of inputs.
2515 2342 """
2516
2343 growl.notify("raw_input: ", "prompt = %r\ncontinue_prompt = %s" % (prompt, continue_prompt))
2517 2344 # Code run by the user may have modified the readline completer state.
2518 2345 # We must ensure that our completer is back in place.
2346
2519 2347 if self.has_readline:
2520 2348 self.set_completer()
2521 2349
@@ -2659,6 +2487,8 b' class InteractiveShell(Component, Magic):'
2659 2487
2660 2488 # save the line away in case we crash, so the post-mortem handler can
2661 2489 # record it
2490 growl.notify("_prefilter: ", "line = %s\ncontinue_prompt = %s" % (line, continue_prompt))
2491
2662 2492 self._last_input_line = line
2663 2493
2664 2494 #print '***line: <%s>' % line # dbg
@@ -2715,9 +2545,11 b' class InteractiveShell(Component, Magic):'
2715 2545 entry and presses enter.
2716 2546
2717 2547 """
2548 growl.notify("multiline_prefilter: ", "%s\n%s" % (line, continue_prompt))
2718 2549 out = []
2719 2550 for l in line.rstrip('\n').split('\n'):
2720 2551 out.append(self._prefilter(l, continue_prompt))
2552 growl.notify("multiline_prefilter return: ", '\n'.join(out))
2721 2553 return '\n'.join(out)
2722 2554
2723 2555 # Set the default prefilter() function (this can be user-overridden)
@@ -2744,8 +2576,7 b' class InteractiveShell(Component, Magic):'
2744 2576
2745 2577 def handle_alias(self,line_info):
2746 2578 """Handle alias input lines. """
2747 tgt = self.alias_table[line_info.iFun]
2748 # print "=>",tgt #dbg
2579 tgt = self.alias_manager.alias_table[line_info.iFun]
2749 2580 if callable(tgt):
2750 2581 if '$' in line_info.line:
2751 2582 call_meth = '(_ip, _ip.var_expand(%s))'
@@ -251,8 +251,8 b' def checkAlias(l_info,ip):'
251 251 # Note: aliases can not contain '.'
252 252 head = l_info.iFun.split('.',1)[0]
253 253
254 if l_info.iFun not in ip.alias_table \
255 or head not in ip.alias_table \
254 if l_info.iFun not in ip.alias_manager \
255 or head not in ip.alias_manager \
256 256 or isShadowed(head,ip):
257 257 return None
258 258
@@ -18,7 +18,7 b' class Notifier(object):'
18 18
19 19 def _notify(self, title, msg):
20 20 if self.g_notifier is not None:
21 self.g_notifier.notify('kernel', title, msg)
21 self.g_notifier.notify('core', title, msg)
22 22
23 23 def notify(self, title, msg):
24 24 self._notify(title, msg)
General Comments 0
You need to be logged in to leave comments. Login now