Bug 1518586 - [mach] Implement bash completion for subcommands and arguments r=nalexander
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 11 Jan 2019 15:28:49 +0000
changeset 453501 729ea50e304585cd0b42ed9138a47b9e7ae68bb0
parent 453500 5b651dea28d8891be33a372e61dea9ce3c2422c3
child 453502 ebff3389b3e49c5bcb0638ed4c7f4a0e84ce58b6
push id35357
push usernerli@mozilla.com
push dateFri, 11 Jan 2019 21:54:07 +0000
treeherdermozilla-central@0ce024c91511 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs1518586
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1518586 - [mach] Implement bash completion for subcommands and arguments r=nalexander Since we're calling into a mach command for the current completion implementation anyway (and incurring python startup penalties), we might as well move all the bash logic into the mach command. The new 'mach-completion' command was created in case there are scripts relying on the current behaviour of 'mach-commands'. Depends on D16254 Differential Revision: https://phabricator.services.mozilla.com/D16255
python/mach/bash-completion.sh
python/mach/mach/commands/commandinfo.py
--- a/python/mach/bash-completion.sh
+++ b/python/mach/bash-completion.sh
@@ -1,29 +1,18 @@
 function _mach()
 {
-  local cur cmds c subcommand
+  local cur targets
   COMPREPLY=()
 
-  # Load the list of commands
-  cmds=`"${COMP_WORDS[0]}" mach-commands`
-
-  # Look for the subcommand.
-  cur="${COMP_WORDS[COMP_CWORD]}"
-  subcommand=""
-  c=1
-  while [ $c -lt $COMP_CWORD ]; do
-    word="${COMP_WORDS[c]}"
-    for cmd in $cmds; do
-      if [ "$cmd" = "$word" ]; then
-        subcommand="$word"
-      fi
-    done
-    c=$((++c))
-  done
-
-  if [[ "$subcommand" == "help" || -z "$subcommand" ]]; then
-      COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) )
+  # Calling `mach-completion` with -h/--help would result in the
+  # help text being used as the completion targets.
+  if [[ $COMP_LINE == *"-h"* || $COMP_LINE == *"--help"* ]]; then
+    return 0
   fi
 
+  # Load the list of targets
+  targets=`"${COMP_WORDS[0]}" mach-completion ${COMP_LINE}`
+  cur="${COMP_WORDS[COMP_CWORD]}"
+  COMPREPLY=( $(compgen -W "$targets" -- ${cur}) )
   return 0
 }
 complete -o default -F _mach mach
--- a/python/mach/mach/commands/commandinfo.py
+++ b/python/mach/mach/commands/commandinfo.py
@@ -1,14 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
+import argparse
+from itertools import chain
+
 from mach.decorators import (
     CommandProvider,
     Command,
     CommandArgument,
 )
 
 
 @CommandProvider
@@ -45,8 +48,43 @@ class BuiltinCommands(object):
 
             print(command)
             print('=' * len(command))
             print('')
             print('File: %s' % inspect.getsourcefile(method))
             print('Class: %s' % cls.__name__)
             print('Method: %s' % handler.method)
             print('')
+
+    @Command('mach-completion', category='misc',
+             description='Prints a list of completion strings for the specified command.')
+    @CommandArgument('args', default=None, nargs=argparse.REMAINDER,
+                     help="Command to complete.")
+    def completion(self, args):
+        all_commands = sorted(self.command_keys)
+        if not args:
+            print("\n".join(all_commands))
+            return
+
+        is_help = 'help' in args
+        command = None
+        for i, arg in enumerate(args):
+            if arg in all_commands:
+                command = arg
+                args = args[i+1:]
+                break
+
+        if not command:
+            print("\n".join(all_commands))
+            return
+
+        handler = self.context.commands.command_handlers[command]
+        for arg in args:
+            if arg in handler.subcommand_handlers:
+                handler = handler.subcommand_handlers[arg]
+                break
+
+        parser = handler.parser
+        targets = sorted(handler.subcommand_handlers.keys())
+        if not is_help:
+            targets.append('help')
+            targets.extend(chain(*[action.option_strings for action in parser._actions]))
+        print("\n".join(targets))