(root)/
Python-3.11.7/
Lib/
cmd.py
       1  """A generic class to build line-oriented command interpreters.
       2  
       3  Interpreters constructed with this class obey the following conventions:
       4  
       5  1. End of file on input is processed as the command 'EOF'.
       6  2. A command is parsed out of each line by collecting the prefix composed
       7     of characters in the identchars member.
       8  3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method
       9     is passed a single argument consisting of the remainder of the line.
      10  4. Typing an empty line repeats the last command.  (Actually, it calls the
      11     method `emptyline', which may be overridden in a subclass.)
      12  5. There is a predefined `help' method.  Given an argument `topic', it
      13     calls the command `help_topic'.  With no arguments, it lists all topics
      14     with defined help_ functions, broken into up to three topics; documented
      15     commands, miscellaneous help topics, and undocumented commands.
      16  6. The command '?' is a synonym for `help'.  The command '!' is a synonym
      17     for `shell', if a do_shell method exists.
      18  7. If completion is enabled, completing commands will be done automatically,
      19     and completing of commands args is done by calling complete_foo() with
      20     arguments text, line, begidx, endidx.  text is string we are matching
      21     against, all returned matches must begin with it.  line is the current
      22     input line (lstripped), begidx and endidx are the beginning and end
      23     indexes of the text being matched, which could be used to provide
      24     different completion depending upon which position the argument is in.
      25  
      26  The `default' method may be overridden to intercept commands for which there
      27  is no do_ method.
      28  
      29  The `completedefault' method may be overridden to intercept completions for
      30  commands that have no complete_ method.
      31  
      32  The data member `self.ruler' sets the character used to draw separator lines
      33  in the help messages.  If empty, no ruler line is drawn.  It defaults to "=".
      34  
      35  If the value of `self.intro' is nonempty when the cmdloop method is called,
      36  it is printed out on interpreter startup.  This value may be overridden
      37  via an optional argument to the cmdloop() method.
      38  
      39  The data members `self.doc_header', `self.misc_header', and
      40  `self.undoc_header' set the headers used for the help function's
      41  listings of documented functions, miscellaneous topics, and undocumented
      42  functions respectively.
      43  """
      44  
      45  import string, sys
      46  
      47  __all__ = ["Cmd"]
      48  
      49  PROMPT = '(Cmd) '
      50  IDENTCHARS = string.ascii_letters + string.digits + '_'
      51  
      52  class ESC[4;38;5;81mCmd:
      53      """A simple framework for writing line-oriented command interpreters.
      54  
      55      These are often useful for test harnesses, administrative tools, and
      56      prototypes that will later be wrapped in a more sophisticated interface.
      57  
      58      A Cmd instance or subclass instance is a line-oriented interpreter
      59      framework.  There is no good reason to instantiate Cmd itself; rather,
      60      it's useful as a superclass of an interpreter class you define yourself
      61      in order to inherit Cmd's methods and encapsulate action methods.
      62  
      63      """
      64      prompt = PROMPT
      65      identchars = IDENTCHARS
      66      ruler = '='
      67      lastcmd = ''
      68      intro = None
      69      doc_leader = ""
      70      doc_header = "Documented commands (type help <topic>):"
      71      misc_header = "Miscellaneous help topics:"
      72      undoc_header = "Undocumented commands:"
      73      nohelp = "*** No help on %s"
      74      use_rawinput = 1
      75  
      76      def __init__(self, completekey='tab', stdin=None, stdout=None):
      77          """Instantiate a line-oriented interpreter framework.
      78  
      79          The optional argument 'completekey' is the readline name of a
      80          completion key; it defaults to the Tab key. If completekey is
      81          not None and the readline module is available, command completion
      82          is done automatically. The optional arguments stdin and stdout
      83          specify alternate input and output file objects; if not specified,
      84          sys.stdin and sys.stdout are used.
      85  
      86          """
      87          if stdin is not None:
      88              self.stdin = stdin
      89          else:
      90              self.stdin = sys.stdin
      91          if stdout is not None:
      92              self.stdout = stdout
      93          else:
      94              self.stdout = sys.stdout
      95          self.cmdqueue = []
      96          self.completekey = completekey
      97  
      98      def cmdloop(self, intro=None):
      99          """Repeatedly issue a prompt, accept input, parse an initial prefix
     100          off the received input, and dispatch to action methods, passing them
     101          the remainder of the line as argument.
     102  
     103          """
     104  
     105          self.preloop()
     106          if self.use_rawinput and self.completekey:
     107              try:
     108                  import readline
     109                  self.old_completer = readline.get_completer()
     110                  readline.set_completer(self.complete)
     111                  readline.parse_and_bind(self.completekey+": complete")
     112              except ImportError:
     113                  pass
     114          try:
     115              if intro is not None:
     116                  self.intro = intro
     117              if self.intro:
     118                  self.stdout.write(str(self.intro)+"\n")
     119              stop = None
     120              while not stop:
     121                  if self.cmdqueue:
     122                      line = self.cmdqueue.pop(0)
     123                  else:
     124                      if self.use_rawinput:
     125                          try:
     126                              line = input(self.prompt)
     127                          except EOFError:
     128                              line = 'EOF'
     129                      else:
     130                          self.stdout.write(self.prompt)
     131                          self.stdout.flush()
     132                          line = self.stdin.readline()
     133                          if not len(line):
     134                              line = 'EOF'
     135                          else:
     136                              line = line.rstrip('\r\n')
     137                  line = self.precmd(line)
     138                  stop = self.onecmd(line)
     139                  stop = self.postcmd(stop, line)
     140              self.postloop()
     141          finally:
     142              if self.use_rawinput and self.completekey:
     143                  try:
     144                      import readline
     145                      readline.set_completer(self.old_completer)
     146                  except ImportError:
     147                      pass
     148  
     149  
     150      def precmd(self, line):
     151          """Hook method executed just before the command line is
     152          interpreted, but after the input prompt is generated and issued.
     153  
     154          """
     155          return line
     156  
     157      def postcmd(self, stop, line):
     158          """Hook method executed just after a command dispatch is finished."""
     159          return stop
     160  
     161      def preloop(self):
     162          """Hook method executed once when the cmdloop() method is called."""
     163          pass
     164  
     165      def postloop(self):
     166          """Hook method executed once when the cmdloop() method is about to
     167          return.
     168  
     169          """
     170          pass
     171  
     172      def parseline(self, line):
     173          """Parse the line into a command name and a string containing
     174          the arguments.  Returns a tuple containing (command, args, line).
     175          'command' and 'args' may be None if the line couldn't be parsed.
     176          """
     177          line = line.strip()
     178          if not line:
     179              return None, None, line
     180          elif line[0] == '?':
     181              line = 'help ' + line[1:]
     182          elif line[0] == '!':
     183              if hasattr(self, 'do_shell'):
     184                  line = 'shell ' + line[1:]
     185              else:
     186                  return None, None, line
     187          i, n = 0, len(line)
     188          while i < n and line[i] in self.identchars: i = i+1
     189          cmd, arg = line[:i], line[i:].strip()
     190          return cmd, arg, line
     191  
     192      def onecmd(self, line):
     193          """Interpret the argument as though it had been typed in response
     194          to the prompt.
     195  
     196          This may be overridden, but should not normally need to be;
     197          see the precmd() and postcmd() methods for useful execution hooks.
     198          The return value is a flag indicating whether interpretation of
     199          commands by the interpreter should stop.
     200  
     201          """
     202          cmd, arg, line = self.parseline(line)
     203          if not line:
     204              return self.emptyline()
     205          if cmd is None:
     206              return self.default(line)
     207          self.lastcmd = line
     208          if line == 'EOF' :
     209              self.lastcmd = ''
     210          if cmd == '':
     211              return self.default(line)
     212          else:
     213              try:
     214                  func = getattr(self, 'do_' + cmd)
     215              except AttributeError:
     216                  return self.default(line)
     217              return func(arg)
     218  
     219      def emptyline(self):
     220          """Called when an empty line is entered in response to the prompt.
     221  
     222          If this method is not overridden, it repeats the last nonempty
     223          command entered.
     224  
     225          """
     226          if self.lastcmd:
     227              return self.onecmd(self.lastcmd)
     228  
     229      def default(self, line):
     230          """Called on an input line when the command prefix is not recognized.
     231  
     232          If this method is not overridden, it prints an error message and
     233          returns.
     234  
     235          """
     236          self.stdout.write('*** Unknown syntax: %s\n'%line)
     237  
     238      def completedefault(self, *ignored):
     239          """Method called to complete an input line when no command-specific
     240          complete_*() method is available.
     241  
     242          By default, it returns an empty list.
     243  
     244          """
     245          return []
     246  
     247      def completenames(self, text, *ignored):
     248          dotext = 'do_'+text
     249          return [a[3:] for a in self.get_names() if a.startswith(dotext)]
     250  
     251      def complete(self, text, state):
     252          """Return the next possible completion for 'text'.
     253  
     254          If a command has not been entered, then complete against command list.
     255          Otherwise try to call complete_<command> to get list of completions.
     256          """
     257          if state == 0:
     258              import readline
     259              origline = readline.get_line_buffer()
     260              line = origline.lstrip()
     261              stripped = len(origline) - len(line)
     262              begidx = readline.get_begidx() - stripped
     263              endidx = readline.get_endidx() - stripped
     264              if begidx>0:
     265                  cmd, args, foo = self.parseline(line)
     266                  if cmd == '':
     267                      compfunc = self.completedefault
     268                  else:
     269                      try:
     270                          compfunc = getattr(self, 'complete_' + cmd)
     271                      except AttributeError:
     272                          compfunc = self.completedefault
     273              else:
     274                  compfunc = self.completenames
     275              self.completion_matches = compfunc(text, line, begidx, endidx)
     276          try:
     277              return self.completion_matches[state]
     278          except IndexError:
     279              return None
     280  
     281      def get_names(self):
     282          # This method used to pull in base class attributes
     283          # at a time dir() didn't do it yet.
     284          return dir(self.__class__)
     285  
     286      def complete_help(self, *args):
     287          commands = set(self.completenames(*args))
     288          topics = set(a[5:] for a in self.get_names()
     289                       if a.startswith('help_' + args[0]))
     290          return list(commands | topics)
     291  
     292      def do_help(self, arg):
     293          'List available commands with "help" or detailed help with "help cmd".'
     294          if arg:
     295              # XXX check arg syntax
     296              try:
     297                  func = getattr(self, 'help_' + arg)
     298              except AttributeError:
     299                  try:
     300                      doc=getattr(self, 'do_' + arg).__doc__
     301                      if doc:
     302                          self.stdout.write("%s\n"%str(doc))
     303                          return
     304                  except AttributeError:
     305                      pass
     306                  self.stdout.write("%s\n"%str(self.nohelp % (arg,)))
     307                  return
     308              func()
     309          else:
     310              names = self.get_names()
     311              cmds_doc = []
     312              cmds_undoc = []
     313              topics = set()
     314              for name in names:
     315                  if name[:5] == 'help_':
     316                      topics.add(name[5:])
     317              names.sort()
     318              # There can be duplicates if routines overridden
     319              prevname = ''
     320              for name in names:
     321                  if name[:3] == 'do_':
     322                      if name == prevname:
     323                          continue
     324                      prevname = name
     325                      cmd=name[3:]
     326                      if cmd in topics:
     327                          cmds_doc.append(cmd)
     328                          topics.remove(cmd)
     329                      elif getattr(self, name).__doc__:
     330                          cmds_doc.append(cmd)
     331                      else:
     332                          cmds_undoc.append(cmd)
     333              self.stdout.write("%s\n"%str(self.doc_leader))
     334              self.print_topics(self.doc_header,   cmds_doc,   15,80)
     335              self.print_topics(self.misc_header,  sorted(topics),15,80)
     336              self.print_topics(self.undoc_header, cmds_undoc, 15,80)
     337  
     338      def print_topics(self, header, cmds, cmdlen, maxcol):
     339          if cmds:
     340              self.stdout.write("%s\n"%str(header))
     341              if self.ruler:
     342                  self.stdout.write("%s\n"%str(self.ruler * len(header)))
     343              self.columnize(cmds, maxcol-1)
     344              self.stdout.write("\n")
     345  
     346      def columnize(self, list, displaywidth=80):
     347          """Display a list of strings as a compact set of columns.
     348  
     349          Each column is only as wide as necessary.
     350          Columns are separated by two spaces (one was not legible enough).
     351          """
     352          if not list:
     353              self.stdout.write("<empty>\n")
     354              return
     355  
     356          nonstrings = [i for i in range(len(list))
     357                          if not isinstance(list[i], str)]
     358          if nonstrings:
     359              raise TypeError("list[i] not a string for i in %s"
     360                              % ", ".join(map(str, nonstrings)))
     361          size = len(list)
     362          if size == 1:
     363              self.stdout.write('%s\n'%str(list[0]))
     364              return
     365          # Try every row count from 1 upwards
     366          for nrows in range(1, len(list)):
     367              ncols = (size+nrows-1) // nrows
     368              colwidths = []
     369              totwidth = -2
     370              for col in range(ncols):
     371                  colwidth = 0
     372                  for row in range(nrows):
     373                      i = row + nrows*col
     374                      if i >= size:
     375                          break
     376                      x = list[i]
     377                      colwidth = max(colwidth, len(x))
     378                  colwidths.append(colwidth)
     379                  totwidth += colwidth + 2
     380                  if totwidth > displaywidth:
     381                      break
     382              if totwidth <= displaywidth:
     383                  break
     384          else:
     385              nrows = len(list)
     386              ncols = 1
     387              colwidths = [0]
     388          for row in range(nrows):
     389              texts = []
     390              for col in range(ncols):
     391                  i = row + nrows*col
     392                  if i >= size:
     393                      x = ""
     394                  else:
     395                      x = list[i]
     396                  texts.append(x)
     397              while texts and not texts[-1]:
     398                  del texts[-1]
     399              for col in range(len(texts)):
     400                  texts[col] = texts[col].ljust(colwidths[col])
     401              self.stdout.write("%s\n"%str("  ".join(texts)))