Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/alias.py: 39%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2System command aliases.
4Authors:
6* Fernando Perez
7* Brian Granger
8"""
9from __future__ import annotations
11#-----------------------------------------------------------------------------
12# Copyright (C) 2008-2011 The IPython Development Team
13#
14# Distributed under the terms of the BSD License.
15#
16# The full license is in the file COPYING.txt, distributed with this software.
17#-----------------------------------------------------------------------------
19#-----------------------------------------------------------------------------
20# Imports
21#-----------------------------------------------------------------------------
23import os
24import re
25import sys
27from traitlets.config.configurable import Configurable
28from .error import UsageError
30from traitlets import List, Instance
31from logging import error
34#-----------------------------------------------------------------------------
35# Utilities
36#-----------------------------------------------------------------------------
38# This is used as the pattern for calls to split_user_input.
39shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
41def default_aliases() -> list[tuple[str, str]]:
42 """Return list of shell aliases to auto-define.
43 """
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
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
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.
51 if os.name == 'posix':
52 default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
53 ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
54 ('cat', 'cat'),
55 ]
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
58 # behavior in ipython, by passing the right flags for each platform
59 if sys.platform.startswith('linux'):
60 ls_aliases = [('ls', 'ls -F --color'),
61 # long ls
62 ('ll', 'ls -F -o --color'),
63 # ls normal files only
64 ('lf', 'ls -F -o --color %l | grep ^-'),
65 # ls symbolic links
66 ('lk', 'ls -F -o --color %l | grep ^l'),
67 # directories or links to directories,
68 ('ldir', 'ls -F -o --color %l | grep /$'),
69 # things which are executable
70 ('lx', 'ls -F -o --color %l | grep ^-..x'),
71 ]
72 elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
73 # OpenBSD, NetBSD. The ls implementation on these platforms do not support
74 # the -G switch and lack the ability to use colorized output.
75 ls_aliases = [('ls', 'ls -F'),
76 # long ls
77 ('ll', 'ls -F -l'),
78 # ls normal files only
79 ('lf', 'ls -F -l %l | grep ^-'),
80 # ls symbolic links
81 ('lk', 'ls -F -l %l | grep ^l'),
82 # directories or links to directories,
83 ('ldir', 'ls -F -l %l | grep /$'),
84 # things which are executable
85 ('lx', 'ls -F -l %l | grep ^-..x'),
86 ]
87 else:
88 # BSD, OSX, etc.
89 ls_aliases = [('ls', 'ls -F -G'),
90 # long ls
91 ('ll', 'ls -F -l -G'),
92 # ls normal files only
93 ('lf', 'ls -F -l -G %l | grep ^-'),
94 # ls symbolic links
95 ('lk', 'ls -F -l -G %l | grep ^l'),
96 # directories or links to directories,
97 ('ldir', 'ls -F -G -l %l | grep /$'),
98 # things which are executable
99 ('lx', 'ls -F -l -G %l | grep ^-..x'),
100 ]
101 default_aliases = default_aliases + ls_aliases
102 elif os.name in ['nt', 'dos']:
103 default_aliases = [('ls', 'dir /on'),
104 ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
105 ('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
106 ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
107 ]
108 else:
109 default_aliases = []
111 return default_aliases
114class AliasError(Exception):
115 pass
118class InvalidAliasError(AliasError):
119 pass
122class Alias:
123 """Callable object storing the details of one alias.
125 Instances are registered as magic functions to allow use of aliases.
126 """
128 # Prepare blacklist
129 blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
131 def __init__(self, shell, name, cmd):
132 self.shell = shell
133 self.name = name
134 self.cmd = cmd
135 self.__doc__ = f"Alias for `!{cmd}`"
136 self.nargs = self.validate()
138 def validate(self):
139 """Validate the alias, and return the number of arguments."""
140 if self.name in self.blacklist:
141 raise InvalidAliasError("The name %s can't be aliased "
142 "because it is a keyword or builtin." % self.name)
143 try:
144 caller = self.shell.magics_manager.magics['line'][self.name]
145 except KeyError:
146 pass
147 else:
148 if not isinstance(caller, Alias):
149 raise InvalidAliasError("The name %s can't be aliased "
150 "because it is another magic command." % self.name)
152 if not (isinstance(self.cmd, str)):
153 raise InvalidAliasError("An alias command must be a string, "
154 "got: %r" % self.cmd)
156 nargs = self.cmd.count('%s') - self.cmd.count('%%s')
158 if (nargs > 0) and (self.cmd.find('%l') >= 0):
159 raise InvalidAliasError('The %s and %l specifiers are mutually '
160 'exclusive in alias definitions.')
162 return nargs
164 def __repr__(self):
165 return f"<alias {self.name} for {self.cmd!r}>"
167 def __call__(self, rest=''):
168 cmd = self.cmd
169 nargs = self.nargs
170 # Expand the %l special to be the user's input line
171 if cmd.find('%l') >= 0:
172 cmd = cmd.replace('%l', rest)
173 rest = ''
175 if nargs==0:
176 if cmd.find('%%s') >= 1:
177 cmd = cmd.replace('%%s', '%s')
178 # Simple, argument-less aliases
179 cmd = '{} {}'.format(cmd, rest)
180 else:
181 # Handle aliases with positional arguments
182 args = rest.split(None, nargs)
183 if len(args) < nargs:
184 raise UsageError('Alias <%s> requires %s arguments, %s given.' %
185 (self.name, nargs, len(args)))
186 cmd = '{} {}'.format(cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
188 self.shell.system(cmd)
190#-----------------------------------------------------------------------------
191# Main AliasManager class
192#-----------------------------------------------------------------------------
194class AliasManager(Configurable):
195 default_aliases: List = List(default_aliases()).tag(config=True)
196 user_aliases: List = List(default_value=[]).tag(config=True)
197 shell = Instance(
198 "IPython.core.interactiveshell.InteractiveShellABC", allow_none=True
199 )
201 def __init__(self, shell=None, **kwargs):
202 super().__init__(shell=shell, **kwargs)
203 # For convenient access
204 if self.shell is not None:
205 self.linemagics = self.shell.magics_manager.magics["line"]
206 self.init_aliases()
208 def init_aliases(self):
209 # Load default & user aliases
210 for name, cmd in self.default_aliases + self.user_aliases:
211 if (
212 cmd.startswith("ls ")
213 and self.shell is not None
214 and self.shell.colors == "nocolor"
215 ):
216 cmd = cmd.replace(" --color", "")
217 self.soft_define_alias(name, cmd)
219 @property
220 def aliases(self) -> list:
221 return [(n, func.cmd) for (n, func) in self.linemagics.items()
222 if isinstance(func, Alias)]
224 def soft_define_alias(self, name, cmd):
225 """Define an alias, but don't raise on an AliasError."""
226 try:
227 self.define_alias(name, cmd)
228 except AliasError as e:
229 error("Invalid alias: %s" % e)
231 def define_alias(self, name, cmd):
232 """Define a new alias after validating it.
234 This will raise an :exc:`AliasError` if there are validation
235 problems.
236 """
237 caller = Alias(shell=self.shell, name=name, cmd=cmd)
238 self.shell.magics_manager.register_function(caller, magic_kind='line',
239 magic_name=name)
241 def get_alias(self, name):
242 """Return an alias, or None if no alias by that name exists."""
243 aname = self.linemagics.get(name, None)
244 return aname if isinstance(aname, Alias) else None
246 def is_alias(self, name):
247 """Return whether or not a given name has been defined as an alias"""
248 return self.get_alias(name) is not None
250 def undefine_alias(self, name):
251 if self.is_alias(name):
252 del self.linemagics[name]
253 else:
254 raise ValueError('%s is not an alias' % name)
256 def clear_aliases(self):
257 for name, _ in self.aliases:
258 self.undefine_alias(name)
260 def retrieve_alias(self, name):
261 """Retrieve the command to which an alias expands."""
262 caller = self.get_alias(name)
263 if caller:
264 return caller.cmd
265 else:
266 raise ValueError('%s is not an alias' % name)