зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1519990 - [mach] Add ability to generate completion scripts via 'mach mach-completion <shell>', r=mhentges
Supported shells are 'bash', 'zsh' and 'fish'. Here are installation instructions with various shells and contexts: ### bash $ mach mach-completion bash -f _mach $ sudo mv _mach /etc/bash_completion.d ### bash (homebrew) $ mach mach-completion bash -f $(brew --prefix)/etc/bash_completion.d/mach.bash-completion ### zsh $ mkdir ~/.zfunc $ mach mach-completion zsh -f ~/.zfunc/_mach then edit ~/.zshrc and add: fpath += ~/.zfunc autoload -U compinit && compinit ### zsh (oh-my-zsh) $ mkdir $ZSH/plugins/mach $ mach mach-completion zsh -f $ZSH/plugins/mach/_mach then edit ~/.zshrc and add 'mach' to your enabled plugins: plugins(mach ...) ### zsh (prezto) $ mach mach-completion zsh -f ~/.zprezto/modules/completion/external/src/_mach ### fish $ ./mach mach-completion fish -f ~/.config/fish/completions/mach.fish ### fish (homebrew) $ ./mach mach-completion fish -f (brew --prefix)/share/fish/vendor_completions.d/mach.fish Differential Revision: https://phabricator.services.mozilla.com/D90416
This commit is contained in:
Родитель
1f357936b2
Коммит
ef21656e00
|
@ -41,46 +41,82 @@ following are valid:
|
|||
$ ./mach try fuzzy --help
|
||||
|
||||
|
||||
Auto Completion
|
||||
---------------
|
||||
Tab Completion
|
||||
--------------
|
||||
|
||||
A `bash completion`_ script is bundled with mach, it can be used with either ``bash`` or ``zsh``.
|
||||
There are commands built-in to ``mach`` that can generate a fast tab completion
|
||||
script for various shells. Supported shells are currently ``bash``, ``zsh`` and
|
||||
``fish``. These generated scripts will slowly become out of date over time, so
|
||||
you may want to create a cron task to periodically re-generate them.
|
||||
|
||||
See below for installation instructions:
|
||||
|
||||
Bash
|
||||
~~~~
|
||||
|
||||
Add the following to your ``~/.bashrc``, ``~/.bash_profile`` or equivalent:
|
||||
.. code-block:: shell
|
||||
|
||||
$ mach mach-completion bash -f _mach
|
||||
$ sudo mv _mach /etc/bash_completion.d
|
||||
|
||||
Bash (homebrew)
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
source <srcdir>/python/mach/bash-completion.sh
|
||||
|
||||
.. tip::
|
||||
|
||||
Windows users using the default shell bundled with mozilla-build should source the completion
|
||||
script from ``~/.bash_profile`` (it may need to be created first).
|
||||
$ mach mach-completion bash -f $(brew --prefix)/etc/bash_completion.d/mach.bash-completion
|
||||
|
||||
Zsh
|
||||
~~~
|
||||
|
||||
Add this to your ``~/.zshrc`` or equivalent:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
source <srcdir>/python/mach/bash-completion.sh
|
||||
|
||||
The ``compinit`` function also needs to be loaded, but if using a framework (like ``oh-my-zsh``),
|
||||
this will often be done for you. So if you see ``command not found: compdef``, you'll need to modify
|
||||
the above instructions to:
|
||||
.. code-block:: shell
|
||||
|
||||
$ mkdir ~/.zfunc
|
||||
$ mach mach-completion zsh -f ~/.zfunc/_mach
|
||||
|
||||
then edit ~/.zshrc and add:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
fpath += ~/.zfunc
|
||||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
source <srcdir>/python/mach/bash-completion.sh
|
||||
|
||||
Don't forget to substitute ``<srcdir>`` with the path to your checkout.
|
||||
You can use any directory of your choosing.
|
||||
|
||||
Zsh (oh-my-zsh)
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ mkdir $ZSH/plugins/mach
|
||||
$ mach mach-completion zsh -f $ZSH/plugins/mach/_mach
|
||||
|
||||
then edit ~/.zshrc and add 'mach' to your enabled plugins:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
plugins(mach ...)
|
||||
|
||||
Zsh (prezto)
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ mach mach-completion zsh -f ~/.zprezto/modules/completion/external/src/_mach
|
||||
|
||||
Fish
|
||||
~~~~
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ ./mach mach-completion fish -f ~/.config/fish/completions/mach.fish
|
||||
|
||||
Fish (homebrew)
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ ./mach mach-completion fish -f (brew --prefix)/share/fish/vendor_completions.d/mach.fish
|
||||
|
||||
|
||||
User Settings
|
||||
|
|
|
@ -5,21 +5,47 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from itertools import chain
|
||||
|
||||
import attr
|
||||
|
||||
from mach.decorators import (
|
||||
CommandProvider,
|
||||
Command,
|
||||
CommandArgument,
|
||||
SubCommand,
|
||||
)
|
||||
from mozbuild.base import MachCommandBase
|
||||
from mozbuild.util import memoize, memoized_property
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
COMPLETION_TEMPLATES_DIR = os.path.join(here, "completion_templates")
|
||||
|
||||
|
||||
@attr.s
|
||||
class CommandInfo(object):
|
||||
name = attr.ib(type=str)
|
||||
description = attr.ib(type=str)
|
||||
subcommands = attr.ib(type=list)
|
||||
options = attr.ib(type=dict)
|
||||
subcommand = attr.ib(type=str, default=None)
|
||||
|
||||
|
||||
def render_template(shell, context):
|
||||
filename = "{}.template".format(shell)
|
||||
with open(os.path.join(COMPLETION_TEMPLATES_DIR, filename)) as fh:
|
||||
template = fh.read()
|
||||
return template % context
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class BuiltinCommands(MachCommandBase):
|
||||
|
||||
@property
|
||||
@memoized_property
|
||||
def command_handlers(self):
|
||||
"""A dictionary of command handlers keyed by command name."""
|
||||
return self._mach_context.commands.command_handlers
|
||||
|
@ -29,23 +55,78 @@ class BuiltinCommands(MachCommandBase):
|
|||
"""A sorted list of all command names."""
|
||||
return sorted(self.command_handlers)
|
||||
|
||||
def _get_parser_options(self, parser):
|
||||
options = {}
|
||||
for action in parser._actions:
|
||||
# ignore positional args
|
||||
if not action.option_strings:
|
||||
continue
|
||||
|
||||
# ignore suppressed args
|
||||
if action.help == argparse.SUPPRESS:
|
||||
continue
|
||||
|
||||
options[tuple(action.option_strings)] = action.help or ""
|
||||
return options
|
||||
|
||||
@memoized_property
|
||||
def global_options(self):
|
||||
"""Return a dict of global options.
|
||||
|
||||
Of the form `{("-o", "--option"): "description"}`.
|
||||
"""
|
||||
for group in self._mach_context.global_parser._action_groups:
|
||||
if group.title == "Global Arguments":
|
||||
return self._get_parser_options(group)
|
||||
|
||||
@memoize
|
||||
def get_handler_options(self, handler):
|
||||
"""Given a command handler, return all available options."""
|
||||
targets = []
|
||||
def _get_handler_options(self, handler):
|
||||
"""Return a dict of options for the given handler.
|
||||
|
||||
# The 'option_strings' are of the form [('-f', '--foo'), ('-b', '--bar'), ...].
|
||||
option_strings = [item[0] for item in handler.arguments]
|
||||
# Filter out positional arguments (we don't want to complete their metavar).
|
||||
option_strings = [opt for opt in option_strings if opt[0].startswith('-')]
|
||||
targets.extend(chain(*option_strings))
|
||||
Of the form `{("-o", "--option"): "description"}`.
|
||||
"""
|
||||
options = {}
|
||||
for option_strings, val in handler.arguments:
|
||||
# ignore positional args
|
||||
if option_strings[0][0] != "-":
|
||||
continue
|
||||
|
||||
# If the command uses its own ArgumentParser, extract options from there as well.
|
||||
if handler.parser:
|
||||
targets.extend(chain(*[action.option_strings
|
||||
for action in handler.parser._actions]))
|
||||
options[tuple(option_strings)] = val.get("help", "")
|
||||
|
||||
return targets
|
||||
if handler._parser:
|
||||
options.update(self._get_parser_options(handler.parser))
|
||||
|
||||
return options
|
||||
|
||||
def _get_handler_info(self, handler):
|
||||
try:
|
||||
options = self._get_handler_options(handler)
|
||||
except (Exception, SystemExit):
|
||||
# We don't want misbehaving commands to break tab completion,
|
||||
# ignore any exceptions.
|
||||
options = {}
|
||||
|
||||
subcommands = []
|
||||
for sub in sorted(handler.subcommand_handlers):
|
||||
subcommands.append(self._get_handler_info(handler.subcommand_handlers[sub]))
|
||||
|
||||
return CommandInfo(
|
||||
name=handler.name,
|
||||
description=handler.description or "",
|
||||
options=options,
|
||||
subcommands=subcommands,
|
||||
subcommand=handler.subcommand,
|
||||
)
|
||||
|
||||
@memoized_property
|
||||
def commands_info(self):
|
||||
"""Return a list of CommandInfo objects for each command."""
|
||||
commands_info = []
|
||||
# Loop over self.commands rather than self.command_handlers.items() for
|
||||
# alphabetical order.
|
||||
for c in self.commands:
|
||||
commands_info.append(self._get_handler_info(self.command_handlers[c]))
|
||||
return commands_info
|
||||
|
||||
@Command('mach-commands', category='misc',
|
||||
description='List all mach commands.')
|
||||
|
@ -109,5 +190,234 @@ class BuiltinCommands(MachCommandBase):
|
|||
return
|
||||
|
||||
targets.append('help')
|
||||
targets.extend(self.get_handler_options(handler))
|
||||
targets.extend(chain(*self._get_handler_options(handler).keys()))
|
||||
print("\n".join(targets))
|
||||
|
||||
def _zsh_describe(self, value, description=None):
|
||||
value = '"' + value.replace(":", "\\:")
|
||||
if description:
|
||||
description = re.sub(
|
||||
r'(["\'#&;`|*?~<>^()\[\]{}$\\\x0A\xFF])', r"\\\1", description
|
||||
)
|
||||
value += ":{}".format(subprocess.list2cmdline([description]).strip('"'))
|
||||
|
||||
value += '"'
|
||||
|
||||
return value
|
||||
|
||||
@SubCommand('mach-completion', 'bash',
|
||||
description="Print mach completion script for bash shell")
|
||||
@CommandArgument("-f", "--file", dest="outfile", default=None,
|
||||
help="File path to save completion script.")
|
||||
def completion_bash(self, outfile):
|
||||
commands_subcommands = []
|
||||
case_options = []
|
||||
case_subcommands = []
|
||||
for i, cmd in enumerate(self.commands_info):
|
||||
# Build case statement for options.
|
||||
options = []
|
||||
for opt_strs, description in cmd.options.items():
|
||||
for opt in opt_strs:
|
||||
options.append(self._zsh_describe(opt, None).strip('"'))
|
||||
|
||||
if options:
|
||||
case_options.append("\n".join([
|
||||
" ({})".format(cmd.name),
|
||||
' opts="${{opts}} {}"'.format(" ".join(options)),
|
||||
" ;;",
|
||||
"",
|
||||
]))
|
||||
|
||||
# Build case statement for subcommand options.
|
||||
for sub in cmd.subcommands:
|
||||
options = []
|
||||
for opt_strs, description in sub.options.items():
|
||||
for opt in opt_strs:
|
||||
options.append(self._zsh_describe(opt, None))
|
||||
|
||||
if options:
|
||||
case_options.append("\n".join([
|
||||
' ("{} {}")'.format(sub.name, sub.subcommand),
|
||||
' opts="${{opts}} {}"'.format(" ".join(options)),
|
||||
" ;;",
|
||||
""
|
||||
]))
|
||||
|
||||
# Build case statement for subcommands.
|
||||
subcommands = [
|
||||
self._zsh_describe(s.subcommand, None) for s in cmd.subcommands
|
||||
]
|
||||
if subcommands:
|
||||
commands_subcommands.append('[{}]=" {} "'.format(
|
||||
cmd.name, " ".join([h.subcommand for h in cmd.subcommands]))
|
||||
)
|
||||
|
||||
case_subcommands.append("\n".join([
|
||||
" ({})".format(cmd.name),
|
||||
' subs="${{subs}} {}"'.format(" ".join(subcommands)),
|
||||
" ;;",
|
||||
"",
|
||||
]))
|
||||
|
||||
globalopts = [opt for opt_strs in self.global_options for opt in opt_strs]
|
||||
context = {
|
||||
"case_options": "\n".join(case_options),
|
||||
"case_subcommands": "\n".join(case_subcommands),
|
||||
"commands": " ".join(self.commands),
|
||||
"commands_subcommands": " ".join(sorted(commands_subcommands)),
|
||||
"globalopts": " ".join(sorted(globalopts)),
|
||||
}
|
||||
|
||||
outfile = open(outfile, 'w') if outfile else sys.stdout
|
||||
print(render_template("bash", context), file=outfile)
|
||||
|
||||
@SubCommand('mach-completion', 'zsh',
|
||||
description="Print mach completion script for zsh shell")
|
||||
@CommandArgument("-f", "--file", dest="outfile", default=None,
|
||||
help="File path to save completion script.")
|
||||
def completion_zsh(self, outfile):
|
||||
commands_descriptions = []
|
||||
commands_subcommands = []
|
||||
case_options = []
|
||||
case_subcommands = []
|
||||
for i, cmd in enumerate(self.commands_info):
|
||||
commands_descriptions.append(
|
||||
self._zsh_describe(cmd.name, cmd.description)
|
||||
)
|
||||
|
||||
# Build case statement for options.
|
||||
options = []
|
||||
for opt_strs, description in cmd.options.items():
|
||||
for opt in opt_strs:
|
||||
options.append(self._zsh_describe(opt, description))
|
||||
|
||||
if options:
|
||||
case_options.append("\n".join([
|
||||
" ({})".format(cmd.name),
|
||||
" opts+=({})".format(" ".join(options)),
|
||||
" ;;",
|
||||
""
|
||||
]))
|
||||
|
||||
# Build case statement for subcommand options.
|
||||
for sub in cmd.subcommands:
|
||||
options = []
|
||||
for opt_strs, description in sub.options.items():
|
||||
for opt in opt_strs:
|
||||
options.append(self._zsh_describe(opt, description))
|
||||
|
||||
if options:
|
||||
case_options.append("\n".join([
|
||||
" ({} {})".format(sub.name, sub.subcommand),
|
||||
" opts+=({})".format(" ".join(options)),
|
||||
" ;;",
|
||||
""
|
||||
]))
|
||||
|
||||
# Build case statement for subcommands.
|
||||
subcommands = [
|
||||
self._zsh_describe(s.subcommand, s.description) for s in cmd.subcommands
|
||||
]
|
||||
if subcommands:
|
||||
commands_subcommands.append('[{}]=" {} "'.format(
|
||||
cmd.name, " ".join([h.subcommand for h in cmd.subcommands]))
|
||||
)
|
||||
|
||||
case_subcommands.append("\n".join([
|
||||
" ({})".format(cmd.name),
|
||||
" subs+=({})".format(" ".join(subcommands)),
|
||||
" ;;",
|
||||
""
|
||||
]))
|
||||
|
||||
globalopts = []
|
||||
for opt_strings, description in self.global_options.items():
|
||||
for opt in opt_strings:
|
||||
globalopts.append(self._zsh_describe(opt, description))
|
||||
|
||||
context = {
|
||||
"case_options": "\n".join(case_options),
|
||||
"case_subcommands": "\n".join(case_subcommands),
|
||||
"commands": " ".join(sorted(commands_descriptions)),
|
||||
"commands_subcommands": " ".join(sorted(commands_subcommands)),
|
||||
"globalopts": " ".join(sorted(globalopts)),
|
||||
}
|
||||
|
||||
outfile = open(outfile, 'w') if outfile else sys.stdout
|
||||
print(render_template("zsh", context), file=outfile)
|
||||
|
||||
@SubCommand('mach-completion', 'fish',
|
||||
description="Print mach completion script for fish shell")
|
||||
@CommandArgument("-f", "--file", dest="outfile", default=None,
|
||||
help="File path to save completion script.")
|
||||
def completion_fish(self, outfile):
|
||||
|
||||
def _append_opt_strs(comp, opt_strs):
|
||||
for opt in opt_strs:
|
||||
if opt.startswith('--'):
|
||||
comp += " -l {}".format(opt[2:])
|
||||
elif opt.startswith('-'):
|
||||
comp += " -s {}".format(opt[1:])
|
||||
return comp
|
||||
|
||||
globalopts = []
|
||||
for opt_strs, description in self.global_options.items():
|
||||
comp = (
|
||||
"complete -c mach -n '__fish_mach_complete_no_command' "
|
||||
"-d '{}'".format(description.replace("'", "\\'"))
|
||||
)
|
||||
comp = _append_opt_strs(comp, opt_strs)
|
||||
globalopts.append(comp)
|
||||
|
||||
cmds = []
|
||||
cmds_opts = []
|
||||
for i, cmd in enumerate(self.commands_info):
|
||||
cmds.append(
|
||||
"complete -c mach -f -n '__fish_mach_complete_no_command' "
|
||||
"-a {} -d '{}'".format(
|
||||
cmd.name,
|
||||
cmd.description.replace("'", "\\'"),
|
||||
)
|
||||
)
|
||||
|
||||
cmds_opts += ["# {}".format(cmd.name)]
|
||||
|
||||
subcommands = " ".join([s.subcommand for s in cmd.subcommands])
|
||||
for opt_strs, description in cmd.options.items():
|
||||
comp = (
|
||||
"complete -c mach -A -n '__fish_mach_complete_command {} {}' "
|
||||
"-d '{}'".format(cmd.name, subcommands, description.replace("'", "\\'"))
|
||||
)
|
||||
comp = _append_opt_strs(comp, opt_strs)
|
||||
cmds_opts.append(comp)
|
||||
|
||||
for sub in cmd.subcommands:
|
||||
|
||||
for opt_strs, description in sub.options.items():
|
||||
comp = (
|
||||
"complete -c mach -A -n '__fish_mach_complete_subcommand {} {}' "
|
||||
"-d '{}'".format(sub.name, sub.subcommand, description.replace("'", "\\'"))
|
||||
)
|
||||
comp = _append_opt_strs(comp, opt_strs)
|
||||
cmds_opts.append(comp)
|
||||
|
||||
description = sub.description or ""
|
||||
description = description.replace("'", "\\'")
|
||||
comp = (
|
||||
"complete -c mach -A -n '__fish_mach_complete_command {} {}' "
|
||||
"-d '{}' -a {}".format(cmd.name, subcommands, description, sub.subcommand)
|
||||
)
|
||||
cmds_opts.append(comp)
|
||||
|
||||
if i < len(self.commands) - 1:
|
||||
cmds_opts.append("")
|
||||
|
||||
context = {
|
||||
"commands": " ".join(self.commands),
|
||||
"command_completions": "\n".join(cmds),
|
||||
"command_option_completions": "\n".join(cmds_opts),
|
||||
"global_option_completions": "\n".join(globalopts),
|
||||
}
|
||||
|
||||
outfile = open(outfile, 'w') if outfile else sys.stdout
|
||||
print(render_template("fish", context), file=outfile)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
_mach_complete()
|
||||
{
|
||||
local com coms comsubs cur opts script sub subs
|
||||
COMPREPLY=()
|
||||
declare -A comsubs=( %(commands_subcommands)s )
|
||||
|
||||
_get_comp_words_by_ref -n : cur words
|
||||
# for an alias, get the real script behind it
|
||||
if [[ $(type -t ${words[0]}) == "alias" ]]; then
|
||||
script=$(alias ${words[0]} | sed -E "s/alias ${words[0]}='(.*)'/\\1/")
|
||||
else
|
||||
script=${words[0]}
|
||||
fi
|
||||
# lookup for command and subcommand
|
||||
for word in ${words[@]:1}; do
|
||||
if [[ $word == -* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -z $com ]]; then
|
||||
com=$word
|
||||
elif [[ "${comsubs[$com]}" == *" $word "* ]]; then
|
||||
sub=$word
|
||||
break
|
||||
fi
|
||||
done
|
||||
# completing for an option
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
if [[ -n $com ]]; then
|
||||
if [[ -n $sub ]]; then
|
||||
optkey="$com $sub"
|
||||
else
|
||||
optkey="$com"
|
||||
fi
|
||||
case $optkey in
|
||||
%(case_options)s
|
||||
esac
|
||||
else
|
||||
# no command, complete global options
|
||||
opts="%(globalopts)s"
|
||||
fi
|
||||
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
|
||||
__ltrim_colon_completions "$cur"
|
||||
return 0;
|
||||
# completing for a command
|
||||
elif [[ $cur == $com ]]; then
|
||||
coms="%(commands)s"
|
||||
COMPREPLY=($(compgen -W "${coms}" -- ${cur}))
|
||||
__ltrim_colon_completions "$cur"
|
||||
return 0
|
||||
else
|
||||
if [[ -z $sub ]]; then
|
||||
case "$com" in
|
||||
%(case_subcommands)s
|
||||
esac
|
||||
COMPREPLY=($(compgen -W "${subs}" -- ${cur}))
|
||||
__ltrim_colon_completions "$cur"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
complete -o default -F _mach_complete mach
|
|
@ -0,0 +1,64 @@
|
|||
function __fish_mach_complete_no_command
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i %(commands)s
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_mach_complete_command_matches
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i %(commands)s
|
||||
set com $i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not set -q com
|
||||
return 1
|
||||
end
|
||||
|
||||
if test "$com" != "$argv"
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_mach_complete_command
|
||||
__fish_mach_complete_command_matches $argv[1]
|
||||
if test $status -ne 0
|
||||
return 1
|
||||
end
|
||||
|
||||
# If a subcommand is already entered, don't complete, we should defer to
|
||||
# '__fish_mach_complete_subcommand'.
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i $argv[2..-1]
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_mach_complete_subcommand
|
||||
__fish_mach_complete_command_matches $argv[1]
|
||||
if test $status -ne 0
|
||||
return 1
|
||||
end
|
||||
|
||||
# Command matches, now check for subcommand
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i $argv[2]
|
||||
return 0
|
||||
end
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
# global options
|
||||
%(global_option_completions)s
|
||||
# commands
|
||||
%(command_completions)s
|
||||
# command options
|
||||
%(command_option_completions)s
|
|
@ -0,0 +1,62 @@
|
|||
#compdef mach
|
||||
_mach_complete()
|
||||
{
|
||||
local com coms comsubs cur optkey opts state sub subs
|
||||
cur=${words[${#words[@]}]}
|
||||
typeset -A comsubs
|
||||
comsubs=( %(commands_subcommands)s )
|
||||
|
||||
# lookup for command and subcommand
|
||||
for word in ${words[@]:1}; do
|
||||
if [[ $word == -* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -z $com ]]; then
|
||||
com=$word
|
||||
elif [[ ${comsubs[$com]} == *" $word "* ]]; then
|
||||
sub=$word
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# check for a subcommand
|
||||
if [[ $cur == $com ]]; then
|
||||
state="command"
|
||||
coms=(%(commands)s)
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
state="option"
|
||||
if [[ -z $com ]]; then
|
||||
# no command, use global options
|
||||
opts=(%(globalopts)s)
|
||||
fi
|
||||
fi
|
||||
case $state in
|
||||
(command)
|
||||
_describe 'command' coms
|
||||
;;
|
||||
(option)
|
||||
if [[ -n $sub ]]; then
|
||||
optkey="$com $sub"
|
||||
else
|
||||
optkey="$com"
|
||||
fi
|
||||
case $optkey in
|
||||
%(case_options)s
|
||||
esac
|
||||
_describe 'option' opts
|
||||
;;
|
||||
*)
|
||||
if [[ -z $sub ]]; then
|
||||
# if we're completing a command with subcommands, add them here
|
||||
case "$com" in
|
||||
%(case_subcommands)s
|
||||
esac
|
||||
_describe 'subcommand' subs
|
||||
fi
|
||||
# also fallback to file completion
|
||||
_arguments '*:file:_files'
|
||||
esac
|
||||
}
|
||||
_mach_complete "$@"
|
||||
compdef _mach_complete mach
|
|
@ -400,6 +400,7 @@ To see more help for a specific command, run:
|
|||
context = ContextWrapper(context, self.populate_context_handler)
|
||||
|
||||
parser = self.get_argument_parser(context)
|
||||
context.global_parser = parser
|
||||
|
||||
if not len(argv):
|
||||
# We don't register the usage until here because if it is globally
|
||||
|
|
|
@ -2,28 +2,34 @@
|
|||
# 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, unicode_literals
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from mozunit import main
|
||||
|
||||
from buildconfig import topsrcdir
|
||||
import mach
|
||||
from mach.test.common import TestBase
|
||||
|
||||
ALL_COMMANDS = [
|
||||
'cmd_bar',
|
||||
'cmd_foo',
|
||||
'cmd_foobar',
|
||||
'mach-commands',
|
||||
'mach-completion',
|
||||
'mach-debug-commands',
|
||||
]
|
||||
|
||||
class TestCommands(TestBase):
|
||||
all_commands = [
|
||||
'cmd_bar',
|
||||
'cmd_foo',
|
||||
'cmd_foobar',
|
||||
'mach-commands',
|
||||
'mach-completion',
|
||||
'mach-debug-commands',
|
||||
]
|
||||
|
||||
def _run_mach(self, args):
|
||||
@pytest.fixture
|
||||
def run_mach():
|
||||
|
||||
tester = TestBase()
|
||||
|
||||
def inner(args):
|
||||
mach_dir = os.path.dirname(mach.__file__)
|
||||
providers = [
|
||||
'commands.py',
|
||||
|
@ -34,27 +40,44 @@ class TestCommands(TestBase):
|
|||
if key == 'topdir':
|
||||
return topsrcdir
|
||||
|
||||
return TestBase._run_mach(self, args, providers,
|
||||
context_handler=context_handler)
|
||||
return tester._run_mach(args, providers, context_handler=context_handler)
|
||||
|
||||
def format(self, targets):
|
||||
return "\n".join(targets) + "\n"
|
||||
return inner
|
||||
|
||||
def test_mach_completion(self):
|
||||
result, stdout, stderr = self._run_mach(['mach-completion'])
|
||||
assert result == 0
|
||||
assert stdout == self.format(self.all_commands)
|
||||
|
||||
result, stdout, stderr = self._run_mach(['mach-completion', 'cmd_f'])
|
||||
assert result == 0
|
||||
# While it seems like this should return only commands that have
|
||||
# 'cmd_f' as a prefix, the completion script will handle this case
|
||||
# properly.
|
||||
assert stdout == self.format(self.all_commands)
|
||||
def format(targets):
|
||||
return "\n".join(targets) + "\n"
|
||||
|
||||
result, stdout, stderr = self._run_mach(['mach-completion', 'cmd_foo'])
|
||||
assert result == 0
|
||||
assert stdout == self.format(['help', '--arg'])
|
||||
|
||||
def test_mach_completion(run_mach):
|
||||
result, stdout, stderr = run_mach(['mach-completion'])
|
||||
assert result == 0
|
||||
assert stdout == format(ALL_COMMANDS)
|
||||
|
||||
result, stdout, stderr = run_mach(['mach-completion', 'cmd_f'])
|
||||
assert result == 0
|
||||
# While it seems like this should return only commands that have
|
||||
# 'cmd_f' as a prefix, the completion script will handle this case
|
||||
# properly.
|
||||
assert stdout == format(ALL_COMMANDS)
|
||||
|
||||
result, stdout, stderr = run_mach(['mach-completion', 'cmd_foo'])
|
||||
assert result == 0
|
||||
assert stdout == format(['help', '--arg'])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("shell", ("bash", "fish", "zsh"))
|
||||
def test_generate_mach_completion_script(run_mach, shell):
|
||||
rv, out, err = run_mach(["mach-completion", shell])
|
||||
print(out)
|
||||
print(err, file=sys.stderr)
|
||||
assert rv == 0
|
||||
assert err == ""
|
||||
|
||||
assert "cmd_foo" in out
|
||||
assert "arg" in out
|
||||
assert "cmd_foobar" in out
|
||||
assert "cmd_bar" in out
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Загрузка…
Ссылка в новой задаче