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