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