mirror of
https://fuchsia.googlesource.com/third_party/pigweed.googlesource.com/pigweed/pigweed
synced 2024-07-12 01:23:34 +00:00
pw_cli: Fish shell completion
For the 'pw' and 'pw build' commands. This mirrors current support for bash and zsh. Change-Id: I187c0af27b64d67745639b8ff3f2768de4934a63 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/213734 Reviewed-by: Chad Norvell <chadnorvell@google.com> Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com> Commit-Queue: Anthony DiGirolamo <tonymd@google.com> Pigweed-Auto-Submit: Anthony DiGirolamo <tonymd@google.com>
This commit is contained in:
parent
f4da9803f0
commit
3fa03cadd9
|
@ -83,7 +83,7 @@ set -e _pw_sourced
|
|||
set -e _PW_BOOTSTRAP_PATH
|
||||
set -e SETUP_SH
|
||||
|
||||
# TODO(tonymd): Source fish pw_cli shell completion.
|
||||
source $PW_ROOT/pw_cli/py/pw_cli/shell_completion/pw.fish
|
||||
|
||||
pw_cleanup
|
||||
|
||||
|
|
|
@ -638,7 +638,7 @@ def main(
|
|||
force_pw_watch: bool = False,
|
||||
) -> int:
|
||||
"""Build upstream Pigweed presubmit steps."""
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-locals,too-many-branches
|
||||
parser = get_parser(presubmit_programs, build_recipes)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -665,14 +665,18 @@ def main(
|
|||
else:
|
||||
charset = ASCII_CHARSET
|
||||
|
||||
if build_recipes and args.tab_complete_recipe is not None:
|
||||
_tab_complete_recipe(build_recipes, text=args.tab_complete_recipe)
|
||||
if args.tab_complete_recipe is not None:
|
||||
if build_recipes:
|
||||
_tab_complete_recipe(build_recipes, text=args.tab_complete_recipe)
|
||||
# Must exit if there are no build_recipes.
|
||||
return 0
|
||||
|
||||
if presubmit_programs and args.tab_complete_presubmit_step is not None:
|
||||
_tab_complete_presubmit_step(
|
||||
presubmit_programs, text=args.tab_complete_presubmit_step
|
||||
)
|
||||
if args.tab_complete_presubmit_step is not None:
|
||||
if presubmit_programs:
|
||||
_tab_complete_presubmit_step(
|
||||
presubmit_programs, text=args.tab_complete_presubmit_step
|
||||
)
|
||||
# Must exit if there are no presubmit_programs.
|
||||
return 0
|
||||
|
||||
# List valid steps + recipes.
|
||||
|
|
|
@ -43,6 +43,7 @@ pw_python_package("py") {
|
|||
"pw_cli/pw_command_plugins.py",
|
||||
"pw_cli/requires.py",
|
||||
"pw_cli/shell_completion/__init__.py",
|
||||
"pw_cli/shell_completion/fish/__init__.py",
|
||||
"pw_cli/shell_completion/zsh/__init__.py",
|
||||
"pw_cli/shell_completion/zsh/pw/__init__.py",
|
||||
"pw_cli/shell_completion/zsh/pw_build/__init__.py",
|
||||
|
@ -63,7 +64,9 @@ pw_python_package("py") {
|
|||
mypy_ini = "$dir_pigweed/.mypy.ini"
|
||||
inputs = [
|
||||
"pw_cli/shell_completion/common.bash",
|
||||
"pw_cli/shell_completion/fish/pw.fish",
|
||||
"pw_cli/shell_completion/pw.bash",
|
||||
"pw_cli/shell_completion/pw.fish",
|
||||
"pw_cli/shell_completion/pw.zsh",
|
||||
"pw_cli/shell_completion/pw_build.bash",
|
||||
"pw_cli/shell_completion/pw_build.zsh",
|
||||
|
|
|
@ -31,10 +31,15 @@ def main() -> NoReturn:
|
|||
|
||||
pw_cli.log.install(level=args.loglevel, debug_log=args.debug_log)
|
||||
|
||||
# Print the banner unless --no-banner or --tab-complete-command is provided.
|
||||
# Note: args.tab_complete_command may be the empty string '' so check for
|
||||
# None instead.
|
||||
if args.banner and args.tab_complete_command is None:
|
||||
# Print the banner unless --no-banner or a tab completion arg is
|
||||
# present.
|
||||
# Note: args.tab_complete_{command,option} may be the empty string
|
||||
# '' so check for None instead.
|
||||
if (
|
||||
args.banner
|
||||
and args.tab_complete_option is None
|
||||
and args.tab_complete_command is None
|
||||
):
|
||||
arguments.print_banner()
|
||||
|
||||
_LOG.debug('Executing the pw command from %s', args.directory)
|
||||
|
@ -53,7 +58,7 @@ def main() -> NoReturn:
|
|||
if args.tab_complete_command is not None:
|
||||
for name, plugin in sorted(pw_command_plugins.plugin_registry.items()):
|
||||
if name.startswith(args.tab_complete_command):
|
||||
if args.tab_complete_format == 'zsh':
|
||||
if args.tab_complete_format in ('fish', 'zsh'):
|
||||
print(':'.join([name, plugin.help()]))
|
||||
else:
|
||||
print(name)
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
"""Defines arguments for the pw command."""
|
||||
|
||||
import argparse
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import cached_property
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import NoReturn
|
||||
from typing import Any, NoReturn
|
||||
|
||||
from pw_cli import argument_types, plugins
|
||||
from pw_cli.branding import banner
|
||||
|
@ -41,39 +42,92 @@ class ShellCompletionFormat(Enum):
|
|||
"""Supported shell tab completion modes."""
|
||||
|
||||
BASH = 'bash'
|
||||
FISH = 'fish'
|
||||
ZSH = 'zsh'
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ShellCompletion:
|
||||
option_strings: list[str] = field(default_factory=list)
|
||||
help: str | None = None
|
||||
choices: list[str] | None = None
|
||||
flag: bool = True
|
||||
"""Transforms argparse actions into bash, fish, zsh shell completions."""
|
||||
|
||||
def bash_completion(self, text: str) -> list[str]:
|
||||
action: argparse.Action
|
||||
parser: argparse.ArgumentParser
|
||||
|
||||
@property
|
||||
def option_strings(self) -> list[str]:
|
||||
return list(self.action.option_strings)
|
||||
|
||||
@cached_property
|
||||
def help(self) -> str:
|
||||
return self.parser._get_formatter()._expand_help( # pylint: disable=protected-access
|
||||
self.action
|
||||
)
|
||||
|
||||
@property
|
||||
def choices(self) -> list[str]:
|
||||
return list(self.action.choices) if self.action.choices else []
|
||||
|
||||
@property
|
||||
def flag(self) -> bool:
|
||||
return self.action.nargs == 0
|
||||
|
||||
@property
|
||||
def default(self) -> Any:
|
||||
return self.action.default
|
||||
|
||||
def bash_option(self, text: str) -> list[str]:
|
||||
result: list[str] = []
|
||||
for option_str in self.option_strings:
|
||||
if option_str.startswith(text):
|
||||
result.append(option_str)
|
||||
return result
|
||||
|
||||
def zsh_completion(self, text: str) -> list[str]:
|
||||
def zsh_option(self, text: str) -> list[str]:
|
||||
result: list[str] = []
|
||||
for option_str in self.option_strings:
|
||||
if option_str.startswith(text):
|
||||
short_and_long_opts = ' '.join(self.option_strings)
|
||||
# '(-h --help)-h[Display help message and exit]'
|
||||
# '(-h --help)--help[Display help message and exit]'
|
||||
help_text = self.help if self.help else ''
|
||||
state_str = ''
|
||||
if not self.flag:
|
||||
state_str = ': :->' + option_str
|
||||
if not option_str.startswith(text):
|
||||
continue
|
||||
|
||||
result.append(
|
||||
f'({short_and_long_opts}){option_str}[{help_text}]'
|
||||
f'{state_str}'
|
||||
)
|
||||
short_and_long_opts = ' '.join(self.option_strings)
|
||||
# '(-h --help)-h[Display help message and exit]'
|
||||
# '(-h --help)--help[Display help message and exit]'
|
||||
help_text = self.help if self.help else ''
|
||||
|
||||
state_str = ''
|
||||
if not self.flag:
|
||||
state_str = ': :->' + option_str
|
||||
|
||||
result.append(
|
||||
f'({short_and_long_opts}){option_str}[{help_text}]'
|
||||
f'{state_str}'
|
||||
)
|
||||
return result
|
||||
|
||||
def fish_option(self, text: str) -> list[str]:
|
||||
result: list[str] = []
|
||||
for option_str in self.option_strings:
|
||||
if not option_str.startswith(text):
|
||||
continue
|
||||
|
||||
output: list[str] = []
|
||||
if option_str.startswith('--'):
|
||||
output.append(f'--long-option\t{option_str.lstrip("-")}')
|
||||
elif option_str.startswith('-'):
|
||||
output.append(f'--short-option\t{option_str.lstrip("-")}')
|
||||
|
||||
if self.choices:
|
||||
choice_str = " ".join(self.choices)
|
||||
output.append('--exclusive')
|
||||
output.append('--arguments')
|
||||
output.append(f'(string split " " "{choice_str}")')
|
||||
elif self.action.type == Path:
|
||||
output.append('--require-parameter')
|
||||
output.append('--force-files')
|
||||
|
||||
if self.help:
|
||||
output.append(f'--description\t"{self.help}"')
|
||||
|
||||
result.append('\t'.join(output))
|
||||
return result
|
||||
|
||||
|
||||
|
@ -81,12 +135,7 @@ def get_options_and_help(
|
|||
parser: argparse.ArgumentParser,
|
||||
) -> list[ShellCompletion]:
|
||||
return list(
|
||||
ShellCompletion(
|
||||
option_strings=list(action.option_strings),
|
||||
help=action.help,
|
||||
choices=list(action.choices) if action.choices else [],
|
||||
flag=action.nargs == 0,
|
||||
)
|
||||
ShellCompletion(action=action, parser=parser)
|
||||
for action in parser._actions # pylint: disable=protected-access
|
||||
)
|
||||
|
||||
|
@ -99,9 +148,11 @@ def print_completions_for_option(
|
|||
matched_lines: list[str] = []
|
||||
for completion in get_options_and_help(parser):
|
||||
if tab_completion_format == ShellCompletionFormat.ZSH.value:
|
||||
matched_lines.extend(completion.zsh_completion(text))
|
||||
matched_lines.extend(completion.zsh_option(text))
|
||||
if tab_completion_format == ShellCompletionFormat.FISH.value:
|
||||
matched_lines.extend(completion.fish_option(text))
|
||||
else:
|
||||
matched_lines.extend(completion.bash_completion(text))
|
||||
matched_lines.extend(completion.bash_option(text))
|
||||
|
||||
for line in matched_lines:
|
||||
print(line)
|
||||
|
@ -179,6 +230,7 @@ def arg_parser() -> argparse.ArgumentParser:
|
|||
)
|
||||
argparser.add_argument(
|
||||
'--debug-log',
|
||||
type=Path,
|
||||
help=(
|
||||
'Additionally log to this file at debug level; does not affect '
|
||||
'terminal output'
|
||||
|
|
0
pw_cli/py/pw_cli/shell_completion/fish/__init__.py
Normal file
0
pw_cli/py/pw_cli/shell_completion/fish/__init__.py
Normal file
82
pw_cli/py/pw_cli/shell_completion/fish/pw.fish
Normal file
82
pw_cli/py/pw_cli/shell_completion/fish/pw.fish
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Copyright 2024 The Pigweed Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy of
|
||||
# the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
|
||||
# Only generate completions if $PW_ROOT is set
|
||||
if test -z "$PW_ROOT"
|
||||
exit
|
||||
end
|
||||
|
||||
set status_message_length 0
|
||||
function print_status_message
|
||||
printf '\033[s\033[1B\033[0G'
|
||||
set -l status_message "Regenerating completions."
|
||||
set status_message_length (math 1 + (string length --visible $status_message))
|
||||
printf $status_message
|
||||
printf '\033[u'
|
||||
end
|
||||
|
||||
function print_status_indicator
|
||||
printf '\033[s\033[1B\033['
|
||||
printf $status_message_length
|
||||
printf G
|
||||
printf '.'
|
||||
printf '\033[u'
|
||||
set status_message_length (math $status_message_length + 1)
|
||||
end
|
||||
|
||||
print_status_message
|
||||
|
||||
complete -c pw --no-files
|
||||
|
||||
# pw completion
|
||||
set --local --append _pw_subcommands (pw --no-banner --tab-complete-format=fish --tab-complete-command "")
|
||||
print_status_indicator
|
||||
set --local --append _pw_options (pw --no-banner --tab-complete-format=fish --tab-complete-option "")
|
||||
print_status_indicator
|
||||
|
||||
# pw short and long options
|
||||
for option in $_pw_options
|
||||
set -l complete_args (string split \t -- "$option")
|
||||
|
||||
complete --command pw --condition "__fish_use_subcommand; and not __fish_seen_subcommand_from $_pw_subcommand_names" $complete_args
|
||||
end
|
||||
|
||||
# pw subcommands
|
||||
for subcommand in $_pw_subcommands
|
||||
set --local command_and_help (string split --max 1 ":" "$subcommand")
|
||||
set --local command $command_and_help[1]
|
||||
set --local help $command_and_help[2]
|
||||
set --append _pw_subcommand_names $command
|
||||
|
||||
complete --command pw --condition __fish_use_subcommand --arguments "$command" --description "$help"
|
||||
end
|
||||
|
||||
# pw build completion
|
||||
if contains build $_pw_subcommand_names
|
||||
set --local --append _pw_build_options (pw --no-banner build --tab-complete-format=fish --tab-complete-option "")
|
||||
print_status_indicator
|
||||
set --local --append _pw_build_recipes (pw --no-banner build --tab-complete-format=fish --tab-complete-recipe "")
|
||||
print_status_indicator
|
||||
set --local --append _pw_build_presubmit_steps (pw --no-banner build --tab-complete-format=fish --tab-complete-presubmit-step "")
|
||||
print_status_indicator
|
||||
|
||||
complete --command pw --condition "__fish_seen_subcommand_from build" -s r -l recipe -x -a "$_pw_build_recipes"
|
||||
complete --command pw --condition "__fish_seen_subcommand_from build" -s s -l step -x -a "$_pw_build_presubmit_steps"
|
||||
|
||||
for option in $_pw_build_options
|
||||
set -l complete_args (string split \t -- "$option")
|
||||
|
||||
complete --command pw --condition "__fish_seen_subcommand_from build" $complete_args
|
||||
end
|
||||
end
|
22
pw_cli/py/pw_cli/shell_completion/pw.fish
Normal file
22
pw_cli/py/pw_cli/shell_completion/pw.fish
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2024 The Pigweed Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy of
|
||||
# the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
|
||||
# This script must be tested on fish 3.6.0
|
||||
|
||||
set _pw_fish_completion_path (path resolve (status current-dirname)/fish)
|
||||
|
||||
# Add the fish subdirectory to the completion path
|
||||
if not contains $_pw_fish_completion_path $fish_complete_path
|
||||
set -x --append fish_complete_path $_pw_fish_completion_path
|
||||
end
|
|
@ -34,8 +34,10 @@ pw_cli =
|
|||
py.typed
|
||||
shell_completion/common.bash
|
||||
shell_completion/pw.bash
|
||||
shell_completion/pw.fish
|
||||
shell_completion/pw.zsh
|
||||
shell_completion/pw_build.bash
|
||||
shell_completion/pw_build.zsh
|
||||
shell_completion/fish/pw.fish
|
||||
shell_completion/zsh/pw/_pw
|
||||
shell_completion/zsh/pw_build/_pw_build
|
||||
|
|
Loading…
Reference in New Issue
Block a user