alias.py 10 KB

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