##// END OF EJS Templates
Merge pull request #8151 from brotherjack/master...
Thomas Kluyver -
r20905:e99285da merge
parent child Browse files
Show More
@@ -1,253 +1,256
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 System command aliases.
3 System command aliases.
4
4
5 Authors:
5 Authors:
6
6
7 * Fernando Perez
7 * Fernando Perez
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License.
14 # Distributed under the terms of the BSD License.
15 #
15 #
16 # The full license is in the file COPYING.txt, distributed with this software.
16 # The full license is in the file COPYING.txt, distributed with this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26
26
27 from IPython.config.configurable import Configurable
27 from IPython.config.configurable import Configurable
28 from IPython.core.error import UsageError
28 from IPython.core.error import UsageError
29
29
30 from IPython.utils.py3compat import string_types
30 from IPython.utils.py3compat import string_types
31 from IPython.utils.traitlets import List, Instance
31 from IPython.utils.traitlets import List, Instance
32 from IPython.utils.warn import error
32 from IPython.utils.warn import error
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Utilities
35 # Utilities
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 # This is used as the pattern for calls to split_user_input.
38 # This is used as the pattern for calls to split_user_input.
39 shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
39 shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
40
40
41 def default_aliases():
41 def default_aliases():
42 """Return list of shell aliases to auto-define.
42 """Return list of shell aliases to auto-define.
43 """
43 """
44 # Note: the aliases defined here should be safe to use on a kernel
44 # Note: the aliases defined here should be safe to use on a kernel
45 # regardless of what frontend it is attached to. Frontends that use a
45 # regardless of what frontend it is attached to. Frontends that use a
46 # kernel in-process can define additional aliases that will only work in
46 # kernel in-process can define additional aliases that will only work in
47 # their case. For example, things like 'less' or 'clear' that manipulate
47 # their case. For example, things like 'less' or 'clear' that manipulate
48 # the terminal should NOT be declared here, as they will only work if the
48 # the terminal should NOT be declared here, as they will only work if the
49 # kernel is running inside a true terminal, and not over the network.
49 # kernel is running inside a true terminal, and not over the network.
50
50
51 if os.name == 'posix':
51 if os.name == 'posix':
52 default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
52 default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
53 ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
53 ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
54 ('cat', 'cat'),
54 ('cat', 'cat'),
55 ]
55 ]
56 # Useful set of ls aliases. The GNU and BSD options are a little
56 # Useful set of ls aliases. The GNU and BSD options are a little
57 # different, so we make aliases that provide as similar as possible
57 # different, so we make aliases that provide as similar as possible
58 # behavior in ipython, by passing the right flags for each platform
58 # behavior in ipython, by passing the right flags for each platform
59 if sys.platform.startswith('linux'):
59 if sys.platform.startswith('linux'):
60 ls_aliases = [('ls', 'ls -F --color'),
60 ls_aliases = [('ls', 'ls -F --color'),
61 # long ls
61 # long ls
62 ('ll', 'ls -F -o --color'),
62 ('ll', 'ls -F -o --color'),
63 # ls normal files only
63 # ls normal files only
64 ('lf', 'ls -F -o --color %l | grep ^-'),
64 ('lf', 'ls -F -o --color %l | grep ^-'),
65 # ls symbolic links
65 # ls symbolic links
66 ('lk', 'ls -F -o --color %l | grep ^l'),
66 ('lk', 'ls -F -o --color %l | grep ^l'),
67 # directories or links to directories,
67 # directories or links to directories,
68 ('ldir', 'ls -F -o --color %l | grep /$'),
68 ('ldir', 'ls -F -o --color %l | grep /$'),
69 # things which are executable
69 # things which are executable
70 ('lx', 'ls -F -o --color %l | grep ^-..x'),
70 ('lx', 'ls -F -o --color %l | grep ^-..x'),
71 ]
71 ]
72 elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
72 elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
73 # OpenBSD, NetBSD. The ls implementation on these platforms do not support
73 # OpenBSD, NetBSD. The ls implementation on these platforms do not support
74 # the -G switch and lack the ability to use colorized output.
74 # the -G switch and lack the ability to use colorized output.
75 ls_aliases = [('ls', 'ls -F'),
75 ls_aliases = [('ls', 'ls -F'),
76 # long ls
76 # long ls
77 ('ll', 'ls -F -l'),
77 ('ll', 'ls -F -l'),
78 # ls normal files only
78 # ls normal files only
79 ('lf', 'ls -F -l %l | grep ^-'),
79 ('lf', 'ls -F -l %l | grep ^-'),
80 # ls symbolic links
80 # ls symbolic links
81 ('lk', 'ls -F -l %l | grep ^l'),
81 ('lk', 'ls -F -l %l | grep ^l'),
82 # directories or links to directories,
82 # directories or links to directories,
83 ('ldir', 'ls -F -l %l | grep /$'),
83 ('ldir', 'ls -F -l %l | grep /$'),
84 # things which are executable
84 # things which are executable
85 ('lx', 'ls -F -l %l | grep ^-..x'),
85 ('lx', 'ls -F -l %l | grep ^-..x'),
86 ]
86 ]
87 else:
87 else:
88 # BSD, OSX, etc.
88 # BSD, OSX, etc.
89 ls_aliases = [('ls', 'ls -F -G'),
89 ls_aliases = [('ls', 'ls -F -G'),
90 # long ls
90 # long ls
91 ('ll', 'ls -F -l -G'),
91 ('ll', 'ls -F -l -G'),
92 # ls normal files only
92 # ls normal files only
93 ('lf', 'ls -F -l -G %l | grep ^-'),
93 ('lf', 'ls -F -l -G %l | grep ^-'),
94 # ls symbolic links
94 # ls symbolic links
95 ('lk', 'ls -F -l -G %l | grep ^l'),
95 ('lk', 'ls -F -l -G %l | grep ^l'),
96 # directories or links to directories,
96 # directories or links to directories,
97 ('ldir', 'ls -F -G -l %l | grep /$'),
97 ('ldir', 'ls -F -G -l %l | grep /$'),
98 # things which are executable
98 # things which are executable
99 ('lx', 'ls -F -l -G %l | grep ^-..x'),
99 ('lx', 'ls -F -l -G %l | grep ^-..x'),
100 ]
100 ]
101 default_aliases = default_aliases + ls_aliases
101 default_aliases = default_aliases + ls_aliases
102 elif os.name in ['nt', 'dos']:
102 elif os.name in ['nt', 'dos']:
103 default_aliases = [('ls', 'dir /on'),
103 default_aliases = [('ls', 'dir /on'),
104 ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
104 ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
105 ('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
105 ('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
106 ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
106 ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
107 ]
107 ]
108 else:
108 else:
109 default_aliases = []
109 default_aliases = []
110
110
111 return default_aliases
111 return default_aliases
112
112
113
113
114 class AliasError(Exception):
114 class AliasError(Exception):
115 pass
115 pass
116
116
117
117
118 class InvalidAliasError(AliasError):
118 class InvalidAliasError(AliasError):
119 pass
119 pass
120
120
121 class Alias(object):
121 class Alias(object):
122 """Callable object storing the details of one alias.
122 """Callable object storing the details of one alias.
123
123
124 Instances are registered as magic functions to allow use of aliases.
124 Instances are registered as magic functions to allow use of aliases.
125 """
125 """
126
126
127 # Prepare blacklist
127 # Prepare blacklist
128 blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
128 blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
129
129
130 def __init__(self, shell, name, cmd):
130 def __init__(self, shell, name, cmd):
131 self.shell = shell
131 self.shell = shell
132 self.name = name
132 self.name = name
133 self.cmd = cmd
133 self.cmd = cmd
134 self.nargs = self.validate()
134 self.nargs = self.validate()
135
135
136 def validate(self):
136 def validate(self):
137 """Validate the alias, and return the number of arguments."""
137 """Validate the alias, and return the number of arguments."""
138 if self.name in self.blacklist:
138 if self.name in self.blacklist:
139 raise InvalidAliasError("The name %s can't be aliased "
139 raise InvalidAliasError("The name %s can't be aliased "
140 "because it is a keyword or builtin." % self.name)
140 "because it is a keyword or builtin." % self.name)
141 try:
141 try:
142 caller = self.shell.magics_manager.magics['line'][self.name]
142 caller = self.shell.magics_manager.magics['line'][self.name]
143 except KeyError:
143 except KeyError:
144 pass
144 pass
145 else:
145 else:
146 if not isinstance(caller, Alias):
146 if not isinstance(caller, Alias):
147 raise InvalidAliasError("The name %s can't be aliased "
147 raise InvalidAliasError("The name %s can't be aliased "
148 "because it is another magic command." % self.name)
148 "because it is another magic command." % self.name)
149
149
150 if not (isinstance(self.cmd, string_types)):
150 if not (isinstance(self.cmd, string_types)):
151 raise InvalidAliasError("An alias command must be a string, "
151 raise InvalidAliasError("An alias command must be a string, "
152 "got: %r" % self.cmd)
152 "got: %r" % self.cmd)
153
153
154 nargs = self.cmd.count('%s')
154 nargs = self.cmd.count('%s') - self.cmd.count('%%s')
155
155
156 if (nargs > 0) and (self.cmd.find('%l') >= 0):
156 if (nargs > 0) and (self.cmd.find('%l') >= 0):
157 raise InvalidAliasError('The %s and %l specifiers are mutually '
157 raise InvalidAliasError('The %s and %l specifiers are mutually '
158 'exclusive in alias definitions.')
158 'exclusive in alias definitions.')
159
159
160 return nargs
160 return nargs
161
161
162 def __repr__(self):
162 def __repr__(self):
163 return "<alias {} for {!r}>".format(self.name, self.cmd)
163 return "<alias {} for {!r}>".format(self.name, self.cmd)
164
164
165 def __call__(self, rest=''):
165 def __call__(self, rest=''):
166 cmd = self.cmd
166 cmd = self.cmd
167 nargs = self.nargs
167 nargs = self.nargs
168 # Expand the %l special to be the user's input line
168 # Expand the %l special to be the user's input line
169 if cmd.find('%l') >= 0:
169 if cmd.find('%l') >= 0:
170 cmd = cmd.replace('%l', rest)
170 cmd = cmd.replace('%l', rest)
171 rest = ''
171 rest = ''
172
172 if nargs==0:
173 if nargs==0:
174 if cmd.find('%%s') >= 1:
175 cmd = cmd.replace('%%s', '%s')
173 # Simple, argument-less aliases
176 # Simple, argument-less aliases
174 cmd = '%s %s' % (cmd, rest)
177 cmd = '%s %s' % (cmd, rest)
175 else:
178 else:
176 # Handle aliases with positional arguments
179 # Handle aliases with positional arguments
177 args = rest.split(None, nargs)
180 args = rest.split(None, nargs)
178 if len(args) < nargs:
181 if len(args) < nargs:
179 raise UsageError('Alias <%s> requires %s arguments, %s given.' %
182 raise UsageError('Alias <%s> requires %s arguments, %s given.' %
180 (self.name, nargs, len(args)))
183 (self.name, nargs, len(args)))
181 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
184 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
182
185
183 self.shell.system(cmd)
186 self.shell.system(cmd)
184
187
185 #-----------------------------------------------------------------------------
188 #-----------------------------------------------------------------------------
186 # Main AliasManager class
189 # Main AliasManager class
187 #-----------------------------------------------------------------------------
190 #-----------------------------------------------------------------------------
188
191
189 class AliasManager(Configurable):
192 class AliasManager(Configurable):
190
193
191 default_aliases = List(default_aliases(), config=True)
194 default_aliases = List(default_aliases(), config=True)
192 user_aliases = List(default_value=[], config=True)
195 user_aliases = List(default_value=[], config=True)
193 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
196 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
194
197
195 def __init__(self, shell=None, **kwargs):
198 def __init__(self, shell=None, **kwargs):
196 super(AliasManager, self).__init__(shell=shell, **kwargs)
199 super(AliasManager, self).__init__(shell=shell, **kwargs)
197 # For convenient access
200 # For convenient access
198 self.linemagics = self.shell.magics_manager.magics['line']
201 self.linemagics = self.shell.magics_manager.magics['line']
199 self.init_aliases()
202 self.init_aliases()
200
203
201 def init_aliases(self):
204 def init_aliases(self):
202 # Load default & user aliases
205 # Load default & user aliases
203 for name, cmd in self.default_aliases + self.user_aliases:
206 for name, cmd in self.default_aliases + self.user_aliases:
204 self.soft_define_alias(name, cmd)
207 self.soft_define_alias(name, cmd)
205
208
206 @property
209 @property
207 def aliases(self):
210 def aliases(self):
208 return [(n, func.cmd) for (n, func) in self.linemagics.items()
211 return [(n, func.cmd) for (n, func) in self.linemagics.items()
209 if isinstance(func, Alias)]
212 if isinstance(func, Alias)]
210
213
211 def soft_define_alias(self, name, cmd):
214 def soft_define_alias(self, name, cmd):
212 """Define an alias, but don't raise on an AliasError."""
215 """Define an alias, but don't raise on an AliasError."""
213 try:
216 try:
214 self.define_alias(name, cmd)
217 self.define_alias(name, cmd)
215 except AliasError as e:
218 except AliasError as e:
216 error("Invalid alias: %s" % e)
219 error("Invalid alias: %s" % e)
217
220
218 def define_alias(self, name, cmd):
221 def define_alias(self, name, cmd):
219 """Define a new alias after validating it.
222 """Define a new alias after validating it.
220
223
221 This will raise an :exc:`AliasError` if there are validation
224 This will raise an :exc:`AliasError` if there are validation
222 problems.
225 problems.
223 """
226 """
224 caller = Alias(shell=self.shell, name=name, cmd=cmd)
227 caller = Alias(shell=self.shell, name=name, cmd=cmd)
225 self.shell.magics_manager.register_function(caller, magic_kind='line',
228 self.shell.magics_manager.register_function(caller, magic_kind='line',
226 magic_name=name)
229 magic_name=name)
227
230
228 def get_alias(self, name):
231 def get_alias(self, name):
229 """Return an alias, or None if no alias by that name exists."""
232 """Return an alias, or None if no alias by that name exists."""
230 aname = self.linemagics.get(name, None)
233 aname = self.linemagics.get(name, None)
231 return aname if isinstance(aname, Alias) else None
234 return aname if isinstance(aname, Alias) else None
232
235
233 def is_alias(self, name):
236 def is_alias(self, name):
234 """Return whether or not a given name has been defined as an alias"""
237 """Return whether or not a given name has been defined as an alias"""
235 return self.get_alias(name) is not None
238 return self.get_alias(name) is not None
236
239
237 def undefine_alias(self, name):
240 def undefine_alias(self, name):
238 if self.is_alias(name):
241 if self.is_alias(name):
239 del self.linemagics[name]
242 del self.linemagics[name]
240 else:
243 else:
241 raise ValueError('%s is not an alias' % name)
244 raise ValueError('%s is not an alias' % name)
242
245
243 def clear_aliases(self):
246 def clear_aliases(self):
244 for name, cmd in self.aliases:
247 for name, cmd in self.aliases:
245 self.undefine_alias(name)
248 self.undefine_alias(name)
246
249
247 def retrieve_alias(self, name):
250 def retrieve_alias(self, name):
248 """Retrieve the command to which an alias expands."""
251 """Retrieve the command to which an alias expands."""
249 caller = self.get_alias(name)
252 caller = self.get_alias(name)
250 if caller:
253 if caller:
251 return caller.cmd
254 return caller.cmd
252 else:
255 else:
253 raise ValueError('%s is not an alias' % name)
256 raise ValueError('%s is not an alias' % name)
@@ -1,41 +1,62
1 from IPython.utils.capture import capture_output
1 from IPython.utils.capture import capture_output
2
2
3 import nose.tools as nt
3 import nose.tools as nt
4
4
5 def test_alias_lifecycle():
5 def test_alias_lifecycle():
6 name = 'test_alias1'
6 name = 'test_alias1'
7 cmd = 'echo "Hello"'
7 cmd = 'echo "Hello"'
8 am = _ip.alias_manager
8 am = _ip.alias_manager
9 am.clear_aliases()
9 am.clear_aliases()
10 am.define_alias(name, cmd)
10 am.define_alias(name, cmd)
11 assert am.is_alias(name)
11 assert am.is_alias(name)
12 nt.assert_equal(am.retrieve_alias(name), cmd)
12 nt.assert_equal(am.retrieve_alias(name), cmd)
13 nt.assert_in((name, cmd), am.aliases)
13 nt.assert_in((name, cmd), am.aliases)
14
14
15 # Test running the alias
15 # Test running the alias
16 orig_system = _ip.system
16 orig_system = _ip.system
17 result = []
17 result = []
18 _ip.system = result.append
18 _ip.system = result.append
19 try:
19 try:
20 _ip.run_cell('%{}'.format(name))
20 _ip.run_cell('%{}'.format(name))
21 result = [c.strip() for c in result]
21 result = [c.strip() for c in result]
22 nt.assert_equal(result, [cmd])
22 nt.assert_equal(result, [cmd])
23 finally:
23 finally:
24 _ip.system = orig_system
24 _ip.system = orig_system
25
25
26 # Test removing the alias
26 # Test removing the alias
27 am.undefine_alias(name)
27 am.undefine_alias(name)
28 assert not am.is_alias(name)
28 assert not am.is_alias(name)
29 with nt.assert_raises(ValueError):
29 with nt.assert_raises(ValueError):
30 am.retrieve_alias(name)
30 am.retrieve_alias(name)
31 nt.assert_not_in((name, cmd), am.aliases)
31 nt.assert_not_in((name, cmd), am.aliases)
32
32
33
33
34 def test_alias_args_error():
34 def test_alias_args_error():
35 """Error expanding with wrong number of arguments"""
35 """Error expanding with wrong number of arguments"""
36 _ip.alias_manager.define_alias('parts', 'echo first %s second %s')
36 _ip.alias_manager.define_alias('parts', 'echo first %s second %s')
37 # capture stderr:
37 # capture stderr:
38 with capture_output() as cap:
38 with capture_output() as cap:
39 _ip.run_cell('parts 1')
39 _ip.run_cell('parts 1')
40
40
41 nt.assert_equal(cap.stderr.split(':')[0], 'UsageError')
41 nt.assert_equal(cap.stderr.split(':')[0], 'UsageError')
42
43 def test_alias_args_commented():
44 """Check that alias correctly ignores 'commented out' args"""
45 _ip.magic('alias commetarg echo this is %%s a commented out arg')
46
47 with capture_output() as cap:
48 _ip.run_cell('commetarg')
49
50 nt.assert_equal(cap.stdout, 'this is %s a commented out arg')
51
52 def test_alias_args_commented_nargs():
53 """Check that alias correctly counts args, excluding those commented out"""
54 am = _ip.alias_manager
55 alias_name = 'comargcount'
56 cmd = 'echo this is %%s a commented out arg and this is not %s'
57
58 am.define_alias(alias_name, cmd)
59 assert am.is_alias(alias_name)
60
61 thealias = am.get_alias(alias_name)
62 nt.assert_equal(thealias.nargs, 1) No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now