Implement fish completion (#29549)
* commands: provide more information to Command * fish: Add script to generate fish completion * fish: auto prepend `spack` command to avoid duplication * fish: impove completion generation code readability * commands: replace match-case with if-else * fish: fix optspec variable name prefix * fish: fix return value in get_optspecs * fish: fix return value in get_optspecs * format: split long line and trim trailing space * bugfix: replace f-string with interpolation * fish: compete more specs and some fixes * fish: complete hash spec starts with / * fish: improve compatibility * style: trim trailing whitespace * commands: add fish to update args and update tests * commands: add fish completion file * style: merge imports * fish: source completion in setup-env * fish: caret only completes dependencies * fish: make sure we always get same order of output * fish: spack activate only show installed packages that have extensions * fish: update completion file * fish: make dict keys sorted * Blacken code * Fix bad merge * Undo style changes to setup-env.fish * Fix unit tests * Style fix * Compatible with fish_indent * Use list for stability of order * Sort one more place * Sort more things * Sorting unneeded * Unsort * Print difference * Style fix * Help messages need quotes * Arguments to -a must be quoted * Update types * Update types * Update types * Add type hints * Change order of positionals * Always expand help * Remove shared base class * Fix type hints * Remove platform-specific choices * First line of help only * Remove unused maps * Remove suppress * Remove debugging comments * Better quoting * Fish completions have no double dash * Remove test for deleted class * Fix grammar in header file * Use single quotes in most places * Better support for remainder nargs * No magic strings * * and + can also complete multiple * lower case, no period --------- Co-authored-by: Adam J. Stewart <ajstewart426@gmail.com>
This commit is contained in:
parent
66e85ae39a
commit
90ac0ef66e
8 changed files with 3947 additions and 165 deletions
|
@ -9,7 +9,7 @@
|
|||
import re
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from typing import IO, Optional, Sequence, Tuple
|
||||
from typing import IO, Any, Iterable, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
|
||||
class Command:
|
||||
|
@ -25,9 +25,9 @@ def __init__(
|
|||
prog: str,
|
||||
description: Optional[str],
|
||||
usage: str,
|
||||
positionals: Sequence[Tuple[str, str]],
|
||||
optionals: Sequence[Tuple[Sequence[str], str, str]],
|
||||
subcommands: Sequence[Tuple[ArgumentParser, str]],
|
||||
positionals: List[Tuple[str, Optional[Iterable[Any]], Union[int, str, None], str]],
|
||||
optionals: List[Tuple[Sequence[str], List[str], str, Union[int, str, None], str]],
|
||||
subcommands: List[Tuple[ArgumentParser, str, str]],
|
||||
) -> None:
|
||||
"""Initialize a new Command instance.
|
||||
|
||||
|
@ -96,13 +96,30 @@ def parse(self, parser: ArgumentParser, prog: str) -> Command:
|
|||
if action.option_strings:
|
||||
flags = action.option_strings
|
||||
dest_flags = fmt._format_action_invocation(action)
|
||||
help = self._expand_help(action) if action.help else ""
|
||||
help = help.replace("\n", " ")
|
||||
optionals.append((flags, dest_flags, help))
|
||||
nargs = action.nargs
|
||||
help = (
|
||||
self._expand_help(action)
|
||||
if action.help and action.help != argparse.SUPPRESS
|
||||
else ""
|
||||
)
|
||||
help = help.split("\n")[0]
|
||||
|
||||
if action.choices is not None:
|
||||
dest = [str(choice) for choice in action.choices]
|
||||
else:
|
||||
dest = [action.dest]
|
||||
|
||||
optionals.append((flags, dest, dest_flags, nargs, help))
|
||||
elif isinstance(action, argparse._SubParsersAction):
|
||||
for subaction in action._choices_actions:
|
||||
subparser = action._name_parser_map[subaction.dest]
|
||||
subcommands.append((subparser, subaction.dest))
|
||||
help = (
|
||||
self._expand_help(subaction)
|
||||
if subaction.help and action.help != argparse.SUPPRESS
|
||||
else ""
|
||||
)
|
||||
help = help.split("\n")[0]
|
||||
subcommands.append((subparser, subaction.dest, help))
|
||||
|
||||
# Look for aliases of the form 'name (alias, ...)'
|
||||
if self.aliases and isinstance(subaction.metavar, str):
|
||||
|
@ -111,12 +128,22 @@ def parse(self, parser: ArgumentParser, prog: str) -> Command:
|
|||
aliases = match.group(2).split(", ")
|
||||
for alias in aliases:
|
||||
subparser = action._name_parser_map[alias]
|
||||
subcommands.append((subparser, alias))
|
||||
help = (
|
||||
self._expand_help(subaction)
|
||||
if subaction.help and action.help != argparse.SUPPRESS
|
||||
else ""
|
||||
)
|
||||
help = help.split("\n")[0]
|
||||
subcommands.append((subparser, alias, help))
|
||||
else:
|
||||
args = fmt._format_action_invocation(action)
|
||||
help = self._expand_help(action) if action.help else ""
|
||||
help = help.replace("\n", " ")
|
||||
positionals.append((args, help))
|
||||
help = (
|
||||
self._expand_help(action)
|
||||
if action.help and action.help != argparse.SUPPRESS
|
||||
else ""
|
||||
)
|
||||
help = help.split("\n")[0]
|
||||
positionals.append((args, action.choices, action.nargs, help))
|
||||
|
||||
return Command(prog, description, usage, positionals, optionals, subcommands)
|
||||
|
||||
|
@ -146,7 +173,7 @@ def _write(self, parser: ArgumentParser, prog: str, level: int = 0) -> None:
|
|||
cmd = self.parse(parser, prog)
|
||||
self.out.write(self.format(cmd))
|
||||
|
||||
for subparser, prog in cmd.subcommands:
|
||||
for subparser, prog, help in cmd.subcommands:
|
||||
self._write(subparser, prog, level=level + 1)
|
||||
|
||||
def write(self, parser: ArgumentParser) -> None:
|
||||
|
@ -205,13 +232,13 @@ def format(self, cmd: Command) -> str:
|
|||
|
||||
if cmd.positionals:
|
||||
string.write(self.begin_positionals())
|
||||
for args, help in cmd.positionals:
|
||||
for args, choices, nargs, help in cmd.positionals:
|
||||
string.write(self.positional(args, help))
|
||||
string.write(self.end_positionals())
|
||||
|
||||
if cmd.optionals:
|
||||
string.write(self.begin_optionals())
|
||||
for flags, dest_flags, help in cmd.optionals:
|
||||
for flags, dest, dest_flags, nargs, help in cmd.optionals:
|
||||
string.write(self.optional(dest_flags, help))
|
||||
string.write(self.end_optionals())
|
||||
|
||||
|
@ -338,7 +365,7 @@ def end_optionals(self) -> str:
|
|||
"""
|
||||
return ""
|
||||
|
||||
def begin_subcommands(self, subcommands: Sequence[Tuple[ArgumentParser, str]]) -> str:
|
||||
def begin_subcommands(self, subcommands: List[Tuple[ArgumentParser, str, str]]) -> str:
|
||||
"""Table with links to other subcommands.
|
||||
|
||||
Arguments:
|
||||
|
@ -355,114 +382,8 @@ def begin_subcommands(self, subcommands: Sequence[Tuple[ArgumentParser, str]]) -
|
|||
|
||||
"""
|
||||
|
||||
for cmd, _ in subcommands:
|
||||
for cmd, _, _ in subcommands:
|
||||
prog = re.sub(r"^[^ ]* ", "", cmd.prog)
|
||||
string += " * :ref:`{0} <{1}>`\n".format(prog, cmd.prog.replace(" ", "-"))
|
||||
|
||||
return string + "\n"
|
||||
|
||||
|
||||
class ArgparseCompletionWriter(ArgparseWriter):
|
||||
"""Write argparse output as shell programmable tab completion functions."""
|
||||
|
||||
def format(self, cmd: Command) -> str:
|
||||
"""Return the string representation of a single node in the parser tree.
|
||||
|
||||
Args:
|
||||
cmd: Parsed information about a command or subcommand.
|
||||
|
||||
Returns:
|
||||
String representation of this subcommand.
|
||||
"""
|
||||
|
||||
assert cmd.optionals # we should always at least have -h, --help
|
||||
assert not (cmd.positionals and cmd.subcommands) # one or the other
|
||||
|
||||
# We only care about the arguments/flags, not the help messages
|
||||
positionals: Tuple[str, ...] = ()
|
||||
if cmd.positionals:
|
||||
positionals, _ = zip(*cmd.positionals)
|
||||
optionals, _, _ = zip(*cmd.optionals)
|
||||
subcommands: Tuple[str, ...] = ()
|
||||
if cmd.subcommands:
|
||||
_, subcommands = zip(*cmd.subcommands)
|
||||
|
||||
# Flatten lists of lists
|
||||
optionals = [x for xx in optionals for x in xx]
|
||||
|
||||
return (
|
||||
self.start_function(cmd.prog)
|
||||
+ self.body(positionals, optionals, subcommands)
|
||||
+ self.end_function(cmd.prog)
|
||||
)
|
||||
|
||||
def start_function(self, prog: str) -> str:
|
||||
"""Return the syntax needed to begin a function definition.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
|
||||
Returns:
|
||||
Function definition beginning.
|
||||
"""
|
||||
name = prog.replace("-", "_").replace(" ", "_")
|
||||
return "\n_{0}() {{".format(name)
|
||||
|
||||
def end_function(self, prog: str) -> str:
|
||||
"""Return the syntax needed to end a function definition.
|
||||
|
||||
Args:
|
||||
prog: Program name
|
||||
|
||||
Returns:
|
||||
Function definition ending.
|
||||
"""
|
||||
return "}\n"
|
||||
|
||||
def body(
|
||||
self, positionals: Sequence[str], optionals: Sequence[str], subcommands: Sequence[str]
|
||||
) -> str:
|
||||
"""Return the body of the function.
|
||||
|
||||
Args:
|
||||
positionals: List of positional arguments.
|
||||
optionals: List of optional arguments.
|
||||
subcommands: List of subcommand parsers.
|
||||
|
||||
Returns:
|
||||
Function body.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def positionals(self, positionals: Sequence[str]) -> str:
|
||||
"""Return the syntax for reporting positional arguments.
|
||||
|
||||
Args:
|
||||
positionals: List of positional arguments.
|
||||
|
||||
Returns:
|
||||
Syntax for positional arguments.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def optionals(self, optionals: Sequence[str]) -> str:
|
||||
"""Return the syntax for reporting optional flags.
|
||||
|
||||
Args:
|
||||
optionals: List of optional arguments.
|
||||
|
||||
Returns:
|
||||
Syntax for optional flags.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def subcommands(self, subcommands: Sequence[str]) -> str:
|
||||
"""Return the syntax for reporting subcommands.
|
||||
|
||||
Args:
|
||||
subcommands: List of subcommand parsers.
|
||||
|
||||
Returns:
|
||||
Syntax for subcommand parsers
|
||||
"""
|
||||
return ""
|
||||
|
|
|
@ -9,16 +9,11 @@
|
|||
import re
|
||||
import sys
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from typing import IO, Any, Callable, Dict, Sequence, Set
|
||||
from typing import IO, Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.argparsewriter import (
|
||||
ArgparseCompletionWriter,
|
||||
ArgparseRstWriter,
|
||||
ArgparseWriter,
|
||||
Command,
|
||||
)
|
||||
from llnl.util.argparsewriter import ArgparseRstWriter, ArgparseWriter, Command
|
||||
from llnl.util.tty.colify import colify
|
||||
|
||||
import spack.cmd
|
||||
|
@ -43,7 +38,13 @@
|
|||
"format": "bash",
|
||||
"header": os.path.join(spack.paths.share_path, "bash", "spack-completion.in"),
|
||||
"update": os.path.join(spack.paths.share_path, "spack-completion.bash"),
|
||||
}
|
||||
},
|
||||
"fish": {
|
||||
"aliases": True,
|
||||
"format": "fish",
|
||||
"header": os.path.join(spack.paths.share_path, "fish", "spack-completion.in"),
|
||||
"update": os.path.join(spack.paths.share_path, "spack-completion.fish"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -178,9 +179,63 @@ def format(self, cmd: Command) -> str:
|
|||
}
|
||||
|
||||
|
||||
class BashCompletionWriter(ArgparseCompletionWriter):
|
||||
class BashCompletionWriter(ArgparseWriter):
|
||||
"""Write argparse output as bash programmable tab completion."""
|
||||
|
||||
def format(self, cmd: Command) -> str:
|
||||
"""Return the string representation of a single node in the parser tree.
|
||||
|
||||
Args:
|
||||
cmd: Parsed information about a command or subcommand.
|
||||
|
||||
Returns:
|
||||
String representation of this subcommand.
|
||||
"""
|
||||
|
||||
assert cmd.optionals # we should always at least have -h, --help
|
||||
assert not (cmd.positionals and cmd.subcommands) # one or the other
|
||||
|
||||
# We only care about the arguments/flags, not the help messages
|
||||
positionals: Tuple[str, ...] = ()
|
||||
if cmd.positionals:
|
||||
positionals, _, _, _ = zip(*cmd.positionals)
|
||||
optionals, _, _, _, _ = zip(*cmd.optionals)
|
||||
subcommands: Tuple[str, ...] = ()
|
||||
if cmd.subcommands:
|
||||
_, subcommands, _ = zip(*cmd.subcommands)
|
||||
|
||||
# Flatten lists of lists
|
||||
optionals = [x for xx in optionals for x in xx]
|
||||
|
||||
return (
|
||||
self.start_function(cmd.prog)
|
||||
+ self.body(positionals, optionals, subcommands)
|
||||
+ self.end_function(cmd.prog)
|
||||
)
|
||||
|
||||
def start_function(self, prog: str) -> str:
|
||||
"""Return the syntax needed to begin a function definition.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
|
||||
Returns:
|
||||
Function definition beginning.
|
||||
"""
|
||||
name = prog.replace("-", "_").replace(" ", "_")
|
||||
return "\n_{0}() {{".format(name)
|
||||
|
||||
def end_function(self, prog: str) -> str:
|
||||
"""Return the syntax needed to end a function definition.
|
||||
|
||||
Args:
|
||||
prog: Program name
|
||||
|
||||
Returns:
|
||||
Function definition ending.
|
||||
"""
|
||||
return "}\n"
|
||||
|
||||
def body(
|
||||
self, positionals: Sequence[str], optionals: Sequence[str], subcommands: Sequence[str]
|
||||
) -> str:
|
||||
|
@ -264,6 +319,396 @@ def subcommands(self, subcommands: Sequence[str]) -> str:
|
|||
return 'SPACK_COMPREPLY="{0}"'.format(" ".join(subcommands))
|
||||
|
||||
|
||||
# Map argument destination names to their complete commands
|
||||
# Earlier items in the list have higher precedence
|
||||
_dest_to_fish_complete = {
|
||||
("activate", "view"): "-f -a '(__fish_complete_directories)'",
|
||||
("bootstrap root", "path"): "-f -a '(__fish_complete_directories)'",
|
||||
("mirror add", "mirror"): "-f",
|
||||
("repo add", "path"): "-f -a '(__fish_complete_directories)'",
|
||||
("test find", "filter"): "-f -a '(__fish_spack_tests)'",
|
||||
("bootstrap", "name"): "-f -a '(__fish_spack_bootstrap_names)'",
|
||||
("buildcache create", "key"): "-f -a '(__fish_spack_gpg_keys)'",
|
||||
("build-env", r"spec \[--\].*"): "-f -a '(__fish_spack_build_env_spec)'",
|
||||
("checksum", "package"): "-f -a '(__fish_spack_packages)'",
|
||||
(
|
||||
"checksum",
|
||||
"versions",
|
||||
): "-f -a '(__fish_spack_package_versions $__fish_spack_argparse_argv[1])'",
|
||||
("config", "path"): "-f -a '(__fish_spack_colon_path)'",
|
||||
("config", "section"): "-f -a '(__fish_spack_config_sections)'",
|
||||
("develop", "specs?"): "-f -k -a '(__fish_spack_specs_or_id)'",
|
||||
("diff", "specs?"): "-f -a '(__fish_spack_installed_specs)'",
|
||||
("gpg sign", "output"): "-f -a '(__fish_complete_directories)'",
|
||||
("gpg", "keys?"): "-f -a '(__fish_spack_gpg_keys)'",
|
||||
("graph", "specs?"): "-f -k -a '(__fish_spack_specs_or_id)'",
|
||||
("help", "help_command"): "-f -a '(__fish_spack_commands)'",
|
||||
("list", "filter"): "-f -a '(__fish_spack_packages)'",
|
||||
("mirror", "mirror"): "-f -a '(__fish_spack_mirrors)'",
|
||||
("pkg", "package"): "-f -a '(__fish_spack_pkg_packages)'",
|
||||
("remove", "specs?"): "-f -a '(__fish_spack_installed_specs)'",
|
||||
("repo", "namespace_or_path"): "$__fish_spack_force_files -a '(__fish_spack_repos)'",
|
||||
("restage", "specs?"): "-f -k -a '(__fish_spack_specs_or_id)'",
|
||||
("rm", "specs?"): "-f -a '(__fish_spack_installed_specs)'",
|
||||
("solve", "specs?"): "-f -k -a '(__fish_spack_specs_or_id)'",
|
||||
("spec", "specs?"): "-f -k -a '(__fish_spack_specs_or_id)'",
|
||||
("stage", "specs?"): "-f -k -a '(__fish_spack_specs_or_id)'",
|
||||
("test-env", r"spec \[--\].*"): "-f -a '(__fish_spack_build_env_spec)'",
|
||||
("test", r"\[?name.*"): "-f -a '(__fish_spack_tests)'",
|
||||
("undevelop", "specs?"): "-f -k -a '(__fish_spack_specs_or_id)'",
|
||||
("verify", "specs_or_files"): "$__fish_spack_force_files -a '(__fish_spack_installed_specs)'",
|
||||
("view", "path"): "-f -a '(__fish_complete_directories)'",
|
||||
("", "comment"): "-f",
|
||||
("", "compiler_spec"): "-f -a '(__fish_spack_installed_compilers)'",
|
||||
("", "config_scopes"): "-f -a '(__fish_complete_directories)'",
|
||||
("", "extendable"): "-f -a '(__fish_spack_extensions)'",
|
||||
("", "installed_specs?"): "-f -a '(__fish_spack_installed_specs)'",
|
||||
("", "job_url"): "-f",
|
||||
("", "location_env"): "-f -a '(__fish_complete_directories)'",
|
||||
("", "pytest_args"): "-f -a '(__fish_spack_unit_tests)'",
|
||||
("", "package_or_file"): "$__fish_spack_force_files -a '(__fish_spack_packages)'",
|
||||
("", "package_or_user"): "-f -a '(__fish_spack_packages)'",
|
||||
("", "package"): "-f -a '(__fish_spack_packages)'",
|
||||
("", "PKG"): "-f -a '(__fish_spack_packages)'",
|
||||
("", "prefix"): "-f -a '(__fish_complete_directories)'",
|
||||
("", r"rev\d?"): "-f -a '(__fish_spack_git_rev)'",
|
||||
("", "specs?"): "-f -k -a '(__fish_spack_specs)'",
|
||||
("", "tags?"): "-f -a '(__fish_spack_tags)'",
|
||||
("", "virtual_package"): "-f -a '(__fish_spack_providers)'",
|
||||
("", "working_dir"): "-f -a '(__fish_complete_directories)'",
|
||||
("", r"(\w*_)?env"): "-f -a '(__fish_spack_environments)'",
|
||||
("", r"(\w*_)?dir(ectory)?"): "-f -a '(__fish_spack_environments)'",
|
||||
("", r"(\w*_)?mirror_name"): "-f -a '(__fish_spack_mirrors)'",
|
||||
}
|
||||
|
||||
|
||||
def _fish_dest_get_complete(prog: str, dest: str) -> Optional[str]:
|
||||
"""Map from subcommand to autocompletion argument.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
dest: Destination.
|
||||
|
||||
Returns:
|
||||
Autocompletion argument.
|
||||
"""
|
||||
s = prog.split(None, 1)
|
||||
subcmd = s[1] if len(s) == 2 else ""
|
||||
|
||||
for (prog_key, pos_key), value in _dest_to_fish_complete.items():
|
||||
if subcmd.startswith(prog_key) and re.match("^" + pos_key + "$", dest):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
class FishCompletionWriter(ArgparseWriter):
|
||||
"""Write argparse output as bash programmable tab completion."""
|
||||
|
||||
def format(self, cmd: Command) -> str:
|
||||
"""Return the string representation of a single node in the parser tree.
|
||||
|
||||
Args:
|
||||
cmd: Parsed information about a command or subcommand.
|
||||
|
||||
Returns:
|
||||
String representation of a node.
|
||||
"""
|
||||
assert cmd.optionals # we should always at least have -h, --help
|
||||
assert not (cmd.positionals and cmd.subcommands) # one or the other
|
||||
|
||||
# We also need help messages and how arguments are used
|
||||
# So we pass everything to completion writer
|
||||
positionals = cmd.positionals
|
||||
optionals = cmd.optionals
|
||||
subcommands = cmd.subcommands
|
||||
|
||||
return (
|
||||
self.prog_comment(cmd.prog)
|
||||
+ self.optspecs(cmd.prog, optionals)
|
||||
+ self.complete(cmd.prog, positionals, optionals, subcommands)
|
||||
)
|
||||
|
||||
def _quote(self, string: str) -> str:
|
||||
"""Quote string and escape special characters if necessary.
|
||||
|
||||
Args:
|
||||
string: Input string.
|
||||
|
||||
Returns:
|
||||
Quoted string.
|
||||
"""
|
||||
# Goal here is to match fish_indent behavior
|
||||
|
||||
# Strings without spaces (or other special characters) do not need to be escaped
|
||||
if not any([sub in string for sub in [" ", "'", '"']]):
|
||||
return string
|
||||
|
||||
string = string.replace("'", r"\'")
|
||||
return f"'{string}'"
|
||||
|
||||
def optspecs(
|
||||
self,
|
||||
prog: str,
|
||||
optionals: List[Tuple[Sequence[str], List[str], str, Union[int, str, None], str]],
|
||||
) -> str:
|
||||
"""Read the optionals and return the command to set optspec.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
optionals: List of optional arguments.
|
||||
|
||||
Returns:
|
||||
Command to set optspec variable.
|
||||
"""
|
||||
# Variables of optspecs
|
||||
optspec_var = "__fish_spack_optspecs_" + prog.replace(" ", "_").replace("-", "_")
|
||||
|
||||
if optionals is None:
|
||||
return "set -g %s\n" % optspec_var
|
||||
|
||||
# Build optspec by iterating over options
|
||||
args = []
|
||||
|
||||
for flags, dest, _, nargs, _ in optionals:
|
||||
if len(flags) == 0:
|
||||
continue
|
||||
|
||||
required = ""
|
||||
|
||||
# Because nargs '?' is treated differently in fish, we treat it as required.
|
||||
# Because multi-argument options are not supported, we treat it like one argument.
|
||||
required = "="
|
||||
if nargs == 0:
|
||||
required = ""
|
||||
|
||||
# Pair short options with long options
|
||||
|
||||
# We need to do this because fish doesn't support multiple short
|
||||
# or long options.
|
||||
# However, since we are paring options only, this is fine
|
||||
|
||||
short = [f[1:] for f in flags if f.startswith("-") and len(f) == 2]
|
||||
long = [f[2:] for f in flags if f.startswith("--")]
|
||||
|
||||
while len(short) > 0 and len(long) > 0:
|
||||
arg = "%s/%s%s" % (short.pop(), long.pop(), required)
|
||||
while len(short) > 0:
|
||||
arg = "%s/%s" % (short.pop(), required)
|
||||
while len(long) > 0:
|
||||
arg = "%s%s" % (long.pop(), required)
|
||||
|
||||
args.append(arg)
|
||||
|
||||
# Even if there is no option, we still set variable.
|
||||
# In fish such variable is an empty array, we use it to
|
||||
# indicate that such subcommand exists.
|
||||
args = " ".join(args)
|
||||
|
||||
return "set -g %s %s\n" % (optspec_var, args)
|
||||
|
||||
@staticmethod
|
||||
def complete_head(
|
||||
prog: str, index: Optional[int] = None, nargs: Optional[Union[int, str]] = None
|
||||
) -> str:
|
||||
"""Return the head of the completion command.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
index: Index of positional argument.
|
||||
nargs: Number of arguments.
|
||||
|
||||
Returns:
|
||||
Head of the completion command.
|
||||
"""
|
||||
# Split command and subcommand
|
||||
s = prog.split(None, 1)
|
||||
subcmd = s[1] if len(s) == 2 else ""
|
||||
|
||||
if index is None:
|
||||
return "complete -c %s -n '__fish_spack_using_command %s'" % (s[0], subcmd)
|
||||
elif nargs in [argparse.ZERO_OR_MORE, argparse.ONE_OR_MORE, argparse.REMAINDER]:
|
||||
head = "complete -c %s -n '__fish_spack_using_command_pos_remainder %d %s'"
|
||||
else:
|
||||
head = "complete -c %s -n '__fish_spack_using_command_pos %d %s'"
|
||||
return head % (s[0], index, subcmd)
|
||||
|
||||
def complete(
|
||||
self,
|
||||
prog: str,
|
||||
positionals: List[Tuple[str, Optional[Iterable[Any]], Union[int, str, None], str]],
|
||||
optionals: List[Tuple[Sequence[str], List[str], str, Union[int, str, None], str]],
|
||||
subcommands: List[Tuple[ArgumentParser, str, str]],
|
||||
) -> str:
|
||||
"""Return all the completion commands.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
positionals: List of positional arguments.
|
||||
optionals: List of optional arguments.
|
||||
subcommands: List of subcommand parsers.
|
||||
|
||||
Returns:
|
||||
Completion command.
|
||||
"""
|
||||
commands = []
|
||||
|
||||
if positionals:
|
||||
commands.append(self.positionals(prog, positionals))
|
||||
|
||||
if subcommands:
|
||||
commands.append(self.subcommands(prog, subcommands))
|
||||
|
||||
if optionals:
|
||||
commands.append(self.optionals(prog, optionals))
|
||||
|
||||
return "".join(commands)
|
||||
|
||||
def positionals(
|
||||
self,
|
||||
prog: str,
|
||||
positionals: List[Tuple[str, Optional[Iterable[Any]], Union[int, str, None], str]],
|
||||
) -> str:
|
||||
"""Return the completion for positional arguments.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
positionals: List of positional arguments.
|
||||
|
||||
Returns:
|
||||
Completion command.
|
||||
"""
|
||||
commands = []
|
||||
|
||||
for idx, (args, choices, nargs, help) in enumerate(positionals):
|
||||
# Make sure we always get same order of output
|
||||
if isinstance(choices, dict):
|
||||
choices = sorted(choices.keys())
|
||||
elif isinstance(choices, (set, frozenset)):
|
||||
choices = sorted(choices)
|
||||
|
||||
# Remove platform-specific choices to avoid hard-coding the platform.
|
||||
if choices is not None:
|
||||
valid_choices = []
|
||||
for choice in choices:
|
||||
if spack.platforms.host().name not in choice:
|
||||
valid_choices.append(choice)
|
||||
choices = valid_choices
|
||||
|
||||
head = self.complete_head(prog, idx, nargs)
|
||||
|
||||
if choices is not None:
|
||||
# If there are choices, we provide a completion for all possible values.
|
||||
commands.append(head + " -f -a %s" % self._quote(" ".join(choices)))
|
||||
else:
|
||||
# Otherwise, we try to find a predefined completion for it
|
||||
value = _fish_dest_get_complete(prog, args)
|
||||
if value is not None:
|
||||
commands.append(head + " " + value)
|
||||
|
||||
return "\n".join(commands) + "\n"
|
||||
|
||||
def prog_comment(self, prog: str) -> str:
|
||||
"""Return a comment line for the command.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
|
||||
Returns:
|
||||
Comment line.
|
||||
"""
|
||||
return "\n# %s\n" % prog
|
||||
|
||||
def optionals(
|
||||
self,
|
||||
prog: str,
|
||||
optionals: List[Tuple[Sequence[str], List[str], str, Union[int, str, None], str]],
|
||||
) -> str:
|
||||
"""Return the completion for optional arguments.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
optionals: List of optional arguments.
|
||||
|
||||
Returns:
|
||||
Completion command.
|
||||
"""
|
||||
commands = []
|
||||
head = self.complete_head(prog)
|
||||
|
||||
for flags, dest, _, nargs, help in optionals:
|
||||
# Make sure we always get same order of output
|
||||
if isinstance(dest, dict):
|
||||
dest = sorted(dest.keys())
|
||||
elif isinstance(dest, (set, frozenset)):
|
||||
dest = sorted(dest)
|
||||
|
||||
# Remove platform-specific choices to avoid hard-coding the platform.
|
||||
if dest is not None:
|
||||
valid_choices = []
|
||||
for choice in dest:
|
||||
if spack.platforms.host().name not in choice:
|
||||
valid_choices.append(choice)
|
||||
dest = valid_choices
|
||||
|
||||
# To provide description for optionals, and also possible values,
|
||||
# we need to use two split completion command.
|
||||
# Otherwise, each option will have same description.
|
||||
prefix = head
|
||||
|
||||
# Add all flags to the completion
|
||||
for f in flags:
|
||||
if f.startswith("--"):
|
||||
long = f[2:]
|
||||
prefix += " -l %s" % long
|
||||
elif f.startswith("-"):
|
||||
short = f[1:]
|
||||
assert len(short) == 1
|
||||
prefix += " -s %s" % short
|
||||
|
||||
# Check if option require argument.
|
||||
# Currently multi-argument options are not supported, so we treat it like one argument.
|
||||
if nargs != 0:
|
||||
prefix += " -r"
|
||||
|
||||
if dest is not None:
|
||||
# If there are choices, we provide a completion for all possible values.
|
||||
commands.append(prefix + " -f -a %s" % self._quote(" ".join(dest)))
|
||||
else:
|
||||
# Otherwise, we try to find a predefined completion for it
|
||||
value = _fish_dest_get_complete(prog, dest)
|
||||
if value is not None:
|
||||
commands.append(prefix + " " + value)
|
||||
|
||||
if help:
|
||||
commands.append(prefix + " -d %s" % self._quote(help))
|
||||
|
||||
return "\n".join(commands) + "\n"
|
||||
|
||||
def subcommands(self, prog: str, subcommands: List[Tuple[ArgumentParser, str, str]]) -> str:
|
||||
"""Return the completion for subcommands.
|
||||
|
||||
Args:
|
||||
prog: Program name.
|
||||
subcommands: List of subcommand parsers.
|
||||
|
||||
Returns:
|
||||
Completion command.
|
||||
"""
|
||||
commands = []
|
||||
head = self.complete_head(prog, 0)
|
||||
|
||||
for _, subcommand, help in subcommands:
|
||||
command = head + " -f -a %s" % self._quote(subcommand)
|
||||
|
||||
if help is not None and len(help) > 0:
|
||||
help = help.split("\n")[0]
|
||||
command += " -d %s" % self._quote(help)
|
||||
|
||||
commands.append(command)
|
||||
|
||||
return "\n".join(commands) + "\n"
|
||||
|
||||
|
||||
@formatter
|
||||
def subcommands(args: Namespace, out: IO) -> None:
|
||||
"""Hierarchical tree of subcommands.
|
||||
|
@ -371,6 +816,15 @@ def bash(args: Namespace, out: IO) -> None:
|
|||
writer.write(parser)
|
||||
|
||||
|
||||
@formatter
|
||||
def fish(args, out):
|
||||
parser = spack.main.make_argument_parser()
|
||||
spack.main.add_all_commands(parser)
|
||||
|
||||
writer = FishCompletionWriter(parser.prog, out, args.aliases)
|
||||
writer.write(parser)
|
||||
|
||||
|
||||
def prepend_header(args: Namespace, out: IO) -> None:
|
||||
"""Prepend header text at the beginning of a file.
|
||||
|
||||
|
|
|
@ -253,12 +253,12 @@ def _configure_mirror(args):
|
|||
|
||||
|
||||
def mirror_set(args):
|
||||
"""Configure the connection details of a mirror"""
|
||||
"""configure the connection details of a mirror"""
|
||||
_configure_mirror(args)
|
||||
|
||||
|
||||
def mirror_set_url(args):
|
||||
"""Change the URL of a mirror."""
|
||||
"""change the URL of a mirror"""
|
||||
_configure_mirror(args)
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
import spack.cmd
|
||||
import spack.main
|
||||
import spack.paths
|
||||
from spack.cmd.commands import _positional_to_subroutine
|
||||
from spack.cmd.commands import _dest_to_fish_complete, _positional_to_subroutine
|
||||
|
||||
commands = spack.main.SpackCommand("commands", subprocess=True)
|
||||
|
||||
|
@ -185,26 +185,59 @@ def test_bash_completion():
|
|||
assert "_spack_compiler_add() {" in out2
|
||||
|
||||
|
||||
def test_update_completion_arg(tmpdir, monkeypatch):
|
||||
def test_fish_completion():
|
||||
"""Test the fish completion writer."""
|
||||
out1 = commands("--format=fish")
|
||||
|
||||
# Make sure header not included
|
||||
assert "function __fish_spack_argparse" not in out1
|
||||
assert "complete -c spack --erase" not in out1
|
||||
|
||||
# Make sure subcommands appear
|
||||
assert "__fish_spack_using_command remove" in out1
|
||||
assert "__fish_spack_using_command compiler find" in out1
|
||||
|
||||
# Make sure aliases don't appear
|
||||
assert "__fish_spack_using_command rm" not in out1
|
||||
assert "__fish_spack_using_command compiler add" not in out1
|
||||
|
||||
# Make sure options appear
|
||||
assert "-s h -l help" in out1
|
||||
|
||||
# Make sure subcommands are called
|
||||
for complete_cmd in _dest_to_fish_complete.values():
|
||||
assert complete_cmd in out1
|
||||
|
||||
out2 = commands("--aliases", "--format=fish")
|
||||
|
||||
# Make sure aliases appear
|
||||
assert "__fish_spack_using_command rm" in out2
|
||||
assert "__fish_spack_using_command compiler add" in out2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("shell", ["bash", "fish"])
|
||||
def test_update_completion_arg(shell, tmpdir, monkeypatch):
|
||||
"""Test the update completion flag."""
|
||||
|
||||
mock_infile = tmpdir.join("spack-completion.in")
|
||||
mock_bashfile = tmpdir.join("spack-completion.bash")
|
||||
mock_outfile = tmpdir.join(f"spack-completion.{shell}")
|
||||
|
||||
mock_args = {
|
||||
"bash": {
|
||||
shell: {
|
||||
"aliases": True,
|
||||
"format": "bash",
|
||||
"format": shell,
|
||||
"header": str(mock_infile),
|
||||
"update": str(mock_bashfile),
|
||||
"update": str(mock_outfile),
|
||||
}
|
||||
}
|
||||
|
||||
# make a mock completion file missing the --update-completion argument
|
||||
real_args = spack.cmd.commands.update_completion_args
|
||||
shutil.copy(real_args["bash"]["header"], mock_args["bash"]["header"])
|
||||
with open(real_args["bash"]["update"]) as old:
|
||||
shutil.copy(real_args[shell]["header"], mock_args[shell]["header"])
|
||||
with open(real_args[shell]["update"]) as old:
|
||||
old_file = old.read()
|
||||
with open(mock_args["bash"]["update"], "w") as mock:
|
||||
mock.write(old_file.replace("--update-completion", ""))
|
||||
with open(mock_args[shell]["update"], "w") as mock:
|
||||
mock.write(old_file.replace("update-completion", ""))
|
||||
|
||||
monkeypatch.setattr(spack.cmd.commands, "update_completion_args", mock_args)
|
||||
|
||||
|
@ -214,16 +247,17 @@ def test_update_completion_arg(tmpdir, monkeypatch):
|
|||
local_commands("--update-completion", "-a")
|
||||
|
||||
# ensure arg is restored
|
||||
assert "--update-completion" not in mock_bashfile.read()
|
||||
assert "update-completion" not in mock_outfile.read()
|
||||
local_commands("--update-completion")
|
||||
assert "--update-completion" in mock_bashfile.read()
|
||||
assert "update-completion" in mock_outfile.read()
|
||||
|
||||
|
||||
# Note: this test is never expected to be supported on Windows
|
||||
@pytest.mark.skipif(
|
||||
sys.platform == "win32", reason="bash completion script generator fails on windows"
|
||||
sys.platform == "win32", reason="shell completion script generator fails on windows"
|
||||
)
|
||||
def test_updated_completion_scripts(tmpdir):
|
||||
@pytest.mark.parametrize("shell", ["bash", "fish"])
|
||||
def test_updated_completion_scripts(shell, tmpdir):
|
||||
"""Make sure our shell tab completion scripts remain up-to-date."""
|
||||
|
||||
msg = (
|
||||
|
@ -233,12 +267,11 @@ def test_updated_completion_scripts(tmpdir):
|
|||
"and adding the changed files to your pull request."
|
||||
)
|
||||
|
||||
for shell in ["bash"]: # 'zsh', 'fish']:
|
||||
header = os.path.join(spack.paths.share_path, shell, "spack-completion.in")
|
||||
script = "spack-completion.{0}".format(shell)
|
||||
old_script = os.path.join(spack.paths.share_path, script)
|
||||
new_script = str(tmpdir.join(script))
|
||||
header = os.path.join(spack.paths.share_path, shell, "spack-completion.in")
|
||||
script = "spack-completion.{0}".format(shell)
|
||||
old_script = os.path.join(spack.paths.share_path, script)
|
||||
new_script = str(tmpdir.join(script))
|
||||
|
||||
commands("--aliases", "--format", shell, "--header", header, "--update", new_script)
|
||||
commands("--aliases", "--format", shell, "--header", header, "--update", new_script)
|
||||
|
||||
assert filecmp.cmp(old_script, new_script), msg
|
||||
assert filecmp.cmp(old_script, new_script), msg
|
||||
|
|
|
@ -22,13 +22,3 @@
|
|||
def test_format_not_overridden():
|
||||
with pytest.raises(TypeError):
|
||||
aw.ArgparseWriter("spack")
|
||||
|
||||
|
||||
def test_completion_format_not_overridden():
|
||||
writer = aw.ArgparseCompletionWriter("spack")
|
||||
|
||||
assert writer.positionals([]) == ""
|
||||
assert writer.optionals([]) == ""
|
||||
assert writer.subcommands([]) == ""
|
||||
|
||||
writer.write(parser)
|
||||
|
|
347
share/spack/fish/spack-completion.in
Normal file
347
share/spack/fish/spack-completion.in
Normal file
|
@ -0,0 +1,347 @@
|
|||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
# NOTE: spack-completion.fish is auto-generated by:
|
||||
#
|
||||
# $ spack commands --aliases --format=fish
|
||||
# --header=fish/spack-completion.in --update=spack-completion.fish
|
||||
#
|
||||
# Please do not manually modify this file.
|
||||
|
||||
# Check fish version before proceeding
|
||||
set -l fish_version (string split '.' $FISH_VERSION)
|
||||
if test $fish_version[1] -lt 3
|
||||
if test $fish_version[1] -eq 3
|
||||
and test $fish_version[2] -lt 2
|
||||
echo 'Fish version is older than 3.2.0. Some completion features may not work'
|
||||
set -g __fish_spack_force_files
|
||||
else
|
||||
echo 'This script requires fish version 3.0 or later'
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
set -g __fish_spack_force_files -F
|
||||
end
|
||||
|
||||
# The following global variables are used as a cache of `__fish_spack_argparse`
|
||||
|
||||
# Cached command line
|
||||
set -g __fish_spack_argparse_cache_line
|
||||
# Parsed command
|
||||
set -g __fish_spack_argparse_command
|
||||
# Remaining arguments
|
||||
set -g __fish_spack_argparse_argv
|
||||
# Return value
|
||||
set -g __fish_spack_argparse_return
|
||||
|
||||
# Spack command generates an optspec variable $__fish_spack_optspecs_<command>.
|
||||
# We check if this command exists, and echo the optspec variable name.
|
||||
function __fish_spack_get_optspecs -d 'Get optspecs of spack command'
|
||||
# Convert arguments to replace ' ' and '-' by '_'
|
||||
set -l cmd_var (string replace -ra -- '[ -]' '_' $argv | string join '_')
|
||||
# Set optspec variable name
|
||||
set -l optspecs_var __fish_spack_optspecs_$cmd_var
|
||||
# Query if variable $$optspecs_var exists
|
||||
set -q $optspecs_var; or return 1
|
||||
# If it exists, echo all optspecs line by line.
|
||||
# String join returns 1 if no join was performed, so we return 0 in such case.
|
||||
string join \n $$optspecs_var; or return 0
|
||||
end
|
||||
|
||||
# Parse command-line arguments, save results to global variables,
|
||||
# and add found flags to __fish_spack_flag_<flag>.
|
||||
# Returns 1 if help flag is found.
|
||||
function __fish_spack_argparse
|
||||
# Figure out if the current invocation already has a command.
|
||||
set -l args $argv
|
||||
set -l commands
|
||||
|
||||
# Return cached result if arguments haven't changed
|
||||
if test "$__fish_spack_argparse_cache_line" = "$args"
|
||||
return $__fish_spack_argparse_return
|
||||
end
|
||||
|
||||
# Clear all flags found in last run
|
||||
set -g | string replace -rf -- '^(__fish_spack_flag_\w+)(.*?)$' 'set -ge $1' | source
|
||||
|
||||
# Set default return value to 0, indicating success
|
||||
set -g __fish_spack_argparse_return 0
|
||||
# Set command line to current arguments
|
||||
set -g __fish_spack_argparse_cache_line $argv
|
||||
|
||||
# Recursively check arguments for commands
|
||||
while set -q args[1]
|
||||
# Get optspecs of current command
|
||||
set -l optspecs (__fish_spack_get_optspecs $commands $args[1])
|
||||
or break
|
||||
|
||||
# If command exists, shift arguments
|
||||
set -a commands $args[1]
|
||||
set -e args[1]
|
||||
|
||||
# If command has no arguments, continue
|
||||
set -q optspecs[1]; or continue
|
||||
|
||||
# Parse arguments. Set variable _flag_<flag> if flag is found.
|
||||
# We find all these variables and set them to the global variable __fish_spack_flag_<flag>.
|
||||
argparse -i -s $optspecs -- $args 2>/dev/null; or break
|
||||
set -l | string replace -rf -- '^(_flag_.*)$' 'set -g __fish_spack$1' | source
|
||||
|
||||
# Set args to not parsed arguments
|
||||
set args $argv
|
||||
|
||||
# If command has help flag, we don't need to parse more so short circuit
|
||||
if set -q _flag_help
|
||||
set -g __fish_spack_argparse_return 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Set cached variables
|
||||
set -g __fish_spack_argparse_command $commands
|
||||
set -g __fish_spack_argparse_argv $args
|
||||
|
||||
return $__fish_spack_argparse_return
|
||||
end
|
||||
|
||||
# Check if current commandline's command is "spack $argv"
|
||||
function __fish_spack_using_command
|
||||
set -l line (commandline -opc)
|
||||
__fish_spack_argparse $line; or return 1
|
||||
|
||||
set -p argv spack
|
||||
test "$__fish_spack_argparse_command" = "$argv"
|
||||
end
|
||||
|
||||
# Check if current commandline's command is "spack $argv[2..-1]",
|
||||
# and cursor is at $argv[1]-th positional argument
|
||||
function __fish_spack_using_command_pos
|
||||
__fish_spack_using_command $argv[2..-1]
|
||||
or return
|
||||
|
||||
test (count $__fish_spack_argparse_argv) -eq $argv[1]
|
||||
end
|
||||
|
||||
function __fish_spack_using_command_pos_remainder
|
||||
__fish_spack_using_command $argv[2..-1]
|
||||
or return
|
||||
|
||||
test (count $__fish_spack_argparse_argv) -ge $argv[1]
|
||||
end
|
||||
|
||||
# Helper functions for subcommands
|
||||
|
||||
function __fish_spack_bootstrap_names
|
||||
if set -q __fish_spack_flag_scope
|
||||
spack bootstrap list --scope $__fish_spack_flag_scope | string replace -rf -- '^Name: (\w+).*?$' '$1'
|
||||
else
|
||||
spack bootstrap list | string replace -rf -- '^Name: (\w+).*?$' '$1'
|
||||
end
|
||||
end
|
||||
|
||||
# Reference: sudo's fish completion
|
||||
function __fish_spack_build_env_spec
|
||||
set token (commandline -opt)
|
||||
|
||||
set -l index (contains -- -- $__fish_spack_argparse_argv)
|
||||
if set -q index[1]
|
||||
__fish_complete_subcommand --commandline $__fish_spack_argparse_argv[(math $index + 1)..-1]
|
||||
else if set -q __fish_spack_argparse_argv[1]
|
||||
__fish_complete_subcommand --commandline "$__fish_spack_argparse_argv[2..-1] $token"
|
||||
else
|
||||
__fish_spack_specs
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_spack_commands
|
||||
spack commands
|
||||
end
|
||||
|
||||
function __fish_spack_colon_path
|
||||
set token (string split -rm1 ':' (commandline -opt))
|
||||
|
||||
if test (count $token) -lt 2
|
||||
__fish_complete_path $token[1]
|
||||
else
|
||||
__fish_complete_path $token[2] | string replace -r -- '^' "$token[1]:"
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_spack_config_sections
|
||||
if set -q __fish_spack_flag_scope
|
||||
spack config --scope $__fish_spack_flag_scope list | string split ' '
|
||||
else
|
||||
spack config list | string split ' '
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_spack_environments
|
||||
string trim (spack env list)
|
||||
end
|
||||
|
||||
function __fish_spack_extensions
|
||||
# Skip optional flags, or it will be really slow
|
||||
string match -q -- '-*' (commandline -opt)
|
||||
and return
|
||||
|
||||
comm -1 -2 (spack extensions | string trim | psub) (__fish_spack_installed_packages | sort | psub)
|
||||
end
|
||||
|
||||
function __fish_spack_gpg_keys
|
||||
spack gpg list
|
||||
end
|
||||
|
||||
function __fish_spack_installed_compilers
|
||||
spack compilers | grep -v '^[=-]\|^$'
|
||||
end
|
||||
|
||||
function __fish_spack_installed_packages
|
||||
spack find --no-groups --format '{name}' | uniq
|
||||
end
|
||||
|
||||
function __fish_spack_installed_specs
|
||||
# Try match local hash first
|
||||
__fish_spack_installed_specs_id
|
||||
and return
|
||||
|
||||
spack find --no-groups --format '{name}@{version}'
|
||||
end
|
||||
|
||||
function __fish_spack_installed_specs_id
|
||||
set -l token (commandline -opt)
|
||||
string match -q -- '/*' $token
|
||||
or return 1
|
||||
|
||||
spack find --format '/{hash:7}'\t'{name}{@version}'
|
||||
end
|
||||
|
||||
function __fish_spack_git_rev
|
||||
type -q __fish_git_ranges
|
||||
and __fish_git_ranges
|
||||
end
|
||||
|
||||
function __fish_spack_mirrors
|
||||
spack mirror list | awk {'printf ("%s\t%s", $1, $2)'}
|
||||
end
|
||||
|
||||
function __fish_spack_package_versions
|
||||
string trim (spack versions $argv)
|
||||
end
|
||||
|
||||
function __fish_spack_packages
|
||||
spack list
|
||||
end
|
||||
|
||||
function __fish_spack_pkg_packages
|
||||
spack pkg list
|
||||
end
|
||||
|
||||
function __fish_spack_providers
|
||||
string trim (spack providers | grep -v '^$')
|
||||
end
|
||||
|
||||
function __fish_spack_repos
|
||||
spack repo list | awk {'printf ("%s\t%s", $1, $2)'}
|
||||
end
|
||||
|
||||
function __fish_spack_scopes
|
||||
# TODO: how to list all scopes?
|
||||
set -l scope system site user defaults
|
||||
set -l platform cray darwin linux test
|
||||
|
||||
string join \n $scope
|
||||
end
|
||||
|
||||
function __fish_spack_specs
|
||||
set -l token (commandline -opt)
|
||||
|
||||
# Complete compilers
|
||||
if string match -rq -- '^(?<pre>.*%)[\w-]*(@[\w\.+~-]*)?$' $token
|
||||
__fish_spack_installed_compilers | string replace -r -- '^' "$pre"
|
||||
return
|
||||
end
|
||||
|
||||
# Try to complete spec version
|
||||
# Currently we can only match '@' after a package name
|
||||
set -l package
|
||||
|
||||
# Match ^ following package name
|
||||
if string match -rq -- '^(?<pre>.*?\^)[\w\.+~-]*$' $token
|
||||
# Package name is the nearest, assuming first character is always a letter or digit
|
||||
set packages (string match -ar -- '^[\w-]+' $__fish_spack_argparse_argv $token)
|
||||
set package $packages[-1]
|
||||
|
||||
if test -n "$package"
|
||||
spack dependencies $package | string replace -r -- '^' "$pre"
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# Match @ following package name
|
||||
if string match -rq -- '^(?<pre>.*?\^?(?<packages>[\w\.+~-]*)@)[\w\.]*$' $token
|
||||
set package $packages[-1]
|
||||
|
||||
# Matched @ starting at next token
|
||||
if test -z "$package"
|
||||
string match -arq -- '(^|\^)(?<inners>[\w\.+~-]*)$' $__fish_spack_argparse_argv[-1]
|
||||
if test -n "$inners[1]"
|
||||
set package $inners[-1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Complete version if package found
|
||||
if test -n "$package"
|
||||
# Only list safe versions for speed
|
||||
string trim (spack versions --safe $package) | string replace -r -- '^' "$pre"
|
||||
return
|
||||
end
|
||||
|
||||
# Else complete package name
|
||||
__fish_spack_installed_packages | string replace -r -- '$' \t"installed"
|
||||
spack list
|
||||
end
|
||||
|
||||
function __fish_spack_specs_or_id
|
||||
# Try to match local hash first
|
||||
__fish_spack_installed_specs_id
|
||||
and return
|
||||
|
||||
__fish_spack_specs
|
||||
end
|
||||
|
||||
function __fish_spack_tags
|
||||
string trim (spack tags)
|
||||
end
|
||||
|
||||
function __fish_spack_tests
|
||||
spack test list | grep -v '^[=-]'
|
||||
end
|
||||
|
||||
function __fish_spack_unit_tests
|
||||
# Skip optional flags, or it will be really slow
|
||||
string match -q -- '-*' (commandline -opt)
|
||||
and return
|
||||
|
||||
spack unit-test -l
|
||||
end
|
||||
|
||||
function __fish_spack_yamls
|
||||
# Trim flag from current token
|
||||
string match -rq -- '(?<pre>-.)?(?<token>.*)' (commandline -opt)
|
||||
|
||||
if test -n "$token"
|
||||
find $token* -type f '(' -iname '*.yaml' -or -iname '*.yml' ')'
|
||||
else
|
||||
find -maxdepth 2 -type f '(' -iname '*.yaml' -or -iname '*.yml' ')' | cut -c 3-
|
||||
end
|
||||
end
|
||||
|
||||
# Reset existing completions
|
||||
complete -c spack --erase
|
||||
|
||||
# Spack commands
|
||||
#
|
||||
# Everything below here is auto-generated.
|
|
@ -785,7 +785,15 @@ if test -z "$SPACK_SKIP_MODULES"
|
|||
sp_multi_pathadd MODULEPATH $_sp_tcl_roots
|
||||
end
|
||||
|
||||
# Add programmable tab completion for fish
|
||||
#
|
||||
set -l fish_version (string split '.' $FISH_VERSION)
|
||||
if test $fish_version[1] -gt 3
|
||||
or test $fish_version[1] -eq 3
|
||||
and test $fish_version[2] -ge 2
|
||||
|
||||
source $sp_share_dir/spack-completion.fish
|
||||
end
|
||||
|
||||
#
|
||||
# NOTES
|
||||
|
|
3029
share/spack/spack-completion.fish
Executable file
3029
share/spack/spack-completion.fish
Executable file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue