From 11f2b612612748ee57728693c7745e3af92e9d54 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Sun, 5 Jan 2020 23:35:23 -0800 Subject: [PATCH] Use `spack commands --format=bash` to generate shell completion (#14393) This PR adds a `--format=bash` option to `spack commands` to auto-generate the Bash programmable tab completion script. It can be extended to work for other shells. Progress: - [x] Fix bug in superclass initialization in `ArgparseWriter` - [x] Refactor `ArgparseWriter` (see below) - [x] Ensure that output of old `--format` options remains the same - [x] Add `ArgparseCompletionWriter` and `BashCompletionWriter` - [x] Add `--aliases` option to add command aliases - [x] Standardize positional argument names - [x] Tests for `spack commands --format=bash` coverage - [x] Tests to make sure `spack-completion.bash` stays up-to-date - [x] Tests for `spack-completion.bash` coverage - [x] Speed up `spack-completion.bash` by caching subroutine calls This PR also necessitates a significant refactoring of `ArgparseWriter`. Previously, `ArgparseWriter` was mostly a single `_write` method which handled everything from extracting the information we care about from the parser to formatting the output. Now, `_write` only handles recursion, while the information extraction is split into a separate `parse` method, and the formatting is handled by `format`. This allows subclasses to completely redefine how the format will appear without overriding all of `_write`. Co-Authored-by: Todd Gamblin --- .codecov.yml | 1 - .coveragerc | 1 + lib/spack/llnl/util/argparsewriter.py | 421 ++- lib/spack/spack/cmd/activate.py | 7 +- lib/spack/spack/cmd/add.py | 6 +- lib/spack/spack/cmd/blame.py | 8 +- lib/spack/spack/cmd/build_env.py | 2 +- lib/spack/spack/cmd/buildcache.py | 32 +- lib/spack/spack/cmd/checksum.py | 5 +- lib/spack/spack/cmd/clean.py | 7 +- lib/spack/spack/cmd/commands.py | 125 +- lib/spack/spack/cmd/common/arguments.py | 46 +- lib/spack/spack/cmd/config.py | 6 +- lib/spack/spack/cmd/configure.py | 13 +- lib/spack/spack/cmd/deactivate.py | 6 +- lib/spack/spack/cmd/dependencies.py | 5 +- lib/spack/spack/cmd/dependents.py | 8 +- lib/spack/spack/cmd/dev_build.py | 7 +- lib/spack/spack/cmd/edit.py | 5 +- lib/spack/spack/cmd/env.py | 4 +- lib/spack/spack/cmd/extensions.py | 2 +- lib/spack/spack/cmd/fetch.py | 12 +- lib/spack/spack/cmd/gpg.py | 15 +- lib/spack/spack/cmd/graph.py | 7 +- lib/spack/spack/cmd/info.py | 6 +- lib/spack/spack/cmd/install.py | 13 +- lib/spack/spack/cmd/load.py | 8 +- lib/spack/spack/cmd/location.py | 6 +- lib/spack/spack/cmd/maintainers.py | 14 +- lib/spack/spack/cmd/mirror.py | 14 +- lib/spack/spack/cmd/patch.py | 13 +- lib/spack/spack/cmd/pkg.py | 5 +- lib/spack/spack/cmd/remove.py | 6 +- lib/spack/spack/cmd/repo.py | 12 +- lib/spack/spack/cmd/restage.py | 10 +- lib/spack/spack/cmd/setup.py | 5 +- lib/spack/spack/cmd/spec.py | 4 +- lib/spack/spack/cmd/stage.py | 7 +- lib/spack/spack/cmd/uninstall.py | 18 +- lib/spack/spack/cmd/unload.py | 7 +- lib/spack/spack/cmd/verify.py | 12 +- lib/spack/spack/cmd/versions.py | 7 +- lib/spack/spack/reporters/cdash.py | 4 +- lib/spack/spack/test/cmd/commands.py | 166 +- .../spack/test/llnl/util/argparsewriter.py | 37 + share/spack/bash/spack-completion.in | 309 ++ share/spack/qa/completion-test.sh | 89 + share/spack/qa/run-unit-tests | 17 +- share/spack/qa/setup-env-test.sh | 195 +- share/spack/qa/test-framework.sh | 195 ++ share/spack/qa/update-completion-scripts.sh | 23 + share/spack/spack-completion.bash | 2692 +++++++++-------- 52 files changed, 2860 insertions(+), 1785 deletions(-) create mode 100644 lib/spack/spack/test/llnl/util/argparsewriter.py create mode 100755 share/spack/bash/spack-completion.in create mode 100755 share/spack/qa/completion-test.sh create mode 100755 share/spack/qa/test-framework.sh create mode 100755 share/spack/qa/update-completion-scripts.sh diff --git a/.codecov.yml b/.codecov.yml index e45c500b76..a70b19c39c 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -12,6 +12,5 @@ ignore: - lib/spack/docs/.* - lib/spack/external/.* - share/spack/qa/.* - - share/spack/spack-completion.bash comment: off diff --git a/.coveragerc b/.coveragerc index c0c5f76688..7292badff5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,6 +9,7 @@ omit = lib/spack/spack/test/* lib/spack/docs/* lib/spack/external/* + share/spack/qa/* [report] # Regexes for lines to exclude from consideration diff --git a/lib/spack/llnl/util/argparsewriter.py b/lib/spack/llnl/util/argparsewriter.py index ec6ea30df9..f43595145e 100644 --- a/lib/spack/llnl/util/argparsewriter.py +++ b/lib/spack/llnl/util/argparsewriter.py @@ -4,134 +4,162 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) from __future__ import print_function + import re import argparse import errno import sys +from six import StringIO + +class Command(object): + """Parsed representation of a command from argparse. + + This is a single command from an argparse parser. ``ArgparseWriter`` + creates these and returns them from ``parse()``, and it passes one of + these to each call to ``format()`` so that we can take an action for + a single command. + + Parts of a Command: + - prog: command name (str) + - description: command description (str) + - usage: command usage (str) + - positionals: list of positional arguments (list) + - optionals: list of optional arguments (list) + - subcommands: list of subcommand parsers (list) + """ + def __init__(self, prog, description, usage, + positionals, optionals, subcommands): + self.prog = prog + self.description = description + self.usage = usage + self.positionals = positionals + self.optionals = optionals + self.subcommands = subcommands + + +# NOTE: The only reason we subclass argparse.HelpFormatter is to get access +# to self._expand_help(), ArgparseWriter is not intended to be used as a +# formatter_class. class ArgparseWriter(argparse.HelpFormatter): """Analyzes an argparse ArgumentParser for easy generation of help.""" - def __init__(self, out=sys.stdout): - super(ArgparseWriter, self).__init__(out) + + def __init__(self, prog, out=sys.stdout, aliases=False): + """Initializes a new ArgparseWriter instance. + + Parameters: + prog (str): the program name + out (file object): the file to write to + aliases (bool): whether or not to include subparsers for aliases + """ + super(ArgparseWriter, self).__init__(prog) self.level = 0 + self.prog = prog self.out = out + self.aliases = aliases - def _write(self, parser, root=True, level=0): + def parse(self, parser, prog): + """Parses the parser object and returns the relavent components. + + Parameters: + parser (argparse.ArgumentParser): the parser + prog (str): the command name + + Returns: + (Command) information about the command from the parser + """ self.parser = parser - self.level = level + + split_prog = parser.prog.split(' ') + split_prog[-1] = prog + prog = ' '.join(split_prog) + description = parser.description + + fmt = parser._get_formatter() actions = parser._actions + groups = parser._mutually_exclusive_groups + usage = fmt._format_usage(None, actions, groups, '').strip() - # allow root level to be flattened with rest of commands - if type(root) == int: - self.level = root - root = True - - # go through actions and split them into optionals, positionals, + # Go through actions and split them into optionals, positionals, # and subcommands optionals = [] positionals = [] subcommands = [] for action in actions: if action.option_strings: - optionals.append(action) + 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)) elif isinstance(action, argparse._SubParsersAction): for subaction in action._choices_actions: subparser = action._name_parser_map[subaction.dest] - subcommands.append(subparser) + subcommands.append((subparser, subaction.dest)) + + # Look for aliases of the form 'name (alias, ...)' + if self.aliases: + match = re.match(r'(.*) \((.*)\)', subaction.metavar) + if match: + aliases = match.group(2).split(', ') + for alias in aliases: + subparser = action._name_parser_map[alias] + subcommands.append((subparser, alias)) else: - positionals.append(action) - - groups = parser._mutually_exclusive_groups - fmt = parser._get_formatter() - description = parser.description - - def action_group(function, actions): - for action in actions: - arg = fmt._format_action_invocation(action) + args = fmt._format_action_invocation(action) help = self._expand_help(action) if action.help else '' - function(arg, re.sub('\n', ' ', help)) + help = help.replace('\n', ' ') + positionals.append((args, help)) - if root: - self.begin_command(parser.prog) + return Command( + prog, description, usage, positionals, optionals, subcommands) - if description: - self.description(parser.description) + def format(self, cmd): + """Returns the string representation of a single node in the + parser tree. - usage = fmt._format_usage(None, actions, groups, '').strip() - self.usage(usage) + Override this in subclasses to define how each subcommand + should be displayed. - if positionals: - self.begin_positionals() - action_group(self.positional, positionals) - self.end_positionals() + Parameters: + (Command): parsed information about a command or subcommand - if optionals: - self.begin_optionals() - action_group(self.optional, optionals) - self.end_optionals() + Returns: + str: the string representation of this subcommand + """ + raise NotImplementedError - if subcommands: - self.begin_subcommands(subcommands) - for subparser in subcommands: - self._write(subparser, root=True, level=level + 1) - self.end_subcommands(subcommands) + def _write(self, parser, prog, level=0): + """Recursively writes a parser. - if root: - self.end_command(parser.prog) + Parameters: + parser (argparse.ArgumentParser): the parser + prog (str): the command name + level (int): the current level + """ + self.level = level - def write(self, parser, root=True): + cmd = self.parse(parser, prog) + self.out.write(self.format(cmd)) + + for subparser, prog in cmd.subcommands: + self._write(subparser, prog, level=level + 1) + + def write(self, parser): """Write out details about an ArgumentParser. Args: - parser (ArgumentParser): an ``argparse`` parser - root (bool or int): if bool, whether to include the root parser; - or ``1`` to flatten the root parser with first-level - subcommands + parser (argparse.ArgumentParser): the parser """ try: - self._write(parser, root, level=0) + self._write(parser, self.prog) except IOError as e: - # swallow pipe errors + # Swallow pipe errors + # Raises IOError in Python 2 and BrokenPipeError in Python 3 if e.errno != errno.EPIPE: raise - def begin_command(self, prog): - pass - - def end_command(self, prog): - pass - - def description(self, description): - pass - - def usage(self, usage): - pass - - def begin_positionals(self): - pass - - def positional(self, name, help): - pass - - def end_positionals(self): - pass - - def begin_optionals(self): - pass - - def optional(self, option, help): - pass - - def end_optionals(self): - pass - - def begin_subcommands(self, subcommands): - pass - - def end_subcommands(self, subcommands): - pass - _rst_levels = ['=', '-', '^', '~', ':', '`'] @@ -139,66 +167,213 @@ def end_subcommands(self, subcommands): class ArgparseRstWriter(ArgparseWriter): """Write argparse output as rst sections.""" - def __init__(self, out=sys.stdout, rst_levels=_rst_levels, - strip_root_prog=True): + def __init__(self, prog, out=sys.stdout, aliases=False, + rst_levels=_rst_levels): """Create a new ArgparseRstWriter. - Args: + Parameters: + prog (str): program name out (file object): file to write to + aliases (bool): whether or not to include subparsers for aliases rst_levels (list of str): list of characters for rst section headings - strip_root_prog (bool): if ``True``, strip the base command name - from subcommands in output """ - super(ArgparseRstWriter, self).__init__(out) + super(ArgparseRstWriter, self).__init__(prog, out, aliases) self.rst_levels = rst_levels - self.strip_root_prog = strip_root_prog - def line(self, string=''): - self.out.write('%s\n' % string) + def format(self, cmd): + string = StringIO() + string.write(self.begin_command(cmd.prog)) + + if cmd.description: + string.write(self.description(cmd.description)) + + string.write(self.usage(cmd.usage)) + + if cmd.positionals: + string.write(self.begin_positionals()) + for args, 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: + string.write(self.optional(dest_flags, help)) + string.write(self.end_optionals()) + + if cmd.subcommands: + string.write(self.begin_subcommands(cmd.subcommands)) + + return string.getvalue() def begin_command(self, prog): - self.line() - self.line('----') - self.line() - self.line('.. _%s:\n' % prog.replace(' ', '-')) - self.line('%s' % prog) - self.line(self.rst_levels[self.level] * len(prog) + '\n') + return """ +---- + +.. _{0}: + +{1} +{2} + +""".format(prog.replace(' ', '-'), prog, + self.rst_levels[self.level] * len(prog)) def description(self, description): - self.line('%s\n' % description) + return description + '\n\n' def usage(self, usage): - self.line('.. code-block:: console\n') - self.line(' %s\n' % usage) + return """\ +.. code-block:: console + + {0} + +""".format(usage) def begin_positionals(self): - self.line() - self.line('**Positional arguments**\n') + return '\n**Positional arguments**\n\n' def positional(self, name, help): - self.line(name) - self.line(' %s\n' % help) + return """\ +{0} + {1} + +""".format(name, help) + + def end_positionals(self): + return '' def begin_optionals(self): - self.line() - self.line('**Optional arguments**\n') + return '\n**Optional arguments**\n\n' def optional(self, opts, help): - self.line('``%s``' % opts) - self.line(' %s\n' % help) + return """\ +``{0}`` + {1} + +""".format(opts, help) + + def end_optionals(self): + return '' def begin_subcommands(self, subcommands): - self.line() - self.line('**Subcommands**\n') - self.line('.. hlist::') - self.line(' :columns: 4\n') + string = """ +**Subcommands** - for cmd in subcommands: - prog = cmd.prog - if self.strip_root_prog: - prog = re.sub(r'^[^ ]* ', '', prog) +.. hlist:: + :columns: 4 - self.line(' * :ref:`%s <%s>`' - % (prog, cmd.prog.replace(' ', '-'))) - self.line() +""" + + 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): + """Returns the string representation of a single node in the + parser tree. + + Override this in subclasses to define how each subcommand + should be displayed. + + Parameters: + (Command): parsed information about a command or subcommand + + Returns: + str: the 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 = [] + if cmd.positionals: + positionals, _ = zip(*cmd.positionals) + optionals, _, _ = zip(*cmd.optionals) + subcommands = [] + 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): + """Returns the syntax needed to begin a function definition. + + Parameters: + prog (str): the command name + + Returns: + str: the function definition beginning + """ + name = prog.replace('-', '_').replace(' ', '_') + return '\n_{0}() {{'.format(name) + + def end_function(self, prog=None): + """Returns the syntax needed to end a function definition. + + Parameters: + prog (str, optional): the command name + + Returns: + str: the function definition ending + """ + return '}\n' + + def body(self, positionals, optionals, subcommands): + """Returns the body of the function. + + Parameters: + positionals (list): list of positional arguments + optionals (list): list of optional arguments + subcommands (list): list of subcommand parsers + + Returns: + str: the function body + """ + return '' + + def positionals(self, positionals): + """Returns the syntax for reporting positional arguments. + + Parameters: + positionals (list): list of positional arguments + + Returns: + str: the syntax for positional arguments + """ + return '' + + def optionals(self, optionals): + """Returns the syntax for reporting optional flags. + + Parameters: + optionals (list): list of optional arguments + + Returns: + str: the syntax for optional flags + """ + return '' + + def subcommands(self, subcommands): + """Returns the syntax for reporting subcommands. + + Parameters: + subcommands (list): list of subcommand parsers + + Returns: + str: the syntax for subcommand parsers + """ + return '' diff --git a/lib/spack/spack/cmd/activate.py b/lib/spack/spack/cmd/activate.py index 718d30ce07..bfca9f5604 100644 --- a/lib/spack/spack/cmd/activate.py +++ b/lib/spack/spack/cmd/activate.py @@ -3,11 +3,10 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.environment as ev from spack.filesystem_view import YamlFilesystemView @@ -23,9 +22,7 @@ def setup_parser(subparser): subparser.add_argument( '-v', '--view', metavar='VIEW', type=str, help="the view to operate on") - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, - help="spec of package extension to activate") + arguments.add_common_arguments(subparser, ['installed_spec']) def activate(parser, args): diff --git a/lib/spack/spack/cmd/add.py b/lib/spack/spack/cmd/add.py index efae7ffeb7..e08c2c5aac 100644 --- a/lib/spack/spack/cmd/add.py +++ b/lib/spack/spack/cmd/add.py @@ -3,11 +3,10 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.environment as ev @@ -20,8 +19,7 @@ def setup_parser(subparser): subparser.add_argument('-l', '--list-name', dest='list_name', default='specs', help="name of the list to add specs to") - subparser.add_argument( - 'specs', nargs=argparse.REMAINDER, help="specs of packages to add") + arguments.add_common_arguments(subparser, ['specs']) def add(parser, args): diff --git a/lib/spack/spack/cmd/blame.py b/lib/spack/spack/cmd/blame.py index ea1a310476..b806058aec 100644 --- a/lib/spack/spack/cmd/blame.py +++ b/lib/spack/spack/cmd/blame.py @@ -35,7 +35,7 @@ def setup_parser(subparser): help='show git blame output instead of summary') subparser.add_argument( - 'package_name', help='name of package to show contributions for, ' + 'package_or_file', help='name of package to show contributions for, ' 'or path to a file in the spack repo') @@ -47,13 +47,13 @@ def blame(parser, args): # Get name of file to blame blame_file = None - if os.path.isfile(args.package_name): - path = os.path.realpath(args.package_name) + if os.path.isfile(args.package_or_file): + path = os.path.realpath(args.package_or_file) if path.startswith(spack.paths.prefix): blame_file = path if not blame_file: - pkg = spack.repo.get(args.package_name) + pkg = spack.repo.get(args.package_or_file) blame_file = pkg.module.__file__.rstrip('c') # .pyc -> .py # get git blame for the package diff --git a/lib/spack/spack/cmd/build_env.py b/lib/spack/spack/cmd/build_env.py index 7f9f08c01f..128d167a29 100644 --- a/lib/spack/spack/cmd/build_env.py +++ b/lib/spack/spack/cmd/build_env.py @@ -33,7 +33,7 @@ def setup_parser(subparser): subparser.add_argument( 'spec', nargs=argparse.REMAINDER, metavar='spec [--] [cmd]...', - help="specs of package environment to emulate") + help="spec of package environment to emulate") subparser.epilog\ = 'If a command is not specified, the environment will be printed ' \ 'to standard output (cf /usr/bin/env) unless --dump and/or --pickle ' \ diff --git a/lib/spack/spack/cmd/buildcache.py b/lib/spack/spack/cmd/buildcache.py index 5baa63af85..cbcbc2c0cb 100644 --- a/lib/spack/spack/cmd/buildcache.py +++ b/lib/spack/spack/cmd/buildcache.py @@ -3,7 +3,6 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse import os import shutil import sys @@ -61,11 +60,9 @@ def setup_parser(subparser): "building package(s)") create.add_argument('-y', '--spec-yaml', default=None, help='Create buildcache entry for spec from yaml file') - create.add_argument( - 'packages', nargs=argparse.REMAINDER, - help="specs of packages to create buildcache for") create.add_argument('--no-deps', action='store_true', default='false', help='Create buildcache entry wo/ dependencies') + arguments.add_common_arguments(create, ['specs']) create.set_defaults(func=createtarball) install = subparsers.add_parser('install', help=installtarball.__doc__) @@ -79,9 +76,7 @@ def setup_parser(subparser): install.add_argument('-u', '--unsigned', action='store_true', help="install unsigned buildcache" + " tarballs for testing") - install.add_argument( - 'packages', nargs=argparse.REMAINDER, - help="specs of packages to install buildcache for") + arguments.add_common_arguments(install, ['specs']) install.set_defaults(func=installtarball) listcache = subparsers.add_parser('list', help=listspecs.__doc__) @@ -92,9 +87,7 @@ def setup_parser(subparser): help='show variants in output (can be long)') listcache.add_argument('-f', '--force', action='store_true', help="force new download of specs") - listcache.add_argument( - 'packages', nargs=argparse.REMAINDER, - help="specs of packages to search for") + arguments.add_common_arguments(listcache, ['specs']) listcache.set_defaults(func=listspecs) dlkeys = subparsers.add_parser('keys', help=getkeys.__doc__) @@ -113,10 +106,9 @@ def setup_parser(subparser): help='analyzes an installed spec and reports whether ' 'executables and libraries are relocatable' ) - preview_parser.add_argument( - 'packages', nargs='+', help='list of installed packages' - ) + arguments.add_common_arguments(preview_parser, ['installed_specs']) preview_parser.set_defaults(func=preview) + # Check if binaries need to be rebuilt on remote mirror check = subparsers.add_parser('check', help=check_binaries.__doc__) check.add_argument( @@ -313,8 +305,10 @@ def _createtarball(env, spec_yaml, packages, directory, key, no_deps, force, tty.debug(yaml_text) s = Spec.from_yaml(yaml_text) packages.add('/{0}'.format(s.dag_hash())) + elif packages: packages = packages + else: tty.die("build cache file creation requires at least one" + " installed package argument or else path to a" + @@ -378,17 +372,17 @@ def createtarball(args): # restrict matching to current environment if one is active env = ev.get_env(args, 'buildcache create') - _createtarball(env, args.spec_yaml, args.packages, args.directory, + _createtarball(env, args.spec_yaml, args.specs, args.directory, args.key, args.no_deps, args.force, args.rel, args.unsigned, args.allow_root, args.no_rebuild_index) def installtarball(args): """install from a binary package""" - if not args.packages: + if not args.specs: tty.die("build cache file installation requires" + " at least one package spec argument") - pkgs = set(args.packages) + pkgs = set(args.specs) matches = match_downloaded_specs(pkgs, args.multiple, args.force) for match in matches: @@ -422,8 +416,8 @@ def install_tarball(spec, args): def listspecs(args): """list binary packages available from mirrors""" specs = bindist.get_specs(args.force) - if args.packages: - constraints = set(args.packages) + if args.specs: + constraints = set(args.specs) specs = [s for s in specs if any(s.satisfies(c) for c in constraints)] display_specs(specs, args, all_headers=True) @@ -440,7 +434,7 @@ def preview(args): Args: args: command line arguments """ - specs = find_matching_specs(args.packages, allow_multiple_matches=True) + specs = find_matching_specs(args.specs, allow_multiple_matches=True) # Cycle over the specs that match for spec in specs: diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py index c606cd3886..343915868c 100644 --- a/lib/spack/spack/cmd/checksum.py +++ b/lib/spack/spack/cmd/checksum.py @@ -10,6 +10,7 @@ import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.repo import spack.stage import spack.util.crypto @@ -22,12 +23,10 @@ def setup_parser(subparser): - subparser.add_argument( - 'package', - help='package to checksum versions for') subparser.add_argument( '--keep-stage', action='store_true', help="don't clean up staging area when command completes") + arguments.add_common_arguments(subparser, ['package']) subparser.add_argument( 'versions', nargs=argparse.REMAINDER, help='versions to generate checksums for') diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py index dc25857b51..791a1b7dc3 100644 --- a/lib/spack/spack/cmd/clean.py +++ b/lib/spack/spack/cmd/clean.py @@ -11,6 +11,7 @@ import spack.caches import spack.cmd +import spack.cmd.common.arguments as arguments import spack.repo import spack.stage from spack.paths import lib_path, var_path @@ -43,11 +44,7 @@ def setup_parser(subparser): subparser.add_argument( '-a', '--all', action=AllClean, help="equivalent to -sdmp", nargs=0 ) - subparser.add_argument( - 'specs', - nargs=argparse.REMAINDER, - help="removes the build stages and tarballs for specs" - ) + arguments.add_common_arguments(subparser, ['specs']) def clean(parser, args): diff --git a/lib/spack/spack/cmd/commands.py b/lib/spack/spack/cmd/commands.py index b4fde1aea4..4966bd7858 100644 --- a/lib/spack/spack/cmd/commands.py +++ b/lib/spack/spack/cmd/commands.py @@ -5,13 +5,16 @@ from __future__ import print_function -import sys +import argparse +import copy import os import re -import argparse +import sys import llnl.util.tty as tty -from llnl.util.argparsewriter import ArgparseWriter, ArgparseRstWriter +from llnl.util.argparsewriter import ( + ArgparseWriter, ArgparseRstWriter, ArgparseCompletionWriter +) from llnl.util.tty.colify import colify import spack.cmd @@ -35,6 +38,8 @@ def formatter(func): def setup_parser(subparser): + subparser.add_argument( + '-a', '--aliases', action='store_true', help='include command aliases') subparser.add_argument( '--format', default='names', choices=formatters, help='format to be used to print the output (default: names)') @@ -52,29 +57,97 @@ def setup_parser(subparser): class SpackArgparseRstWriter(ArgparseRstWriter): """RST writer tailored for spack documentation.""" - def __init__(self, documented_commands, out=sys.stdout): - super(SpackArgparseRstWriter, self).__init__(out) - self.documented = documented_commands if documented_commands else [] + def __init__(self, prog, out=sys.stdout, aliases=False, + documented_commands=[], + rst_levels=['-', '-', '^', '~', ':', '`']): + super(SpackArgparseRstWriter, self).__init__( + prog, out, aliases, rst_levels) + self.documented = documented_commands def usage(self, *args): - super(SpackArgparseRstWriter, self).usage(*args) - cmd = re.sub(' ', '-', self.parser.prog) + string = super(SpackArgparseRstWriter, self).usage(*args) + + cmd = self.parser.prog.replace(' ', '-') if cmd in self.documented: - self.line() - self.line(':ref:`More documentation `' % cmd) + string += '\n:ref:`More documentation `\n'.format(cmd) + + return string class SubcommandWriter(ArgparseWriter): - def begin_command(self, prog): - self.out.write(' ' * self.level + prog) - self.out.write('\n') + def format(self, cmd): + return ' ' * self.level + cmd.prog + '\n' + + +_positional_to_subroutine = { + 'package': '_all_packages', + 'spec': '_all_packages', + 'filter': '_all_packages', + 'installed': '_installed_packages', + 'compiler': '_installed_compilers', + 'section': '_config_sections', + 'env': '_environments', + 'extendable': '_extensions', + 'keys': '_keys', + 'help_command': '_subcommands', + 'mirror': '_mirrors', + 'virtual': '_providers', + 'namespace': '_repos', + 'hash': '_all_resource_hashes', + 'pytest': '_tests', +} + + +class BashCompletionWriter(ArgparseCompletionWriter): + """Write argparse output as bash programmable tab completion.""" + + def body(self, positionals, optionals, subcommands): + if positionals: + return """ + if $list_options + then + {0} + else + {1} + fi +""".format(self.optionals(optionals), self.positionals(positionals)) + elif subcommands: + return """ + if $list_options + then + {0} + else + {1} + fi +""".format(self.optionals(optionals), self.subcommands(subcommands)) + else: + return """ + {0} +""".format(self.optionals(optionals)) + + def positionals(self, positionals): + # If match found, return function name + for positional in positionals: + for key, value in _positional_to_subroutine.items(): + if positional.startswith(key): + return value + + # If no matches found, return empty list + return 'SPACK_COMPREPLY=""' + + def optionals(self, optionals): + return 'SPACK_COMPREPLY="{0}"'.format(' '.join(optionals)) + + def subcommands(self, subcommands): + return 'SPACK_COMPREPLY="{0}"'.format(' '.join(subcommands)) @formatter def subcommands(args, out): parser = spack.main.make_argument_parser() spack.main.add_all_commands(parser) - SubcommandWriter(out).write(parser) + writer = SubcommandWriter(parser.prog, out, args.aliases) + writer.write(parser) def rst_index(out): @@ -124,12 +197,28 @@ def rst(args, out): out.write('\n') # print sections for each command and subcommand - SpackArgparseRstWriter(documented_commands, out).write(parser, root=1) + writer = SpackArgparseRstWriter( + parser.prog, out, args.aliases, documented_commands) + writer.write(parser) @formatter def names(args, out): - colify(spack.cmd.all_commands(), output=out) + commands = copy.copy(spack.cmd.all_commands()) + + if args.aliases: + commands.extend(spack.main.aliases.keys()) + + colify(commands, output=out) + + +@formatter +def bash(args, out): + parser = spack.main.make_argument_parser() + spack.main.add_all_commands(parser) + + writer = BashCompletionWriter(parser.prog, out, args.aliases) + writer.write(parser) def prepend_header(args, out): @@ -148,12 +237,14 @@ def commands(parser, args): tty.die("No such file: '%s'" % args.header) # if we're updating an existing file, only write output if a command - # is newer than the file. + # or the header is newer than the file. if args.update: if os.path.exists(args.update): files = [ spack.cmd.get_module(command).__file__.rstrip('c') # pyc -> py for command in spack.cmd.all_commands()] + if args.header: + files.append(args.header) last_update = os.path.getmtime(args.update) if not any(os.path.getmtime(f) > last_update for f in files): tty.msg('File is up to date: %s' % args.update) diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py index da8b0b0277..b93f265c7a 100644 --- a/lib/spack/spack/cmd/common/arguments.py +++ b/lib/spack/spack/cmd/common/arguments.py @@ -120,7 +120,7 @@ def default(self, value): class DeptypeAction(argparse.Action): - """Creates a tuple of valid dependency tpyes from a deptype argument.""" + """Creates a tuple of valid dependency types from a deptype argument.""" def __call__(self, parser, namespace, values, option_string=None): deptype = dep.all_deptypes if values: @@ -132,11 +132,53 @@ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, deptype) +# TODO: merge constraint and installed_specs @arg def constraint(): return Args( 'constraint', nargs=argparse.REMAINDER, action=ConstraintAction, - help='constraint to select a subset of installed packages') + help='constraint to select a subset of installed packages', + metavar='installed_specs') + + +@arg +def package(): + return Args('package', help='package name') + + +@arg +def packages(): + return Args( + 'packages', nargs='+', help='one or more package names', + metavar='package') + + +# Specs must use `nargs=argparse.REMAINDER` because a single spec can +# contain spaces, and contain variants like '-mpi' that argparse thinks +# are a collection of optional flags. +@arg +def spec(): + return Args('spec', nargs=argparse.REMAINDER, help='package spec') + + +@arg +def specs(): + return Args( + 'specs', nargs=argparse.REMAINDER, help='one or more package specs') + + +@arg +def installed_spec(): + return Args( + 'spec', nargs=argparse.REMAINDER, help='installed package spec', + metavar='installed_spec') + + +@arg +def installed_specs(): + return Args( + 'specs', nargs=argparse.REMAINDER, + help='one or more installed package specs', metavar='installed_specs') @arg diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py index 9951f346cb..b6055a7f6b 100644 --- a/lib/spack/spack/cmd/config.py +++ b/lib/spack/spack/cmd/config.py @@ -34,7 +34,7 @@ def setup_parser(subparser): help="configuration section to print. " "options: %(choices)s", nargs='?', - metavar='SECTION', + metavar='section', choices=spack.config.section_schemas) blame_parser = sp.add_parser( @@ -42,14 +42,14 @@ def setup_parser(subparser): blame_parser.add_argument('section', help="configuration section to print. " "options: %(choices)s", - metavar='SECTION', + metavar='section', choices=spack.config.section_schemas) edit_parser = sp.add_parser('edit', help='edit configuration file') edit_parser.add_argument('section', help="configuration section to edit. " "options: %(choices)s", - metavar='SECTION', + metavar='section', nargs='?', choices=spack.config.section_schemas) edit_parser.add_argument( diff --git a/lib/spack/spack/cmd/configure.py b/lib/spack/spack/cmd/configure.py index d15d2be912..3df3c87413 100644 --- a/lib/spack/spack/cmd/configure.py +++ b/lib/spack/spack/cmd/configure.py @@ -7,6 +7,7 @@ import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.cmd.install as inst from spack.build_systems.autotools import AutotoolsPackage @@ -36,16 +37,12 @@ def setup_parser(subparser): - subparser.add_argument( - 'package', - nargs=argparse.REMAINDER, - help="spec of the package to install" - ) subparser.add_argument( '-v', '--verbose', action='store_true', help="print additional output during builds" ) + arguments.add_common_arguments(subparser, ['spec']) def _stop_at_phase_during_install(args, calling_fn, phase_mapping): @@ -64,15 +61,15 @@ def _stop_at_phase_during_install(args, calling_fn, phase_mapping): # Install package dependencies if needed parser = argparse.ArgumentParser() inst.setup_parser(parser) - tty.msg('Checking dependencies for {0}'.format(args.package[0])) + tty.msg('Checking dependencies for {0}'.format(args.spec[0])) cli_args = ['-v'] if args.verbose else [] install_args = parser.parse_args(cli_args + ['--only=dependencies']) - install_args.package = args.package + install_args.spec = args.spec inst.install(parser, install_args) # Install package and stop at the given phase cli_args = ['-v'] if args.verbose else [] install_args = parser.parse_args(cli_args + ['--only=package']) - install_args.package = args.package + install_args.spec = args.spec inst.install(parser, install_args, stop_at=phase) except IndexError: tty.error( diff --git a/lib/spack/spack/cmd/deactivate.py b/lib/spack/spack/cmd/deactivate.py index 43ef09a9b1..3c72531a9c 100644 --- a/lib/spack/spack/cmd/deactivate.py +++ b/lib/spack/spack/cmd/deactivate.py @@ -3,10 +3,10 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.environment as ev import spack.store from spack.filesystem_view import YamlFilesystemView @@ -28,9 +28,7 @@ def setup_parser(subparser): '-a', '--all', action='store_true', help="deactivate all extensions of an extendable package, or " "deactivate an extension AND its dependencies") - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, - help="spec of package extension to deactivate") + arguments.add_common_arguments(subparser, ['installed_spec']) def deactivate(parser, args): diff --git a/lib/spack/spack/cmd/dependencies.py b/lib/spack/spack/cmd/dependencies.py index db8fbe4b48..e65e050bfa 100644 --- a/lib/spack/spack/cmd/dependencies.py +++ b/lib/spack/spack/cmd/dependencies.py @@ -3,8 +3,6 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty from llnl.util.tty.colify import colify @@ -31,8 +29,7 @@ def setup_parser(subparser): subparser.add_argument( '-V', '--no-expand-virtuals', action='store_false', default=True, dest="expand_virtuals", help="do not expand virtual dependencies") - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, help="spec or package name") + arguments.add_common_arguments(subparser, ['spec']) def dependencies(parser, args): diff --git a/lib/spack/spack/cmd/dependents.py b/lib/spack/spack/cmd/dependents.py index 78e862a982..e60733f589 100644 --- a/lib/spack/spack/cmd/dependents.py +++ b/lib/spack/spack/cmd/dependents.py @@ -3,15 +3,14 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty from llnl.util.tty.colify import colify +import spack.cmd +import spack.cmd.common.arguments as arguments import spack.environment as ev import spack.repo import spack.store -import spack.cmd description = "show packages that depend on another" section = "basic" @@ -26,8 +25,7 @@ def setup_parser(subparser): subparser.add_argument( '-t', '--transitive', action='store_true', default=False, help="Show all transitive dependents.") - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, help="spec or package name") + arguments.add_common_arguments(subparser, ['spec']) def inverted_dependencies(): diff --git a/lib/spack/spack/cmd/dev_build.py b/lib/spack/spack/cmd/dev_build.py index 190720b05f..c1004f24b3 100644 --- a/lib/spack/spack/cmd/dev_build.py +++ b/lib/spack/spack/cmd/dev_build.py @@ -5,14 +5,13 @@ import sys import os -import argparse import llnl.util.tty as tty import spack.config import spack.cmd -import spack.repo import spack.cmd.common.arguments as arguments +import spack.repo from spack.stage import DIYStage description = "developer build: build from code in current working directory" @@ -41,9 +40,7 @@ def setup_parser(subparser): subparser.add_argument( '-u', '--until', type=str, dest='until', default=None, help="phase to stop after when installing (default None)") - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, - help="specs to use for install. must contain package AND version") + arguments.add_common_arguments(subparser, ['spec']) cd_group = subparser.add_mutually_exclusive_group() arguments.add_common_arguments(cd_group, ['clean', 'dirty']) diff --git a/lib/spack/spack/cmd/edit.py b/lib/spack/spack/cmd/edit.py index 1438383b2c..6cdc3b788d 100644 --- a/lib/spack/spack/cmd/edit.py +++ b/lib/spack/spack/cmd/edit.py @@ -84,12 +84,11 @@ def setup_parser(subparser): help="namespace of package to edit") subparser.add_argument( - 'name', nargs='?', default=None, - help="name of package to edit") + 'package', nargs='?', default=None, help="package name") def edit(parser, args): - name = args.name + name = args.package # By default, edit package files path = spack.paths.packages_path diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py index 1d61dfc250..a8bc1e5bbe 100644 --- a/lib/spack/spack/cmd/env.py +++ b/lib/spack/spack/cmd/env.py @@ -157,7 +157,7 @@ def env_deactivate(args): def env_create_setup_parser(subparser): """create a new environment""" subparser.add_argument( - 'create_env', metavar='ENV', help='name of environment to create') + 'create_env', metavar='env', help='name of environment to create') subparser.add_argument( '-d', '--dir', action='store_true', help='create an environment in a specific directory') @@ -221,7 +221,7 @@ def _env_create(name_or_path, init_file=None, dir=False, with_view=None): def env_remove_setup_parser(subparser): """remove an existing environment""" subparser.add_argument( - 'rm_env', metavar='ENV', nargs='+', + 'rm_env', metavar='env', nargs='+', help='environment(s) to remove') arguments.add_common_arguments(subparser, ['yes_to_all']) diff --git a/lib/spack/spack/cmd/extensions.py b/lib/spack/spack/cmd/extensions.py index 7e3db66384..e834d7fd18 100644 --- a/lib/spack/spack/cmd/extensions.py +++ b/lib/spack/spack/cmd/extensions.py @@ -37,7 +37,7 @@ def setup_parser(subparser): subparser.add_argument( 'spec', nargs=argparse.REMAINDER, - help='spec of package to list extensions for') + help='spec of package to list extensions for', metavar='extendable') def extensions(parser, args): diff --git a/lib/spack/spack/cmd/fetch.py b/lib/spack/spack/cmd/fetch.py index 3004250f03..b91eb52ab8 100644 --- a/lib/spack/spack/cmd/fetch.py +++ b/lib/spack/spack/cmd/fetch.py @@ -3,14 +3,12 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.config import spack.repo -import spack.cmd.common.arguments as arguments description = "fetch archives for packages" section = "build" @@ -25,19 +23,17 @@ def setup_parser(subparser): subparser.add_argument( '-D', '--dependencies', action='store_true', help="also fetch all dependencies") - subparser.add_argument( - 'packages', nargs=argparse.REMAINDER, - help="specs of packages to fetch") + arguments.add_common_arguments(subparser, ['specs']) def fetch(parser, args): - if not args.packages: + if not args.specs: tty.die("fetch requires at least one package argument") if args.no_checksum: spack.config.set('config:checksum', False, scope='command_line') - specs = spack.cmd.parse_specs(args.packages, concretize=True) + specs = spack.cmd.parse_specs(args.specs, concretize=True) for spec in specs: if args.missing or args.dependencies: for s in spec.traverse(): diff --git a/lib/spack/spack/cmd/gpg.py b/lib/spack/spack/cmd/gpg.py index c1a0cafe45..0a77812c12 100644 --- a/lib/spack/spack/cmd/gpg.py +++ b/lib/spack/spack/cmd/gpg.py @@ -6,6 +6,7 @@ import os import argparse +import spack.cmd.common.arguments as arguments import spack.paths from spack.util.gpg import Gpg @@ -19,8 +20,7 @@ def setup_parser(subparser): subparsers = subparser.add_subparsers(help='GPG sub-commands') verify = subparsers.add_parser('verify', help=gpg_verify.__doc__) - verify.add_argument('package', type=str, - help='the package to verify') + arguments.add_common_arguments(verify, ['installed_spec']) verify.add_argument('signature', type=str, nargs='?', help='the signature file') verify.set_defaults(func=gpg_verify) @@ -44,8 +44,7 @@ def setup_parser(subparser): help='the key to use for signing') sign.add_argument('--clearsign', action='store_true', help='if specified, create a clearsign signature') - sign.add_argument('package', type=str, - help='the package to sign') + arguments.add_common_arguments(sign, ['installed_spec']) sign.set_defaults(func=gpg_sign) create = subparsers.add_parser('create', help=gpg_create.__doc__) @@ -122,9 +121,9 @@ def gpg_sign(args): 'please choose one') output = args.output if not output: - output = args.package + '.asc' + output = args.spec[0] + '.asc' # TODO: Support the package format Spack creates. - Gpg.sign(key, args.package, output, args.clearsign) + Gpg.sign(key, ' '.join(args.spec), output, args.clearsign) def gpg_trust(args): @@ -155,8 +154,8 @@ def gpg_verify(args): # TODO: Support the package format Spack creates. signature = args.signature if signature is None: - signature = args.package + '.asc' - Gpg.verify(signature, args.package) + signature = args.spec[0] + '.asc' + Gpg.verify(signature, ' '.join(args.spec)) def gpg(parser, args): diff --git a/lib/spack/spack/cmd/graph.py b/lib/spack/spack/cmd/graph.py index 94197283b8..d0fbf8e6c6 100644 --- a/lib/spack/spack/cmd/graph.py +++ b/lib/spack/spack/cmd/graph.py @@ -5,7 +5,6 @@ from __future__ import print_function -import argparse import llnl.util.tty as tty import spack.cmd @@ -38,11 +37,7 @@ def setup_parser(subparser): '-i', '--installed', action='store_true', help="graph all installed specs in dot format (implies --dot)") - arguments.add_common_arguments(subparser, ['deptype']) - - subparser.add_argument( - 'specs', nargs=argparse.REMAINDER, - help="specs of packages to graph") + arguments.add_common_arguments(subparser, ['deptype', 'specs']) def graph(parser, args): diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py index 413b96fe18..81a68dae96 100644 --- a/lib/spack/spack/cmd/info.py +++ b/lib/spack/spack/cmd/info.py @@ -11,6 +11,7 @@ import llnl.util.tty.color as color from llnl.util.tty.colify import colify +import spack.cmd.common.arguments as arguments import spack.repo import spack.spec import spack.fetch_strategy as fs @@ -36,8 +37,7 @@ def pad(string): def setup_parser(subparser): - subparser.add_argument( - 'name', metavar='PACKAGE', help='name of package to get info for') + arguments.add_common_arguments(subparser, ['package']) def section_title(s): @@ -237,5 +237,5 @@ def print_text_info(pkg): def info(parser, args): - pkg = spack.repo.get(args.name) + pkg = spack.repo.get(args.package) print_text_info(pkg) diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py index ace27577eb..18dad6108b 100644 --- a/lib/spack/spack/cmd/install.py +++ b/lib/spack/spack/cmd/install.py @@ -122,11 +122,6 @@ def setup_parser(subparser): cd_group = subparser.add_mutually_exclusive_group() arguments.add_common_arguments(cd_group, ['clean', 'dirty']) - subparser.add_argument( - 'package', - nargs=argparse.REMAINDER, - help="spec of the package to install" - ) testing = subparser.add_mutually_exclusive_group() testing.add_argument( '--test', default=None, @@ -157,7 +152,7 @@ def setup_parser(subparser): help="Show usage instructions for CDash reporting" ) add_cdash_args(subparser, False) - arguments.add_common_arguments(subparser, ['yes_to_all']) + arguments.add_common_arguments(subparser, ['yes_to_all', 'spec']) def add_cdash_args(subparser, add_help): @@ -258,7 +253,7 @@ def install(parser, args, **kwargs): parser.print_help() return - if not args.package and not args.specfiles: + if not args.spec and not args.specfiles: # if there are no args but an active environment or spack.yaml file # then install the packages from it. env = ev.get_env(args, 'install') @@ -293,7 +288,7 @@ def install(parser, args, **kwargs): if args.log_file: reporter.filename = args.log_file - abstract_specs = spack.cmd.parse_specs(args.package) + abstract_specs = spack.cmd.parse_specs(args.spec) tests = False if args.test == 'all' or args.run_tests: tests = True @@ -303,7 +298,7 @@ def install(parser, args, **kwargs): try: specs = spack.cmd.parse_specs( - args.package, concretize=True, tests=tests) + args.spec, concretize=True, tests=tests) except SpackError as e: tty.debug(e) reporter.concretization_report(e.message) diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py index 89dd61675e..9c48fe802a 100644 --- a/lib/spack/spack/cmd/load.py +++ b/lib/spack/spack/cmd/load.py @@ -3,7 +3,6 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse from spack.cmd.common import print_module_placeholder_help, arguments description = "add package to environment using `module load`" @@ -14,11 +13,8 @@ def setup_parser(subparser): """Parser is only constructed so that this prints a nice help message with -h. """ - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, - help="spec of package to load with modules " - ) - arguments.add_common_arguments(subparser, ['recurse_dependencies']) + arguments.add_common_arguments( + subparser, ['recurse_dependencies', 'installed_spec']) def load(parser, args): diff --git a/lib/spack/spack/cmd/location.py b/lib/spack/spack/cmd/location.py index a48ce85261..60978fe404 100644 --- a/lib/spack/spack/cmd/location.py +++ b/lib/spack/spack/cmd/location.py @@ -6,11 +6,11 @@ from __future__ import print_function import os -import argparse import llnl.util.tty as tty import spack.environment as ev import spack.cmd +import spack.cmd.common.arguments as arguments import spack.environment import spack.paths import spack.repo @@ -55,9 +55,7 @@ def setup_parser(subparser): '-e', '--env', action='store', help="location of an environment managed by spack") - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, - help="spec of package to fetch directory for") + arguments.add_common_arguments(subparser, ['spec']) def location(parser, args): diff --git a/lib/spack/spack/cmd/maintainers.py b/lib/spack/spack/cmd/maintainers.py index a1fb7716f9..a1cf477146 100644 --- a/lib/spack/spack/cmd/maintainers.py +++ b/lib/spack/spack/cmd/maintainers.py @@ -40,7 +40,7 @@ def setup_parser(subparser): # options for commands that take package arguments subparser.add_argument( - 'pkg_or_user', nargs=argparse.REMAINDER, + 'package_or_user', nargs=argparse.REMAINDER, help='names of packages or users to get info for') @@ -104,31 +104,31 @@ def maintainers(parser, args): if args.all: if args.by_user: - maintainers = maintainers_to_packages(args.pkg_or_user) + maintainers = maintainers_to_packages(args.package_or_user) for user, packages in sorted(maintainers.items()): color.cprint('@c{%s}: %s' % (user, ', '.join(sorted(packages)))) return 0 if maintainers else 1 else: - packages = packages_to_maintainers(args.pkg_or_user) + packages = packages_to_maintainers(args.package_or_user) for pkg, maintainers in sorted(packages.items()): color.cprint('@c{%s}: %s' % (pkg, ', '.join(sorted(maintainers)))) return 0 if packages else 1 if args.by_user: - if not args.pkg_or_user: + if not args.package_or_user: tty.die('spack maintainers --by-user requires a user or --all') - packages = union_values(maintainers_to_packages(args.pkg_or_user)) + packages = union_values(maintainers_to_packages(args.package_or_user)) colify(packages) return 0 if packages else 1 else: - if not args.pkg_or_user: + if not args.package_or_user: tty.die('spack maintainers requires a package or --all') - users = union_values(packages_to_maintainers(args.pkg_or_user)) + users = union_values(packages_to_maintainers(args.package_or_user)) colify(users) return 0 if users else 1 diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py index 10f01fd363..5206927895 100644 --- a/lib/spack/spack/cmd/mirror.py +++ b/lib/spack/spack/cmd/mirror.py @@ -5,7 +5,6 @@ import sys -import argparse import llnl.util.tty as tty from llnl.util.tty.colify import colify @@ -39,9 +38,6 @@ def setup_parser(subparser): create_parser.add_argument('-d', '--directory', default=None, help="directory in which to create mirror") - create_parser.add_argument( - 'specs', nargs=argparse.REMAINDER, - help="specs of packages to put in mirror") create_parser.add_argument( '-a', '--all', action='store_true', help="mirror all versions of all packages in Spack, or all packages" @@ -57,6 +53,7 @@ def setup_parser(subparser): '-n', '--versions-per-spec', help="the number of versions to fetch for each spec, choose 'all' to" " retrieve all versions of each package") + arguments.add_common_arguments(create_parser, ['specs']) # used to construct scope arguments below scopes = spack.config.scopes() @@ -64,7 +61,8 @@ def setup_parser(subparser): # Add add_parser = sp.add_parser('add', help=mirror_add.__doc__) - add_parser.add_argument('name', help="mnemonic name for mirror") + add_parser.add_argument( + 'name', help="mnemonic name for mirror", metavar="mirror") add_parser.add_argument( 'url', help="url of mirror directory from 'spack mirror create'") add_parser.add_argument( @@ -75,7 +73,8 @@ def setup_parser(subparser): # Remove remove_parser = sp.add_parser('remove', aliases=['rm'], help=mirror_remove.__doc__) - remove_parser.add_argument('name') + remove_parser.add_argument( + 'name', help="mnemonic name for mirror", metavar="mirror") remove_parser.add_argument( '--scope', choices=scopes, metavar=scopes_metavar, default=spack.config.default_modify_scope(), @@ -83,7 +82,8 @@ def setup_parser(subparser): # Set-Url set_url_parser = sp.add_parser('set-url', help=mirror_set_url.__doc__) - set_url_parser.add_argument('name', help="mnemonic name for mirror") + set_url_parser.add_argument( + 'name', help="mnemonic name for mirror", metavar="mirror") set_url_parser.add_argument( 'url', help="url of mirror directory from 'spack mirror create'") set_url_parser.add_argument( diff --git a/lib/spack/spack/cmd/patch.py b/lib/spack/spack/cmd/patch.py index 9e7cc4164b..8f91edb8f1 100644 --- a/lib/spack/spack/cmd/patch.py +++ b/lib/spack/spack/cmd/patch.py @@ -3,8 +3,6 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty import spack.repo @@ -18,20 +16,17 @@ def setup_parser(subparser): - arguments.add_common_arguments(subparser, ['no_checksum']) - subparser.add_argument( - 'packages', nargs=argparse.REMAINDER, - help="specs of packages to stage") + arguments.add_common_arguments(subparser, ['no_checksum', 'specs']) def patch(parser, args): - if not args.packages: - tty.die("patch requires at least one package argument") + if not args.specs: + tty.die("patch requires at least one spec argument") if args.no_checksum: spack.config.set('config:checksum', False, scope='command_line') - specs = spack.cmd.parse_specs(args.packages, concretize=True) + specs = spack.cmd.parse_specs(args.specs, concretize=True) for spec in specs: package = spack.repo.get(spec) package.do_patch() diff --git a/lib/spack/spack/cmd/pkg.py b/lib/spack/spack/cmd/pkg.py index 86ff535d5e..b988d6a848 100644 --- a/lib/spack/spack/cmd/pkg.py +++ b/lib/spack/spack/cmd/pkg.py @@ -6,7 +6,6 @@ from __future__ import print_function import os -import argparse import re import llnl.util.tty as tty @@ -14,6 +13,7 @@ from llnl.util.filesystem import working_dir import spack.cmd +import spack.cmd.common.arguments as arguments import spack.paths import spack.repo from spack.util.executable import which @@ -28,8 +28,7 @@ def setup_parser(subparser): metavar='SUBCOMMAND', dest='pkg_command') add_parser = sp.add_parser('add', help=pkg_add.__doc__) - add_parser.add_argument('packages', nargs=argparse.REMAINDER, - help="names of packages to add to git repo") + arguments.add_common_arguments(add_parser, ['packages']) list_parser = sp.add_parser('list', help=pkg_list.__doc__) list_parser.add_argument('rev', default='HEAD', nargs='?', diff --git a/lib/spack/spack/cmd/remove.py b/lib/spack/spack/cmd/remove.py index cce197af2e..049041ce83 100644 --- a/lib/spack/spack/cmd/remove.py +++ b/lib/spack/spack/cmd/remove.py @@ -3,11 +3,10 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.environment as ev @@ -26,8 +25,7 @@ def setup_parser(subparser): subparser.add_argument( '-f', '--force', action='store_true', help="remove concretized spec (if any) immediately") - subparser.add_argument( - 'specs', nargs=argparse.REMAINDER, help="specs to be removed") + arguments.add_common_arguments(subparser, ['specs']) def remove(parser, args): diff --git a/lib/spack/spack/cmd/repo.py b/lib/spack/spack/cmd/repo.py index 019813fc9f..83acf796a2 100644 --- a/lib/spack/spack/cmd/repo.py +++ b/lib/spack/spack/cmd/repo.py @@ -51,8 +51,8 @@ def setup_parser(subparser): remove_parser = sp.add_parser( 'remove', help=repo_remove.__doc__, aliases=['rm']) remove_parser.add_argument( - 'path_or_namespace', - help="path or namespace of a Spack package repository") + 'namespace_or_path', + help="namespace or path of a Spack package repository") remove_parser.add_argument( '--scope', choices=scopes, metavar=scopes_metavar, default=spack.config.default_modify_scope(), @@ -101,10 +101,10 @@ def repo_add(args): def repo_remove(args): """Remove a repository from Spack's configuration.""" repos = spack.config.get('repos', scope=args.scope) - path_or_namespace = args.path_or_namespace + namespace_or_path = args.namespace_or_path # If the argument is a path, remove that repository from config. - canon_path = canonicalize_path(path_or_namespace) + canon_path = canonicalize_path(namespace_or_path) for repo_path in repos: repo_canon_path = canonicalize_path(repo_path) if canon_path == repo_canon_path: @@ -117,7 +117,7 @@ def repo_remove(args): for path in repos: try: repo = Repo(path) - if repo.namespace == path_or_namespace: + if repo.namespace == namespace_or_path: repos.remove(path) spack.config.set('repos', repos, args.scope) tty.msg("Removed repository %s with namespace '%s'." @@ -127,7 +127,7 @@ def repo_remove(args): continue tty.die("No repository with path or namespace: %s" - % path_or_namespace) + % namespace_or_path) def repo_list(args): diff --git a/lib/spack/spack/cmd/restage.py b/lib/spack/spack/cmd/restage.py index f74ef09a12..0f55884bfe 100644 --- a/lib/spack/spack/cmd/restage.py +++ b/lib/spack/spack/cmd/restage.py @@ -3,11 +3,10 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty import spack.cmd +import spack.cmd.common.arguments as arguments import spack.repo description = "revert checked out package source code" @@ -16,15 +15,14 @@ def setup_parser(subparser): - subparser.add_argument('packages', nargs=argparse.REMAINDER, - help="specs of packages to restage") + arguments.add_common_arguments(subparser, ['specs']) def restage(parser, args): - if not args.packages: + if not args.specs: tty.die("spack restage requires at least one package spec.") - specs = spack.cmd.parse_specs(args.packages, concretize=True) + specs = spack.cmd.parse_specs(args.specs, concretize=True) for spec in specs: package = spack.repo.get(spec) package.do_restage() diff --git a/lib/spack/spack/cmd/setup.py b/lib/spack/spack/cmd/setup.py index 3e4f9135d7..246e3b4275 100644 --- a/lib/spack/spack/cmd/setup.py +++ b/lib/spack/spack/cmd/setup.py @@ -30,13 +30,10 @@ def setup_parser(subparser): subparser.add_argument( '-i', '--ignore-dependencies', action='store_true', dest='ignore_deps', help="do not try to install dependencies of requested packages") - arguments.add_common_arguments(subparser, ['no_checksum']) + arguments.add_common_arguments(subparser, ['no_checksum', 'spec']) subparser.add_argument( '-v', '--verbose', action='store_true', dest='verbose', help="display verbose build output while installing") - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, - help="specs to use for install. must contain package AND version") cd_group = subparser.add_mutually_exclusive_group() arguments.add_common_arguments(cd_group, ['clean', 'dirty']) diff --git a/lib/spack/spack/cmd/spec.py b/lib/spack/spack/cmd/spec.py index 85fe5a1a9e..fd03f09e57 100644 --- a/lib/spack/spack/cmd/spec.py +++ b/lib/spack/spack/cmd/spec.py @@ -5,7 +5,6 @@ from __future__ import print_function -import argparse import contextlib import sys @@ -47,8 +46,7 @@ def setup_parser(subparser): subparser.add_argument( '-t', '--types', action='store_true', default=False, help='show dependency types') - subparser.add_argument( - 'specs', nargs=argparse.REMAINDER, help="specs of packages") + arguments.add_common_arguments(subparser, ['specs']) @contextlib.contextmanager diff --git a/lib/spack/spack/cmd/stage.py b/lib/spack/spack/cmd/stage.py index 9c0d3ad63c..1acefb723c 100644 --- a/lib/spack/spack/cmd/stage.py +++ b/lib/spack/spack/cmd/stage.py @@ -3,8 +3,6 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse - import llnl.util.tty as tty import spack.environment as ev @@ -18,14 +16,11 @@ def setup_parser(subparser): - arguments.add_common_arguments(subparser, ['no_checksum']) + arguments.add_common_arguments(subparser, ['no_checksum', 'specs']) subparser.add_argument( '-p', '--path', dest='path', help="path to stage package, does not add to spack tree") - subparser.add_argument( - 'specs', nargs=argparse.REMAINDER, help="specs of packages to stage") - def stage(parser, args): if not args.specs: diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py index 906da8d3b3..0ad42f4dfb 100644 --- a/lib/spack/spack/cmd/uninstall.py +++ b/lib/spack/spack/cmd/uninstall.py @@ -5,7 +5,6 @@ from __future__ import print_function -import argparse import sys import spack.cmd @@ -38,17 +37,13 @@ } -def add_common_arguments(subparser): +def setup_parser(subparser): subparser.add_argument( '-f', '--force', action='store_true', dest='force', help="remove regardless of whether other packages or environments " "depend on this one") arguments.add_common_arguments( - subparser, ['recurse_dependents', 'yes_to_all']) - - -def setup_parser(subparser): - add_common_arguments(subparser) + subparser, ['recurse_dependents', 'yes_to_all', 'installed_specs']) subparser.add_argument( '-a', '--all', action='store_true', dest='all', help="USE CAREFULLY. Remove ALL installed packages that match each " @@ -58,11 +53,6 @@ def setup_parser(subparser): "If used in an environment, all packages in the environment " "will be uninstalled.") - subparser.add_argument( - 'packages', - nargs=argparse.REMAINDER, - help="specs of packages to uninstall") - def find_matching_specs(env, specs, allow_multiple_matches=False, force=False): """Returns a list of specs matching the not necessarily @@ -351,10 +341,10 @@ def confirm_removal(specs): def uninstall(parser, args): - if not args.packages and not args.all: + if not args.specs and not args.all: tty.die('uninstall requires at least one package argument.', ' Use `spack uninstall --all` to uninstall ALL packages.') # [any] here handles the --all case by forcing all specs to be returned - specs = spack.cmd.parse_specs(args.packages) if args.packages else [any] + specs = spack.cmd.parse_specs(args.specs) if args.specs else [any] uninstall_specs(args, specs) diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py index 581b37a013..92a25478b6 100644 --- a/lib/spack/spack/cmd/unload.py +++ b/lib/spack/spack/cmd/unload.py @@ -3,8 +3,7 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import argparse -from spack.cmd.common import print_module_placeholder_help +from spack.cmd.common import print_module_placeholder_help, arguments description = "remove package from environment using `module unload`" section = "modules" @@ -14,9 +13,7 @@ def setup_parser(subparser): """Parser is only constructed so that this prints a nice help message with -h. """ - subparser.add_argument( - 'spec', nargs=argparse.REMAINDER, - help='spec of package to unload with modules') + arguments.add_common_arguments(subparser, ['installed_spec']) def unload(parser, args): diff --git a/lib/spack/spack/cmd/verify.py b/lib/spack/spack/cmd/verify.py index 9a38284691..b20d795ce5 100644 --- a/lib/spack/spack/cmd/verify.py +++ b/lib/spack/spack/cmd/verify.py @@ -25,8 +25,8 @@ def setup_parser(subparser): help="Ouptut json-formatted errors") subparser.add_argument('-a', '--all', action='store_true', help="Verify all packages") - subparser.add_argument('files_or_specs', nargs=argparse.REMAINDER, - help="Files or specs to verify") + subparser.add_argument('specs_or_files', nargs=argparse.REMAINDER, + help="Specs or files to verify") type = subparser.add_mutually_exclusive_group() type.add_argument( @@ -47,7 +47,7 @@ def verify(parser, args): setup_parser.parser.print_help() return 1 - for file in args.files_or_specs: + for file in args.specs_or_files: results = spack.verify.check_file_manifest(file) if results.has_errors(): if args.json: @@ -57,21 +57,21 @@ def verify(parser, args): return 0 else: - spec_args = spack.cmd.parse_specs(args.files_or_specs) + spec_args = spack.cmd.parse_specs(args.specs_or_files) if args.all: query = spack.store.db.query_local if local else spack.store.db.query # construct spec list if spec_args: - spec_list = spack.cmd.parse_specs(args.files_or_specs) + spec_list = spack.cmd.parse_specs(args.specs_or_files) specs = [] for spec in spec_list: specs += query(spec, installed=True) else: specs = query(installed=True) - elif args.files_or_specs: + elif args.specs_or_files: # construct disambiguated spec list env = ev.get_env(args, 'verify') specs = list(map(lambda x: spack.cmd.disambiguate_spec(x, env, diff --git a/lib/spack/spack/cmd/versions.py b/lib/spack/spack/cmd/versions.py index c12e7d6290..723f89ce08 100644 --- a/lib/spack/spack/cmd/versions.py +++ b/lib/spack/spack/cmd/versions.py @@ -5,11 +5,13 @@ from __future__ import print_function +import sys + from llnl.util.tty.colify import colify import llnl.util.tty as tty +import spack.cmd.common.arguments as arguments import spack.repo -import sys description = "list available versions of a package" section = "packaging" @@ -17,10 +19,9 @@ def setup_parser(subparser): - subparser.add_argument('package', metavar='PACKAGE', - help='package to list versions for') subparser.add_argument('-s', '--safe-only', action='store_true', help='only list safe versions of the package') + arguments.add_common_arguments(subparser, ['package']) def versions(parser, args): diff --git a/lib/spack/spack/reporters/cdash.py b/lib/spack/spack/reporters/cdash.py index 178747706a..580df7866f 100644 --- a/lib/spack/spack/reporters/cdash.py +++ b/lib/spack/spack/reporters/cdash.py @@ -72,8 +72,8 @@ def __init__(self, args): tty.verbose("Using CDash auth token from environment") self.authtoken = os.environ.get('SPACK_CDASH_AUTH_TOKEN') - if args.package: - packages = args.package + if args.spec: + packages = args.spec else: packages = [] for file in args.specfiles: diff --git a/lib/spack/spack/test/cmd/commands.py b/lib/spack/spack/test/cmd/commands.py index c8ec60d823..2fe62b9bba 100644 --- a/lib/spack/spack/test/cmd/commands.py +++ b/lib/spack/spack/test/cmd/commands.py @@ -3,13 +3,17 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import re +import filecmp +import os +import subprocess + import pytest -from llnl.util.argparsewriter import ArgparseWriter - import spack.cmd +from spack.cmd.commands import _positional_to_subroutine import spack.main +import spack.paths + commands = spack.main.SpackCommand('commands') @@ -17,38 +21,64 @@ spack.main.add_all_commands(parser) -def test_commands_by_name(): +def test_names(): """Test default output of spack commands.""" - out = commands() - assert out.strip().split('\n') == sorted(spack.cmd.all_commands()) + out1 = commands().strip().split('\n') + assert out1 == spack.cmd.all_commands() + assert 'rm' not in out1 + + out2 = commands('--aliases').strip().split('\n') + assert out1 != out2 + assert 'rm' in out2 + + out3 = commands('--format=names').strip().split('\n') + assert out1 == out3 def test_subcommands(): """Test subcommand traversal.""" - out = commands('--format=subcommands') - assert 'spack mirror create' in out - assert 'spack buildcache list' in out - assert 'spack repo add' in out - assert 'spack pkg diff' in out - assert 'spack url parse' in out - assert 'spack view symlink' in out + out1 = commands('--format=subcommands') + assert 'spack mirror create' in out1 + assert 'spack buildcache list' in out1 + assert 'spack repo add' in out1 + assert 'spack pkg diff' in out1 + assert 'spack url parse' in out1 + assert 'spack view symlink' in out1 + assert 'spack rm' not in out1 + assert 'spack compiler add' not in out1 - class Subcommands(ArgparseWriter): - def begin_command(self, prog): - assert prog in out - - Subcommands().write(parser) + out2 = commands('--aliases', '--format=subcommands') + assert 'spack mirror create' in out2 + assert 'spack buildcache list' in out2 + assert 'spack repo add' in out2 + assert 'spack pkg diff' in out2 + assert 'spack url parse' in out2 + assert 'spack view symlink' in out2 + assert 'spack rm' in out2 + assert 'spack compiler add' in out2 def test_rst(): """Do some simple sanity checks of the rst writer.""" - out = commands('--format=rst') + out1 = commands('--format=rst') + assert 'spack mirror create' in out1 + assert 'spack buildcache list' in out1 + assert 'spack repo add' in out1 + assert 'spack pkg diff' in out1 + assert 'spack url parse' in out1 + assert 'spack view symlink' in out1 + assert 'spack rm' not in out1 + assert 'spack compiler add' not in out1 - class Subcommands(ArgparseWriter): - def begin_command(self, prog): - assert prog in out - assert re.sub(r' ', '-', prog) in out - Subcommands().write(parser) + out2 = commands('--aliases', '--format=rst') + assert 'spack mirror create' in out2 + assert 'spack buildcache list' in out2 + assert 'spack repo add' in out2 + assert 'spack pkg diff' in out2 + assert 'spack url parse' in out2 + assert 'spack view symlink' in out2 + assert 'spack rm' in out2 + assert 'spack compiler add' in out2 def test_rst_with_input_files(tmpdir): @@ -109,3 +139,91 @@ def test_rst_update(tmpdir): assert update_file.exists() with update_file.open() as f: assert f.read() == 'empty\n' + + +def test_update_with_header(tmpdir): + update_file = tmpdir.join('output') + + # not yet created when commands is run + commands('--update', str(update_file)) + assert update_file.exists() + with update_file.open() as f: + assert f.read() + fake_header = 'this is a header!\n\n' + + filename = tmpdir.join('header.txt') + with filename.open('w') as f: + f.write(fake_header) + + # created, newer than commands, but older than header + commands('--update', str(update_file), '--header', str(filename)) + + # newer than commands and header + commands('--update', str(update_file), '--header', str(filename)) + + +@pytest.mark.xfail +def test_no_pipe_error(): + """Make sure we don't see any pipe errors when piping output.""" + + proc = subprocess.Popen( + ['spack', 'commands', '--format=rst'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # Call close() on stdout to cause a broken pipe + proc.stdout.close() + proc.wait() + stderr = proc.stderr.read().decode('utf-8') + + assert 'Broken pipe' not in stderr + + +def test_bash_completion(): + """Test the bash completion writer.""" + out1 = commands('--format=bash') + + # Make sure header not included + assert '_bash_completion_spack() {' not in out1 + assert '_all_packages() {' not in out1 + + # Make sure subcommands appear + assert '_spack_remove() {' in out1 + assert '_spack_compiler_find() {' in out1 + + # Make sure aliases don't appear + assert '_spack_rm() {' not in out1 + assert '_spack_compiler_add() {' not in out1 + + # Make sure options appear + assert '-h --help' in out1 + + # Make sure subcommands are called + for function in _positional_to_subroutine.values(): + assert function in out1 + + out2 = commands('--aliases', '--format=bash') + + # Make sure aliases appear + assert '_spack_rm() {' in out2 + assert '_spack_compiler_add() {' in out2 + + +def test_updated_completion_scripts(tmpdir): + """Make sure our shell tab completion scripts remain up-to-date.""" + + msg = ("It looks like Spack's command-line interface has been modified. " + "Please update Spack's shell tab completion scripts by running:\n\n" + " share/spack/qa/update-completion-scripts.sh\n\n" + "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)) + + commands('--aliases', '--format', shell, + '--header', header, '--update', new_script) + + assert filecmp.cmp(old_script, new_script), msg diff --git a/lib/spack/spack/test/llnl/util/argparsewriter.py b/lib/spack/spack/test/llnl/util/argparsewriter.py new file mode 100644 index 0000000000..127149bbaa --- /dev/null +++ b/lib/spack/spack/test/llnl/util/argparsewriter.py @@ -0,0 +1,37 @@ +# Copyright 2013-2020 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) + +"""Tests for ``llnl/util/argparsewriter.py`` + +These tests are fairly minimal, and ArgparseWriter is more extensively +tested in ``cmd/commands.py``. +""" + +import pytest + +import llnl.util.argparsewriter as aw + +import spack.main + + +parser = spack.main.make_argument_parser() +spack.main.add_all_commands(parser) + + +def test_format_not_overridden(): + writer = aw.ArgparseWriter('spack') + + with pytest.raises(NotImplementedError): + writer.write(parser) + + +def test_completion_format_not_overridden(): + writer = aw.ArgparseCompletionWriter('spack') + + assert writer.positionals([]) == '' + assert writer.optionals([]) == '' + assert writer.subcommands([]) == '' + + writer.write(parser) diff --git a/share/spack/bash/spack-completion.in b/share/spack/bash/spack-completion.in new file mode 100755 index 0000000000..2ab39a57a3 --- /dev/null +++ b/share/spack/bash/spack-completion.in @@ -0,0 +1,309 @@ +# Copyright 2013-2020 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.bash is auto-generated by: +# +# $ spack commands --aliases --format=bash +# --header=bash/spack-completion.in --update=spack-completion.bash +# +# Please do not manually modify this file. + + +# The following global variables are set by Bash programmable completion: +# +# COMP_CWORD: An index into ${COMP_WORDS} of the word containing the +# current cursor position +# COMP_KEY: The key (or final key of a key sequence) used to invoke +# the current completion function +# COMP_LINE: The current command line +# COMP_POINT: The index of the current cursor position relative to the +# beginning of the current command +# COMP_TYPE: Set to an integer value corresponding to the type of +# completion attempted that caused a completion function +# to be called +# COMP_WORDBREAKS: The set of characters that the readline library treats +# as word separators when performing word completion +# COMP_WORDS: An array variable consisting of the individual words in +# the current command line +# +# The following global variable is used by Bash programmable completion: +# +# COMPREPLY: An array variable from which bash reads the possible +# completions generated by a shell function invoked by the +# programmable completion facility +# +# See `man bash` for more details. + +# Bash programmable completion for Spack +_bash_completion_spack() { + # In all following examples, let the cursor be denoted by brackets, i.e. [] + + # For our purposes, flags should not affect tab completion. For instance, + # `spack install []` and `spack -d install --jobs 8 []` should both give the same + # possible completions. Therefore, we need to ignore any flags in COMP_WORDS. + local COMP_WORDS_NO_FLAGS=() + local index=0 + while [[ "$index" -lt "$COMP_CWORD" ]] + do + if [[ "${COMP_WORDS[$index]}" == [a-z]* ]] + then + COMP_WORDS_NO_FLAGS+=("${COMP_WORDS[$index]}") + fi + let index++ + done + + # Options will be listed by a subfunction named after non-flag arguments. + # For example, `spack -d install []` will call _spack_install + # and `spack compiler add []` will call _spack_compiler_add + local subfunction=$(IFS='_'; echo "_${COMP_WORDS_NO_FLAGS[*]}") + + # Translate dashes to underscores, as dashes are not permitted in + # compatibility mode. See https://github.com/spack/spack/pull/4079 + subfunction=${subfunction//-/_} + + # However, the word containing the current cursor position needs to be + # added regardless of whether or not it is a flag. This allows us to + # complete something like `spack install --keep-st[]` + COMP_WORDS_NO_FLAGS+=("${COMP_WORDS[$COMP_CWORD]}") + + # Since we have removed all words after COMP_CWORD, we can safely assume + # that COMP_CWORD_NO_FLAGS is simply the index of the last element + local COMP_CWORD_NO_FLAGS=$((${#COMP_WORDS_NO_FLAGS[@]} - 1)) + + # There is no guarantee that the cursor is at the end of the command line + # when tab completion is envoked. For example, in the following situation: + # `spack -d [] install` + # if the user presses the TAB key, a list of valid flags should be listed. + # Note that we cannot simply ignore everything after the cursor. In the + # previous scenario, the user should expect to see a list of flags, but + # not of other subcommands. Obviously, `spack -d list install` would be + # invalid syntax. To accomplish this, we use the variable list_options + # which is true if the current word starts with '-' or if the cursor is + # not at the end of the line. + local list_options=false + if [[ "${COMP_WORDS[$COMP_CWORD]}" == -* || "$COMP_POINT" -ne "${#COMP_LINE}" ]] + then + list_options=true + fi + + # In general, when envoking tab completion, the user is not expecting to + # see optional flags mixed in with subcommands or package names. Tab + # completion is used by those who are either lazy or just bad at spelling. + # If someone doesn't remember what flag to use, seeing single letter flags + # in their results won't help them, and they should instead consult the + # documentation. However, if the user explicitly declares that they are + # looking for a flag, we can certainly help them out. + # `spack install -[]` + # and + # `spack install --[]` + # should list all flags and long flags, respectively. Furthermore, if a + # subcommand has no non-flag completions, such as `spack arch []`, it + # should list flag completions. + + local cur=${COMP_WORDS_NO_FLAGS[$COMP_CWORD_NO_FLAGS]} + + # If the cursor is in the middle of the line, like: + # `spack -d [] install` + # COMP_WORDS will not contain the empty character, so we have to add it. + if [[ "${COMP_LINE:$COMP_POINT:1}" == " " ]] + then + cur="" + fi + + # Uncomment this line to enable logging + #_test_vars >> temp + + # Make sure function exists before calling it + if [[ "$(type -t $subfunction)" == "function" ]] + then + $subfunction + COMPREPLY=($(compgen -W "$SPACK_COMPREPLY" -- "$cur")) + fi +} + +# Helper functions for subcommands +# Results of each query are cached via environment variables + +_subcommands() { + if [[ -z "${SPACK_SUBCOMMANDS:-}" ]] + then + SPACK_SUBCOMMANDS="$(spack commands)" + fi + SPACK_COMPREPLY="$SPACK_SUBCOMMANDS" +} + +_all_packages() { + if [[ -z "${SPACK_ALL_PACKAGES:-}" ]] + then + SPACK_ALL_PACKAGES="$(spack list)" + fi + SPACK_COMPREPLY="$SPACK_ALL_PACKAGES" +} + +_all_resource_hashes() { + if [[ -z "${SPACK_ALL_RESOURCES_HASHES:-}" ]] + then + SPACK_ALL_RESOURCE_HASHES="$(spack resource list --only-hashes)" + fi + SPACK_COMPREPLY="$SPACK_ALL_RESOURCE_HASHES" +} + +_installed_packages() { + if [[ -z "${SPACK_INSTALLED_PACKAGES:-}" ]] + then + SPACK_INSTALLED_PACKAGES="$(spack --color=never find --no-groups)" + fi + SPACK_COMPREPLY="$SPACK_INSTALLED_PACKAGES" +} + +_installed_compilers() { + if [[ -z "${SPACK_INSTALLED_COMPILERS:-}" ]] + then + SPACK_INSTALLED_COMPILERS="$(spack compilers | egrep -v "^(-|=)")" + fi + SPACK_COMPREPLY="$SPACK_INSTALLED_COMPILERS" +} + +_providers() { + if [[ -z "${SPACK_PROVIDERS:-}" ]] + then + SPACK_PROVIDERS="$(spack providers)" + fi + SPACK_COMPREPLY="$SPACK_PROVIDERS" +} + +_mirrors() { + if [[ -z "${SPACK_MIRRORS:-}" ]] + then + SPACK_MIRRORS="$(spack mirror list | awk '{print $1}')" + fi + SPACK_COMPREPLY="$SPACK_MIRRORS" +} + +_repos() { + if [[ -z "${SPACK_REPOS:-}" ]] + then + SPACK_REPOS="$(spack repo list | awk '{print $1}')" + fi + SPACK_COMPREPLY="$SPACK_REPOS" +} + +_tests() { + if [[ -z "${SPACK_TESTS:-}" ]] + then + SPACK_TESTS="$(spack test -l)" + fi + SPACK_COMPREPLY="$SPACK_TESTS" +} + +_environments() { + if [[ -z "${SPACK_ENVIRONMENTS:-}" ]] + then + SPACK_ENVIRONMENTS="$(spack env list)" + fi + SPACK_COMPREPLY="$SPACK_ENVIRONMENTS" +} + +_keys() { + if [[ -z "${SPACK_KEYS:-}" ]] + then + SPACK_KEYS="$(spack gpg list)" + fi + SPACK_COMPREPLY="$SPACK_KEYS" +} + +_config_sections() { + if [[ -z "${SPACK_CONFIG_SECTIONS:-}" ]] + then + SPACK_CONFIG_SECTIONS="compilers mirrors repos packages modules config upstreams" + fi + SPACK_COMPREPLY="$SPACK_CONFIG_SECTIONS" +} + +_extensions() { + if [[ -z "${SPACK_EXTENSIONS:-}" ]] + then + SPACK_EXTENSIONS="aspell go-bootstrap go icedtea jdk kim-api lua matlab mofem-cephas octave openjdk perl python r ruby rust tcl yorick" + fi + SPACK_COMPREPLY="$SPACK_EXTENSIONS" +} + +# Testing functions + +# Function for unit testing tab completion +# Syntax: _spack_completions spack install py- +_spack_completions() { + local COMP_CWORD COMP_KEY COMP_LINE COMP_POINT COMP_TYPE COMP_WORDS COMPREPLY + + # Set each variable the way bash would + COMP_LINE="$*" + COMP_POINT=${#COMP_LINE} + COMP_WORDS=("$@") + if [[ ${COMP_LINE: -1} == ' ' ]] + then + COMP_WORDS+=('') + fi + COMP_CWORD=$((${#COMP_WORDS[@]} - 1)) + COMP_KEY=9 # ASCII 09: Horizontal Tab + COMP_TYPE=64 # ASCII 64: '@', to list completions if the word is not unmodified + + # Run Spack's tab completion function + _bash_completion_spack + + # Return the result + echo "${COMPREPLY[@]:-}" +} + +# Log the environment variables used +# Syntax: _test_vars >> temp +_test_vars() { + echo "-----------------------------------------------------" + echo "Variables set by bash:" + echo + echo "COMP_LINE: '$COMP_LINE'" + echo "# COMP_LINE: '${#COMP_LINE}'" + echo "COMP_WORDS: $(_pretty_print COMP_WORDS[@])" + echo "# COMP_WORDS: '${#COMP_WORDS[@]}'" + echo "COMP_CWORD: '$COMP_CWORD'" + echo "COMP_KEY: '$COMP_KEY'" + echo "COMP_POINT: '$COMP_POINT'" + echo "COMP_TYPE: '$COMP_TYPE'" + echo "COMP_WORDBREAKS: '$COMP_WORDBREAKS'" + echo + echo "Intermediate variables:" + echo + echo "COMP_WORDS_NO_FLAGS: $(_pretty_print COMP_WORDS_NO_FLAGS[@])" + echo "# COMP_WORDS_NO_FLAGS: '${#COMP_WORDS_NO_FLAGS[@]}'" + echo "COMP_CWORD_NO_FLAGS: '$COMP_CWORD_NO_FLAGS'" + echo + echo "Subfunction: '$subfunction'" + if $list_options + then + echo "List options: 'True'" + else + echo "List options: 'False'" + fi + echo "Current word: '$cur'" +} + +# Pretty-prints one or more arrays +# Syntax: _pretty_print array1[@] ... +_pretty_print() { + for arg in $@ + do + local array=("${!arg}") + printf "$arg: [" + printf "'%s'" "${array[0]}" + printf ", '%s'" "${array[@]:1}" + echo "]" + done +} + +complete -o bashdefault -o default -F _bash_completion_spack spack + +# Spack commands +# +# Everything below here is auto-generated. diff --git a/share/spack/qa/completion-test.sh b/share/spack/qa/completion-test.sh new file mode 100755 index 0000000000..5b326b4a6d --- /dev/null +++ b/share/spack/qa/completion-test.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# +# Copyright 2013-2020 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) + +# +# This script tests that Spack's tab completion scripts work. +# +# The tests are portable to bash, zsh, and bourne shell, and can be run +# in any of these shells. +# + +export QA_DIR=$(dirname "$0") +export SHARE_DIR=$(cd "$QA_DIR/.." && pwd) +export SPACK_ROOT=$(cd "$QA_DIR/../../.." && pwd) + +. "$QA_DIR/test-framework.sh" + +# Fail on undefined variables +set -u + +# Source setup-env.sh before tests +. "$SHARE_DIR/setup-env.sh" +. "$SHARE_DIR/spack-completion.$_sp_shell" + +title "Testing spack-completion.$_sp_shell with $_sp_shell" + +# Spack command is now available +succeeds which spack + +title 'Testing all subcommands' +while IFS= read -r line +do + # Test that completion with no args works + succeeds _spack_completions ${line[*]} '' + + # Test that completion with flags works + contains '-h --help' _spack_completions ${line[*]} - +done <<- EOF + $(spack commands --aliases --format=subcommands) +EOF + +title 'Testing for correct output' +contains 'compiler' _spack_completions spack '' +contains 'install' _spack_completions spack inst +contains 'find' _spack_completions spack help '' +contains 'hdf5' _spack_completions spack list '' +contains 'py-numpy' _spack_completions spack list py- +contains 'mpi' _spack_completions spack providers '' +contains 'builtin' _spack_completions spack repo remove '' +contains 'packages' _spack_completions spack config edit '' +contains 'python' _spack_completions spack extensions '' +contains 'hdf5' _spack_completions spack -d install --jobs 8 '' +contains 'hdf5' _spack_completions spack install -v '' + +# XFAIL: Fails for Python 2.6 because pkg_resources not found? +#contains 'compilers.py' _spack_completions spack test '' + +title 'Testing debugging functions' + +# This is a particularly tricky case that involves the following situation: +# `spack -d [] install ` +# Here, [] represents the cursor, which is in the middle of the line. +# We should tab-complete optional flags for `spack`, not optional flags for +# `spack install` or package names. +COMP_LINE='spack -d install ' +COMP_POINT=9 +COMP_WORDS=(spack -d install) +COMP_CWORD=2 +COMP_KEY=9 +COMP_TYPE=64 + +_bash_completion_spack +contains "--all-help" echo "${COMPREPLY[@]}" + +contains "['spack', '-d', 'install', '']" _pretty_print COMP_WORDS[@] + +# Set the rest of the intermediate variables manually +COMP_WORDS_NO_FLAGS=(spack install) +COMP_CWORD_NO_FLAGS=1 +subfunction=_spack +cur= + +list_options=true +contains "'True'" _test_vars +list_options=false +contains "'False'" _test_vars diff --git a/share/spack/qa/run-unit-tests b/share/spack/qa/run-unit-tests index 8ba6eed350..52748dacdf 100755 --- a/share/spack/qa/run-unit-tests +++ b/share/spack/qa/run-unit-tests @@ -23,7 +23,7 @@ ORIGINAL_PATH="$PATH" . "$(dirname $0)/setup.sh" -check_dependencies ${coverage} git hg svn +check_dependencies $coverage git hg svn # Move to root directory of Spack # Allows script to be run from anywhere @@ -46,7 +46,7 @@ extra_args="" if [[ -n "$@" ]]; then extra_args="-k $@" fi -${coverage_run} bin/spack test -x --verbose "$extra_args" +$coverage_run bin/spack test -x --verbose "$extra_args" #----------------------------------------------------------- # Run tests for setup-env.sh @@ -57,15 +57,18 @@ export PATH="$ORIGINAL_PATH" unset spack # start in the spack root directory -cd $SPACK_ROOT +cd "$SPACK_ROOT" # Run bash tests with coverage enabled, but pipe output to /dev/null # because it seems that kcov seems to undo the script's redirection if [ "$BASH_COVERAGE" = true ]; then - ${QA_DIR}/bashcov ${QA_DIR}/setup-env-test.sh &> /dev/null + "$QA_DIR/bashcov" "$QA_DIR/setup-env-test.sh" &> /dev/null + "$QA_DIR/bashcov" "$QA_DIR/completion-test.sh" &> /dev/null fi # run the test scripts for their output (these will print nicely) -bash ${QA_DIR}/setup-env-test.sh -zsh ${QA_DIR}/setup-env-test.sh -dash ${QA_DIR}/setup-env-test.sh +bash "$QA_DIR/setup-env-test.sh" +zsh "$QA_DIR/setup-env-test.sh" +dash "$QA_DIR/setup-env-test.sh" + +bash "$QA_DIR/completion-test.sh" diff --git a/share/spack/qa/setup-env-test.sh b/share/spack/qa/setup-env-test.sh index 7613637984..66284d1a96 100755 --- a/share/spack/qa/setup-env-test.sh +++ b/share/spack/qa/setup-env-test.sh @@ -12,159 +12,11 @@ # in any of these shells. # -# ------------------------------------------------------------------------ -# Functions for color output. -# ------------------------------------------------------------------------ - -# Colors for output -red='\033[1;31m' -cyan='\033[1;36m' -green='\033[1;32m' -reset='\033[0m' - -echo_red() { - printf "${red}$*${reset}\n" -} - -echo_green() { - printf "${green}$*${reset}\n" -} - -echo_msg() { - printf "${cyan}$*${reset}\n" -} - -# ------------------------------------------------------------------------ -# Generic functions for testing shell code. -# ------------------------------------------------------------------------ - -# counts of test successes and failures. -success=0 -errors=0 - -# Print out a header for a group of tests. -title() { - echo - echo_msg "$@" - echo_msg "---------------------------------" -} - -# echo FAIL in red text; increment failures -fail() { - echo_red FAIL - errors=$((errors+1)) -} - -# -# Echo SUCCESS in green; increment successes -# -pass() { - echo_green SUCCESS - success=$((success+1)) -} - -# -# Run a command and suppress output unless it fails. -# On failure, echo the exit code and output. -# -succeeds() { - printf "'%s' succeeds ... " "$*" - output=$($* 2>&1) - err="$?" - - if [ "$err" != 0 ]; then - fail - echo_red "Command failed with error $err." - if [ -n "$output" ]; then - echo_msg "Output:" - echo "$output" - else - echo_msg "No output." - fi - else - pass - fi -} - -# -# Run a command and suppress output unless it succeeds. -# If the command succeeds, echo the output. -# -fails() { - printf "'%s' fails ... " "$*" - output=$("$@" 2>&1) - err="$?" - - if [ "$err" = 0 ]; then - fail - echo_red "Command failed with error $err." - if [ -n "$output" ]; then - echo_msg "Output:" - echo "$output" - else - echo_msg "No output." - fi - else - pass - fi -} - -# -# Ensure that a string is in the output of a command. -# Suppresses output on success. -# On failure, echo the exit code and output. -# -contains() { - string="$1" - shift - - printf "'%s' output contains '$string' ... " "$*" - output=$("$@" 2>&1) - err="$?" - - if [ "${output#*$string}" = "${output}" ]; then - fail - echo_red "Command exited with error $err." - echo_red "'$string' was not in output." - if [ -n "$output" ]; then - echo_msg "Output:" - echo "$output" - else - echo_msg "No output." - fi - else - pass - fi -} - -# -# Ensure that a variable is set. -# -is_set() { - printf "'%s' is set ... " "$1" - if eval "[ -z \${${1:-}+x} ]"; then - fail - echo_msg "$1 was not set!" - else - pass - fi -} - -# -# Ensure that a variable is not set. -# Fails and prints the value of the variable if it is set. -# -is_not_set() { - printf "'%s' is not set ... " "$1" - if eval "[ ! -z \${${1:-}+x} ]"; then - fail - echo_msg "$1 was set:" - echo " $1" - else - pass - fi -} +export QA_DIR=$(dirname "$0") +export SHARE_DIR=$(cd "$QA_DIR/.." && pwd) +export SPACK_ROOT=$(cd "$QA_DIR/../../.." && pwd) +. "$QA_DIR/test-framework.sh" # ----------------------------------------------------------------------- # Instead of invoking the module commands, we print the @@ -184,28 +36,28 @@ module() { # Make sure no environment is active unset SPACK_ENV -# fail on undefined variables +# Fail on undefined variables set -u # Source setup-env.sh before tests -. share/spack/setup-env.sh +. "$SHARE_DIR/setup-env.sh" -# bash should expand aliases even when non-interactive +# Bash should expand aliases even when non-interactive if [ -n "${BASH:-}" ]; then shopt -s expand_aliases fi title "Testing setup-env.sh with $_sp_shell" -# spack command is now avaialble +# Spack command is now available succeeds which spack -# mock cd command (intentionally define only AFTER setup-env.sh) +# Mock cd command (intentionally define only AFTER setup-env.sh) cd() { echo cd "$@" } -# create a fake mock package install and store its location for later +# Create a fake mock package install and store its location for later title "Setup" echo "Creating a mock package installation" spack -m install --fake a @@ -215,19 +67,13 @@ a_module=$(spack -m module tcl find a) b_install=$(spack location -i b) b_module=$(spack -m module tcl find b) -# create a test environment for tesitng environment commands +# Create a test environment for testing environment commands echo "Creating a mock environment" spack env create spack_test_env test_env_location=$(spack location -e spack_test_env) -# ensure that we uninstall b on exit +# Ensure that we uninstall b on exit cleanup() { - if [ "$?" != 0 ]; then - trapped_error=true - else - trapped_error=false - fi - echo "Removing test environment before exiting." spack env deactivate 2>&1 > /dev/null spack env rm -y spack_test_env @@ -235,24 +81,7 @@ cleanup() { title "Cleanup" echo "Removing test packages before exiting." spack -m uninstall -yf b a - - echo - echo "$success tests succeeded." - echo "$errors tests failed." - - if [ "$trapped_error" = true ]; then - echo "Exited due to an error." - fi - - if [ "$errors" = 0 ] && [ "$trapped_error" = false ]; then - pass - exit 0 - else - fail - exit 1 - fi } -trap cleanup EXIT # ----------------------------------------------------------------------- # Test all spack commands with special env support diff --git a/share/spack/qa/test-framework.sh b/share/spack/qa/test-framework.sh new file mode 100755 index 0000000000..14b58bbecf --- /dev/null +++ b/share/spack/qa/test-framework.sh @@ -0,0 +1,195 @@ +# Copyright 2013-2020 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) + +# +# A testing framework for any POSIX-compatible shell. +# + +# ------------------------------------------------------------------------ +# Functions for color output. +# ------------------------------------------------------------------------ + +# Colors for output +red='\033[1;31m' +cyan='\033[1;36m' +green='\033[1;32m' +reset='\033[0m' + +echo_red() { + printf "${red}$*${reset}\n" +} + +echo_green() { + printf "${green}$*${reset}\n" +} + +echo_msg() { + printf "${cyan}$*${reset}\n" +} + +# ------------------------------------------------------------------------ +# Generic functions for testing shell code. +# ------------------------------------------------------------------------ + +# counts of test successes and failures. +success=0 +errors=0 + +# Print out a header for a group of tests. +title() { + echo + echo_msg "$@" + echo_msg "---------------------------------" +} + +# echo FAIL in red text; increment failures +fail() { + echo_red FAIL + errors=$((errors+1)) +} + +# +# Echo SUCCESS in green; increment successes +# +pass() { + echo_green SUCCESS + success=$((success+1)) +} + +# +# Run a command and suppress output unless it fails. +# On failure, echo the exit code and output. +# +succeeds() { + printf "'%s' succeeds ... " "$*" + output=$("$@" 2>&1) + err="$?" + + if [ "$err" != 0 ]; then + fail + echo_red "Command failed with error $err." + if [ -n "$output" ]; then + echo_msg "Output:" + echo "$output" + else + echo_msg "No output." + fi + else + pass + fi +} + +# +# Run a command and suppress output unless it succeeds. +# If the command succeeds, echo the output. +# +fails() { + printf "'%s' fails ... " "$*" + output=$("$@" 2>&1) + err="$?" + + if [ "$err" = 0 ]; then + fail + echo_red "Command failed with error $err." + if [ -n "$output" ]; then + echo_msg "Output:" + echo "$output" + else + echo_msg "No output." + fi + else + pass + fi +} + +# +# Ensure that a string is in the output of a command. +# Suppresses output on success. +# On failure, echo the exit code and output. +# +contains() { + string="$1" + shift + + printf "'%s' output contains '$string' ... " "$*" + output=$("$@" 2>&1) + err="$?" + + if [ "${output#*$string}" = "${output}" ]; then + fail + echo_red "Command exited with error $err." + echo_red "'$string' was not in output." + if [ -n "$output" ]; then + echo_msg "Output:" + echo "$output" + else + echo_msg "No output." + fi + else + pass + fi +} + +# +# Ensure that a variable is set. +# +is_set() { + printf "'%s' is set ... " "$1" + if eval "[ -z \${${1:-}+x} ]"; then + fail + echo_msg "$1 was not set!" + else + pass + fi +} + +# +# Ensure that a variable is not set. +# Fails and prints the value of the variable if it is set. +# +is_not_set() { + printf "'%s' is not set ... " "$1" + if eval "[ ! -z \${${1:-}+x} ]"; then + fail + echo_msg "$1 was set:" + echo " $1" + else + pass + fi +} + +# +# Report the number of tests that succeeded and failed on exit. +# +teardown() { + if [ "$?" != 0 ]; then + trapped_error=true + else + trapped_error=false + fi + + if type cleanup &> /dev/null + then + cleanup + fi + + echo + echo "$success tests succeeded." + echo "$errors tests failed." + + if [ "$trapped_error" = true ]; then + echo "Exited due to an error." + fi + + if [ "$errors" = 0 ] && [ "$trapped_error" = false ]; then + pass + exit 0 + else + fail + exit 1 + fi +} + +trap teardown EXIT diff --git a/share/spack/qa/update-completion-scripts.sh b/share/spack/qa/update-completion-scripts.sh new file mode 100755 index 0000000000..8fcd321457 --- /dev/null +++ b/share/spack/qa/update-completion-scripts.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# +# Copyright 2013-2020 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) + +# Updates Spack's shell tab completion scripts + +# Switch to parent directory +QA_DIR="$(dirname "${BASH_SOURCE[0]}")" +cd "$QA_DIR/.." + +# Update each shell +for shell in bash # zsh fish +do + header=$shell/spack-completion.in + script=spack-completion.$shell + + rm -f $script + spack commands --aliases --format=$shell --header=$header --update=$script + chmod +x $script +done diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash index a8dcbff5c0..0284e81113 100755 --- a/share/spack/spack-completion.bash +++ b/share/spack/spack-completion.bash @@ -4,16 +4,41 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) -# The following global variables are used/set by Bash programmable completion -# COMP_CWORD: An index into ${COMP_WORDS} of the word containing the -# current cursor position -# COMP_LINE: The current command line -# COMP_WORDS: an array containing individual command arguments typed so far -# COMPREPLY: an array containing possible completions as a result of your -# function +# NOTE: spack-completion.bash is auto-generated by: +# +# $ spack commands --aliases --format=bash +# --header=bash/spack-completion.in --update=spack-completion.bash +# +# Please do not manually modify this file. + + +# The following global variables are set by Bash programmable completion: +# +# COMP_CWORD: An index into ${COMP_WORDS} of the word containing the +# current cursor position +# COMP_KEY: The key (or final key of a key sequence) used to invoke +# the current completion function +# COMP_LINE: The current command line +# COMP_POINT: The index of the current cursor position relative to the +# beginning of the current command +# COMP_TYPE: Set to an integer value corresponding to the type of +# completion attempted that caused a completion function +# to be called +# COMP_WORDBREAKS: The set of characters that the readline library treats +# as word separators when performing word completion +# COMP_WORDS: An array variable consisting of the individual words in +# the current command line +# +# The following global variable is used by Bash programmable completion: +# +# COMPREPLY: An array variable from which bash reads the possible +# completions generated by a shell function invoked by the +# programmable completion facility +# +# See `man bash` for more details. # Bash programmable completion for Spack -_bash_completion_spack () { +_bash_completion_spack() { # In all following examples, let the cursor be denoted by brackets, i.e. [] # For our purposes, flags should not affect tab completion. For instance, @@ -46,7 +71,7 @@ _bash_completion_spack () { # Since we have removed all words after COMP_CWORD, we can safely assume # that COMP_CWORD_NO_FLAGS is simply the index of the last element - local COMP_CWORD_NO_FLAGS=$(( ${#COMP_WORDS_NO_FLAGS[@]} - 1 )) + local COMP_CWORD_NO_FLAGS=$((${#COMP_WORDS_NO_FLAGS[@]} - 1)) # There is no guarantee that the cursor is at the end of the command line # when tab completion is envoked. For example, in the following situation: @@ -59,8 +84,7 @@ _bash_completion_spack () { # which is true if the current word starts with '-' or if the cursor is # not at the end of the line. local list_options=false - if [[ "${COMP_WORDS[$COMP_CWORD]}" == -* || \ - "$COMP_CWORD" -ne "${#COMP_WORDS[@]}-1" ]] + if [[ "${COMP_WORDS[$COMP_CWORD]}" == -* || "$COMP_POINT" -ne "${#COMP_LINE}" ]] then list_options=true fi @@ -80,1266 +104,1488 @@ _bash_completion_spack () { # should list flag completions. local cur=${COMP_WORDS_NO_FLAGS[$COMP_CWORD_NO_FLAGS]} - local prev=${COMP_WORDS_NO_FLAGS[$COMP_CWORD_NO_FLAGS-1]} - #_test_vars + # If the cursor is in the middle of the line, like: + # `spack -d [] install` + # COMP_WORDS will not contain the empty character, so we have to add it. + if [[ "${COMP_LINE:$COMP_POINT:1}" == " " ]] + then + cur="" + fi + + # Uncomment this line to enable logging + #_test_vars >> temp # Make sure function exists before calling it if [[ "$(type -t $subfunction)" == "function" ]] then - COMPREPLY=($($subfunction)) + $subfunction + COMPREPLY=($(compgen -W "$SPACK_COMPREPLY" -- "$cur")) fi } -# Spack commands - -_spack () { - if $list_options - then - compgen -W "-h --help -H --all-help --color -C --config-scope -d --debug --timestamp --pdb -e --env -D --env-dir -E --no-env --use-env-repo -k --insecure -l --enable-locks -L --disable-locks -m --mock -p --profile --sorted-profile --lines -v --verbose --stacktrace -V --version --print-shell-vars" -- "$cur" - else - compgen -W "$(_subcommands)" -- "$cur" - fi -} - -_spack_activate () { - if $list_options - then - compgen -W "-h --help -f --force -v --view" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_add () { - if $list_options - then - compgen -W "-h --help -l --list-name" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_arch () { - compgen -W "-h --help --known-targets -p --platform -o --operating-system -t --target -f --frontend -b --backend" -- "$cur" -} - -_spack_blame () { - if $list_options - then - compgen -W "-h --help -t --time -p --percent -g --git" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_bootstrap () { - compgen -W "-h --help -j --jobs --keep-prefix --keep-stage -n --no-checksum -v --verbose --use-cache --no-cache --cache-only --clean --dirty" -- "$cur" -} - -_spack_build () { - if $list_options - then - compgen -W "-h --help -v --verbose" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_build_env () { - if $list_options - then - compgen -W "-h --help --clean --dirty --dump --pickle" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_buildcache () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "create install list keys preview check download get-buildcache-name save-yaml copy update-index" -- "$cur" - fi -} - -_spack_buildcache_create () { - if $list_options - then - compgen -W "-h --help -r --rel -f --force -u --unsigned -a --allow-root -k --key -d --directory --no-rebuild-index -y --spec-yaml --no-deps" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_buildcache_install () { - if $list_options - then - compgen -W "-h --help -f --force -m --multiple -a --allow-root -u --unsigned" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_buildcache_list () { - if $list_options - then - compgen -W "-h --help -l --long -L --very-long -v --variants -f --force" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_buildcache_keys () { - compgen -W "-h --help -i --install -t --trust -f --force" -- "$cur" -} - -_spack_buildcache_preview () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_buildcache_check () { - compgen -W "-h --help -m --mirror-url -o --output-file --scope -s --spec -y --spec-yaml --rebuild-on-error" -- "$cur" -} - -_spack_buildcache_download () { - compgen -W "-h --help -s --spec -y --spec-yaml -p --path -c --require-cdashid" -- "$cur" -} - -_spack_buildcache_get_buildcache_name () { - compgen -W "-h --help -s --spec -y --spec-yaml" -- "$cur" -} - -_spack_buildcache_save_yaml () { - compgen -W "-h --help --root-spec --root-spec-yaml -s --specs -y --yaml-dir" -- "$cur" -} - -_spack_buildcache_copy () { - compgen -W "-h --help --base-dir --spec-yaml --destination-url" -- "$cur" -} - -_spack_buildcache_update_index () { - compgen -W "-h --help -d --mirror-url" -- "$cur" -} - -_spack_cd () { - if $list_options - then - compgen -W "-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages -b --build-dir -e --env" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_checksum () { - if $list_options - then - compgen -W "-h --help --keep-stage" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_clean () { - if $list_options - then - compgen -W "-h --help -s --stage -d --downloads -m --misc-cache -p --python-cache -a --all" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_clone () { - if $list_options - then - compgen -W "-h --help -r --remote" -- "$cur" - fi -} - -_spack_commands () { - if $list_options - then - compgen -W "-h --help --format --header --update" -- "$cur" - fi -} - -_spack_compiler () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "find add remove rm list info" -- "$cur" - fi -} - -_spack_compiler_find () { - if $list_options - then - compgen -W "-h --help --scope" -- "$cur" - fi -} - -_spack_compiler_add () { - # Alias to `spack compiler find` - _spack_compiler_find -} - -_spack_compiler_remove () { - if $list_options - then - compgen -W "-h --help -a --all --scope" -- "$cur" - else - compgen -W "$(_installed_compilers)" -- "$cur" - fi -} - -_spack_compiler_rm () { - # Alias to `spack compiler remove` - _spack_compiler_remove -} - -_spack_compiler_list () { - compgen -W "-h --help --scope" -- "$cur" -} - -_spack_compiler_info () { - if $list_options - then - compgen -W "-h --help --scope" -- "$cur" - else - compgen -W "$(_installed_compilers)" -- "$cur" - fi -} - -_spack_compilers () { - # Alias to `spack compiler list` - _spack_compiler_list -} - -_spack_concretize () { - compgen -W "-h --help -f --force" -- "$cur" -} - -_spack_config () { - if $list_options - then - compgen -W "-h --help --scope" -- "$cur" - else - compgen -W "get blame edit" -- "$cur" - fi -} - -_spack_config_get () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "compilers mirrors repos packages modules config upstreams" -- "$cur" - fi -} - -_spack_config_blame () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "compilers mirrors repos packages modules config upstreams" -- "$cur" - fi -} - -_spack_config_edit () { - if $list_options - then - compgen -W "-h --help --print-file" -- "$cur" - else - compgen -W "compilers mirrors repos packages modules config upstreams" -- "$cur" - fi -} - -_spack_configure () { - if $list_options - then - compgen -W "-h --help -v --verbose" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_create () { - if $list_options - then - compgen -W "-h --help --keep-stage -n --name -t --template -r --repo -N --namespace -f --force --skip-editor" -- "$cur" - fi -} - -_spack_deactivate () { - if $list_options - then - compgen -W "-h --help -f --force -v --view -a --all" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_debug () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "create-db-tarball" -- "$cur" - fi -} - -_spack_debug_create_db_tarball () { - compgen -W "-h --help" -- "$cur" -} - -_spack_dependencies () { - if $list_options - then - compgen -W "-h --help -i --installed -t --transitive --deptype -V --no-expand-virtuals" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_dependents () { - if $list_options - then - compgen -W "-h --help -i --installed -t --transitive" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_deprecate () { - if $list_options - then - compgen -W "-h --help -y --yes-to-all -d --dependencies -D --no-dependencies -i --install-deprecator -I --no-install-deprecator -l --link-type" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_dev_build () { - if $list_options - then - compgen -W "-h --help -j --jobs -d --source-path -i --ignore-dependencies -n --no-checksum --keep-prefix --skip-patch -q --quiet -u --until --clean --dirty" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_diy () { - if $list_options - then - compgen -W "-h --help -j --jobs -d --source-path -i --ignore-dependencies -n --no-checksum --keep-prefix --skip-patch -q --quiet -u --until --clean --dirty" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_docs () { - compgen -W "-h --help" -- "$cur" -} - -_spack_edit () { - if $list_options - then - compgen -W "-h --help -b --build-system -c --command -d --docs -t --test -m --module -r --repo -N --namespace" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_env () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "activate deactivate create remove rm list ls status st loads view" -- "$cur" - fi -} - -_spack_env_activate () { - if $list_options - then - compgen -W "-h --help --sh --csh -v --with-view -V --without-view -d --dir -p --prompt" -- "$cur" - else - compgen -W "$(_environments)" -- "$cur" - fi -} - -_spack_env_deactivate () { - compgen -W "-h --help --sh --csh" -- "$cur" -} - -_spack_env_create () { - if $list_options - then - compgen -W "-h --help -d --dir --without-view --with-view" -- "$cur" - fi -} - -_spack_env_remove () { - if $list_options - then - compgen -W "-h --help -y --yes-to-all" -- "$cur" - else - compgen -W "$(_environments)" -- "$cur" - fi -} - -_spack_env_rm () { - # Alias to `spack env remove` - _spack_env_remove -} - -_spack_env_list () { - compgen -W "-h --help" -- "$cur" -} - -_spack_env_ls () { - # Alias to `spack env list` - _spack_env_list -} - -_spack_env_status () { - compgen -W "-h --help" -- "$cur" -} - -_spack_env_st () { - # Alias to `spack env status` - _spack_env_status -} - -_spack_env_loads () { - if $list_options - then - compgen -W "-h --help -m --module-type --input-only -p --prefix -x --exclude -r --dependencies" -- "$cur" - else - compgen -W "$(_environments)" -- "$cur" - fi -} - -_spack_env_view () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "regenerate enable disable" -- "$cur" - fi -} - -_spack_extensions () { - if $list_options - then - compgen -W "-h --help -l --long -L --very-long -d --deps -p --paths -s --show -v --view" -- "$cur" - else - compgen -W "aspell go-bootstrap go icedtea jdk kim-api lua matlab mofem-cephas octave openjdk perl python r ruby rust tcl yorick" -- "$cur" - fi -} - -_spack_fetch () { - if $list_options - then - compgen -W "-h --help -n --no-checksum -m --missing -D --dependencies" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_find () { - if $list_options - then - compgen -W "-h --help --format --json -d --deps -p --paths --groups --no-groups -l --long -L --very-long -t --tags -c --show-concretized -f --show-flags --show-full-compiler -x --explicit -X --implicit -u --unknown -m --missing -v --variants -M --only-missing --deprecated --only-deprecated -N --namespace --start-date --end-date" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_flake8 () { - if $list_options - then - compgen -W "-h --help -b --base -k --keep-temp -a --all -o --output -r --root-relative -U --no-untracked" -- "$cur" - fi -} - -_spack_gpg () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "verify trust untrust sign create list init export" -- "$cur" - fi -} - -_spack_gpg_verify () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(installed_packages)" -- "$cur" - fi -} - -_spack_gpg_trust () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_gpg_untrust () { - if $list_options - then - compgen -W "-h --help --signing" -- "$cur" - else - compgen -W "$(_keys)" -- "$cur" - fi -} - -_spack_gpg_sign () { - if $list_options - then - compgen -W "-h --help --output --key --clearsign" -- "$cur" - else - compgen -W "$(installed_packages)" -- "$cur" - fi -} - -_spack_gpg_create () { - if $list_options - then - compgen -W "-h --help --comment --expires --export" -- "$cur" - fi -} - -_spack_gpg_list () { - compgen -W "-h --help --trusted --signing" -- "$cur" -} - -_spack_gpg_init () { - compgen -W "-h --help" -- "$cur" -} - -_spack_gpg_export () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_keys)" -- "$cur" - fi -} - -_spack_graph () { - if $list_options - then - compgen -W "-h --help -a --ascii -d --dot -s --static -i --installed --deptype" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_help () { - if $list_options - then - compgen -W "-h --help -a --all --spec" -- "$cur" - else - compgen -W "$(_subcommands)" -- "$cur" - fi -} - -_spack_info () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_install () { - if $list_options - then - compgen -W "-h --help --only -u --until -j --jobs --overwrite --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --show-log-on-error --source -n --no-checksum -v --verbose --fake --only-concrete -f --file --clean --dirty --test --run-tests --log-format --log-file --help-cdash -y --yes-to-all --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_license () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "list-files verify" -- "$cur" - fi -} - -_spack_license_list_files () { - compgen -W "-h --help" -- "$cur" -} - -_spack_license_verify () { - compgen -W "-h --help --root" -- "$cur" -} - -_spack_list () { - if $list_options - then - compgen -W "-h --help -d --search-description --format --update -t --tags" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_load () { - if $list_options - then - compgen -W "-h --help -r --dependencies" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_location () { - if $list_options - then - compgen -W "-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages -b --build-dir -e --env" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_log_parse () { - if $list_options - then - compgen -W "-h --help --show -c --context -p --profile -w --width -j --jobs" -- "$cur" - fi -} - -_spack_maintainers () { - if $list_options - then - compgen -W "-h --help --maintained --unmaintained -a --all --by-user" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_mirror () { - if $list_options - then - compgen -W "-h --help -n --no-checksum" -- "$cur" - else - compgen -W "create add remove rm set-url list" -- "$cur" - fi -} - -_spack_mirror_create () { - if $list_options - then - compgen -W "-h --help -d --directory -a --all -f --file -D --dependencies -n --versions-per-spec" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_mirror_add () { - if $list_options - then - compgen -W "-h --help --scope" -- "$cur" - fi -} - -_spack_mirror_remove () { - if $list_options - then - compgen -W "-h --help --scope" -- "$cur" - else - compgen -W "$(_mirrors)" -- "$cur" - fi -} - -_spack_mirror_rm () { - # Alias to `spack mirror remove` - _spack_mirror_remove -} - -_spack_mirror_set_url () { - if $list_options - then - compgen -W "-h --help --push --scope" -- "$cur" - else - compgen -W "$(_mirrors)" -- "$cur" - fi -} - -_spack_mirror_list () { - compgen -W "-h --help --scope" -- "$cur" -} - -_spack_module () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "lmod tcl" -- "$cur" - fi -} - -_spack_module_lmod () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "refresh find rm loads setdefault" -- "$cur" - fi -} - -_spack_module_lmod_refresh () { - if $list_options - then - compgen -W "-h --help --delete-tree --upstream-modules -y --yes-to-all" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_module_lmod_find () { - if $list_options - then - compgen -W "-h --help --full-path -r --dependencies" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_module_lmod_rm () { - if $list_options - then - compgen -W "-h --help -y --yes-to-all" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_module_lmod_loads () { - if $list_options - then - compgen -W "-h --help --input-only -p --prefix -x --exclude -r --dependencies" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi - -} - -_spack_module_lmod_setdefault () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_module_tcl () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "refresh find rm loads" -- "$cur" - fi -} - -_spack_module_tcl_refresh () { - if $list_options - then - compgen -W "-h --help --delete-tree --upstream-modules -y --yes-to-all" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_module_tcl_find () { - if $list_options - then - compgen -W "-h --help --full-path -r --dependencies" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_module_tcl_rm () { - if $list_options - then - compgen -W "-h --help -y --yes-to-all" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_module_tcl_loads () { - if $list_options - then - compgen -W "-h --help --input-only -p --prefix -x --exclude -r --dependencies" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_patch () { - if $list_options - then - compgen -W "-h --help -n --no-checksum" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_pkg () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "add list diff added changed removed" -- "$cur" - fi -} - -_spack_pkg_add () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_pkg_list () { - # FIXME: How to list git revisions? - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_pkg_diff () { - # FIXME: How to list git revisions? - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_pkg_added () { - # FIXME: How to list git revisions? - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_pkg_changed () { - # FIXME: How to list git revisions? - if $list_options - then - compgen -W "-h --help -t --type" -- "$cur" - fi -} - -_spack_pkg_removed () { - # FIXME: How to list git revisions? - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_providers () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_providers)" -- "$cur" - fi -} - -_spack_pydoc () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_python () { - if $list_options - then - compgen -W "-h --help -c" -- "$cur" - fi -} - -_spack_reindex () { - compgen -W "-h --help" -- "$cur" -} - -_spack_release_jobs () { - compgen -W "-h --help -o --output-file -p --print-summary --cdash-credentials" -- "$cur" -} - -_spack_remove () { - if $list_options - then - compgen -W "-h --help -a --all -l --list-name -f --force" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_repo () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "create list add remove rm" -- "$cur" - fi -} - -_spack_repo_create () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_repo_list () { - compgen -W "-h --help --scope" -- "$cur" -} - -_spack_repo_add () { - if $list_options - then - compgen -W "-h --help --scope" -- "$cur" - fi -} - -_spack_repo_remove () { - if $list_options - then - compgen -W "-h --help --scope" -- "$cur" - else - compgen -W "$(_repos)" -- "$cur" - fi -} - -_spack_repo_rm () { - # Alias to `spack repo remove` - _spack_repo_remove -} - -_spack_resource () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "list show" -- "$cur" - fi -} - -_spack_resource_list () { - compgen -W "-h --help --only-hashes" -- "$cur" -} - -_spack_resource_show () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_all_resource_hashes)" -- "$cur" - fi -} - -_spack_restage () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_setup () { - if $list_options - then - compgen -W "-h --help -i --ignore-dependencies -n --no-checksum -v --verbose --clean --dirty" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_spec () { - if $list_options - then - compgen -W "-h --help -l --long -L --very-long -I --install-status -y --yaml -j --json -c --cover -N --namespaces -t --types" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_stage () { - if $list_options - then - compgen -W "-h --help -n --no-checksum -p --path" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_test () { - if $list_options - then - compgen -W "-h --help -H --pytest-help --extension -l --list -L --list-long -N --list-names -s -k --showlocals" -- "$cur" - else - compgen -W "$(_tests)" -- "$cur" - fi -} - -_spack_uninstall () { - if $list_options - then - compgen -W "-h --help -f --force -R --dependents -y --yes-to-all -a --all" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_unload () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "$(_installed_packages)" -- "$cur" - fi -} - -_spack_upload_s3 () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "spec index" -- "$cur" - fi -} - -_spack_upload_s3_spec () { - compgen -W "-h --help -s --spec -y --spec-yaml -b --base-dir -e --endpoint-url" -- "$cur" -} - -_spack_upload_s3_index () { - compgen -W "-h --help -e --endpoint-url" -- "$cur" -} - -_spack_url () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - else - compgen -W "parse list summary stats" -- "$cur" - fi -} - -_spack_url_parse () { - if $list_options - then - compgen -W "-h --help -s --spider" -- "$cur" - fi -} - -_spack_url_list () { - compgen -W "-h --help -c --color -e --extrapolation -n --incorrect-name -N --correct-name -v --incorrect-version -V --correct-version" -- "$cur" -} - -_spack_url_summary () { - compgen -W "-h --help" -- "$cur" -} - -_spack_url_stats () { - compgen -W "-h --help" -- "$cur" -} - -_spack_verify () { - if $list_options - then - compgen -W "-h --help -l --local -j --json -a --all -s --specs -f --files" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_versions () { - if $list_options - then - compgen -W "-h --help -s --safe-only" -- "$cur" - else - compgen -W "$(_all_packages)" -- "$cur" - fi -} - -_spack_view () { - if $list_options - then - compgen -W "-h --help -v --verbose -e --exclude -d --dependencies" -- "$cur" - else - compgen -W "symlink add soft hardlink hard remove rm statlink status check" -- "$cur" - fi -} - -_spack_view_symlink () { - if $list_options - then - compgen -W "-h --help --projection-file -i --ignore-conflicts" -- "$cur" - fi -} - -_spack_view_add () { - # Alias for `spack view symlink` - _spack_view_symlink -} - -_spack_view_soft () { - # Alias for `spack view symlink` - _spack_view_symlink -} - -_spack_view_hardlink () { - if $list_options - then - compgen -W "-h --help --projection-file -i --ignore-conflicts" -- "$cur" - fi -} - -_spack_view_hard () { - # Alias for `spack view hardlink` - _spack_view_hardlink -} - -_spack_view_remove () { - if $list_options - then - compgen -W "-h --help --no-remove-dependents -a --all" -- "$cur" - fi -} - -_spack_view_rm () { - # Alias for `spack view remove` - _spack_view_remove -} - -_spack_view_statlink () { - if $list_options - then - compgen -W "-h --help" -- "$cur" - fi -} - -_spack_view_status () { - # Alias for `spack view statlink` - _spack_view_statlink -} - -_spack_view_check () { - # Alias for `spack view statlink` - _spack_view_statlink -} - # Helper functions for subcommands +# Results of each query are cached via environment variables -_subcommands () { - spack commands +_subcommands() { + if [[ -z "${SPACK_SUBCOMMANDS:-}" ]] + then + SPACK_SUBCOMMANDS="$(spack commands)" + fi + SPACK_COMPREPLY="$SPACK_SUBCOMMANDS" } -_all_packages () { - spack list +_all_packages() { + if [[ -z "${SPACK_ALL_PACKAGES:-}" ]] + then + SPACK_ALL_PACKAGES="$(spack list)" + fi + SPACK_COMPREPLY="$SPACK_ALL_PACKAGES" } -_all_resource_hashes () { - spack resource list --only-hashes +_all_resource_hashes() { + if [[ -z "${SPACK_ALL_RESOURCES_HASHES:-}" ]] + then + SPACK_ALL_RESOURCE_HASHES="$(spack resource list --only-hashes)" + fi + SPACK_COMPREPLY="$SPACK_ALL_RESOURCE_HASHES" } -_installed_packages () { - spack --color=never find --no-groups +_installed_packages() { + if [[ -z "${SPACK_INSTALLED_PACKAGES:-}" ]] + then + SPACK_INSTALLED_PACKAGES="$(spack --color=never find --no-groups)" + fi + SPACK_COMPREPLY="$SPACK_INSTALLED_PACKAGES" } -_installed_compilers () { - spack compilers | egrep -v "^(-|=)" +_installed_compilers() { + if [[ -z "${SPACK_INSTALLED_COMPILERS:-}" ]] + then + SPACK_INSTALLED_COMPILERS="$(spack compilers | egrep -v "^(-|=)")" + fi + SPACK_COMPREPLY="$SPACK_INSTALLED_COMPILERS" } -_providers () { - spack providers +_providers() { + if [[ -z "${SPACK_PROVIDERS:-}" ]] + then + SPACK_PROVIDERS="$(spack providers)" + fi + SPACK_COMPREPLY="$SPACK_PROVIDERS" } -_mirrors () { - spack mirror list | awk '{print $1}' +_mirrors() { + if [[ -z "${SPACK_MIRRORS:-}" ]] + then + SPACK_MIRRORS="$(spack mirror list | awk '{print $1}')" + fi + SPACK_COMPREPLY="$SPACK_MIRRORS" } -_repos () { - spack repo list | awk '{print $1}' +_repos() { + if [[ -z "${SPACK_REPOS:-}" ]] + then + SPACK_REPOS="$(spack repo list | awk '{print $1}')" + fi + SPACK_COMPREPLY="$SPACK_REPOS" } -_tests () { - spack test -l +_tests() { + if [[ -z "${SPACK_TESTS:-}" ]] + then + SPACK_TESTS="$(spack test -l)" + fi + SPACK_COMPREPLY="$SPACK_TESTS" } -_environments () { - spack env list +_environments() { + if [[ -z "${SPACK_ENVIRONMENTS:-}" ]] + then + SPACK_ENVIRONMENTS="$(spack env list)" + fi + SPACK_COMPREPLY="$SPACK_ENVIRONMENTS" } -_keys () { - spack gpg list +_keys() { + if [[ -z "${SPACK_KEYS:-}" ]] + then + SPACK_KEYS="$(spack gpg list)" + fi + SPACK_COMPREPLY="$SPACK_KEYS" +} + +_config_sections() { + if [[ -z "${SPACK_CONFIG_SECTIONS:-}" ]] + then + SPACK_CONFIG_SECTIONS="compilers mirrors repos packages modules config upstreams" + fi + SPACK_COMPREPLY="$SPACK_CONFIG_SECTIONS" +} + +_extensions() { + if [[ -z "${SPACK_EXTENSIONS:-}" ]] + then + SPACK_EXTENSIONS="aspell go-bootstrap go icedtea jdk kim-api lua matlab mofem-cephas octave openjdk perl python r ruby rust tcl yorick" + fi + SPACK_COMPREPLY="$SPACK_EXTENSIONS" } # Testing functions -_test_vars () { - echo "-----------------------------------------------------" >> temp - echo "Full line: '$COMP_LINE'" >> temp - echo >> temp - echo "Word list w/ flags: $(_pretty_print COMP_WORDS[@])" >> temp - echo "# words w/ flags: '${#COMP_WORDS[@]}'" >> temp - echo "Cursor index w/ flags: '$COMP_CWORD'" >> temp - echo >> temp - echo "Word list w/out flags: $(_pretty_print COMP_WORDS_NO_FLAGS[@])" >> temp - echo "# words w/out flags: '${#COMP_WORDS_NO_FLAGS[@]}'" >> temp - echo "Cursor index w/out flags: '$COMP_CWORD_NO_FLAGS'" >> temp - echo >> temp - echo "Subfunction: '$subfunction'" >> temp +# Function for unit testing tab completion +# Syntax: _spack_completions spack install py- +_spack_completions() { + local COMP_CWORD COMP_KEY COMP_LINE COMP_POINT COMP_TYPE COMP_WORDS COMPREPLY + + # Set each variable the way bash would + COMP_LINE="$*" + COMP_POINT=${#COMP_LINE} + COMP_WORDS=("$@") + if [[ ${COMP_LINE: -1} == ' ' ]] + then + COMP_WORDS+=('') + fi + COMP_CWORD=$((${#COMP_WORDS[@]} - 1)) + COMP_KEY=9 # ASCII 09: Horizontal Tab + COMP_TYPE=64 # ASCII 64: '@', to list completions if the word is not unmodified + + # Run Spack's tab completion function + _bash_completion_spack + + # Return the result + echo "${COMPREPLY[@]:-}" +} + +# Log the environment variables used +# Syntax: _test_vars >> temp +_test_vars() { + echo "-----------------------------------------------------" + echo "Variables set by bash:" + echo + echo "COMP_LINE: '$COMP_LINE'" + echo "# COMP_LINE: '${#COMP_LINE}'" + echo "COMP_WORDS: $(_pretty_print COMP_WORDS[@])" + echo "# COMP_WORDS: '${#COMP_WORDS[@]}'" + echo "COMP_CWORD: '$COMP_CWORD'" + echo "COMP_KEY: '$COMP_KEY'" + echo "COMP_POINT: '$COMP_POINT'" + echo "COMP_TYPE: '$COMP_TYPE'" + echo "COMP_WORDBREAKS: '$COMP_WORDBREAKS'" + echo + echo "Intermediate variables:" + echo + echo "COMP_WORDS_NO_FLAGS: $(_pretty_print COMP_WORDS_NO_FLAGS[@])" + echo "# COMP_WORDS_NO_FLAGS: '${#COMP_WORDS_NO_FLAGS[@]}'" + echo "COMP_CWORD_NO_FLAGS: '$COMP_CWORD_NO_FLAGS'" + echo + echo "Subfunction: '$subfunction'" if $list_options then - echo "List options: 'True'" >> temp + echo "List options: 'True'" else - echo "List options: 'False'" >> temp + echo "List options: 'False'" fi - echo "Current word: '$cur'" >> temp - echo "Previous word: '$prev'" >> temp + echo "Current word: '$cur'" } # Pretty-prints one or more arrays # Syntax: _pretty_print array1[@] ... -_pretty_print () { +_pretty_print() { for arg in $@ do local array=("${!arg}") - echo -n "$arg: [" + printf "$arg: [" printf "'%s'" "${array[0]}" printf ", '%s'" "${array[@]:1}" echo "]" done } -complete -o default -F _bash_completion_spack spack +complete -o bashdefault -o default -F _bash_completion_spack spack + +# Spack commands +# +# Everything below here is auto-generated. + +_spack() { + if $list_options + then + SPACK_COMPREPLY="-h --help -H --all-help --color -C --config-scope -d --debug --timestamp --pdb -e --env -D --env-dir -E --no-env --use-env-repo -k --insecure -l --enable-locks -L --disable-locks -m --mock -p --profile --sorted-profile --lines -v --verbose --stacktrace -V --version --print-shell-vars" + else + SPACK_COMPREPLY="activate add arch blame bootstrap build build-env buildcache cd checksum ci clean clone commands compiler compilers concretize config configure create deactivate debug dependencies dependents deprecate dev-build diy docs edit env extensions fetch find flake8 gc gpg graph help info install license list load location log-parse maintainers mirror module patch pkg providers pydoc python reindex remove rm repo resource restage setup spec stage test uninstall unload upload-s3 url verify versions view" + fi +} + +_spack_activate() { + if $list_options + then + SPACK_COMPREPLY="-h --help -f --force -v --view" + else + _installed_packages + fi +} + +_spack_add() { + if $list_options + then + SPACK_COMPREPLY="-h --help -l --list-name" + else + _all_packages + fi +} + +_spack_arch() { + SPACK_COMPREPLY="-h --help --known-targets -p --platform -o --operating-system -t --target -f --frontend -b --backend" +} + +_spack_blame() { + if $list_options + then + SPACK_COMPREPLY="-h --help -t --time -p --percent -g --git" + else + _all_packages + fi +} + +_spack_bootstrap() { + SPACK_COMPREPLY="-h --help -j --jobs --keep-prefix --keep-stage -n --no-checksum -v --verbose --use-cache --no-cache --cache-only --clean --dirty" +} + +_spack_build() { + if $list_options + then + SPACK_COMPREPLY="-h --help -v --verbose" + else + _all_packages + fi +} + +_spack_build_env() { + if $list_options + then + SPACK_COMPREPLY="-h --help --clean --dirty --dump --pickle" + else + _all_packages + fi +} + +_spack_buildcache() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="create install list keys preview check download get-buildcache-name save-yaml copy update-index" + fi +} + +_spack_buildcache_create() { + if $list_options + then + SPACK_COMPREPLY="-h --help -r --rel -f --force -u --unsigned -a --allow-root -k --key -d --directory --no-rebuild-index -y --spec-yaml --no-deps" + else + _all_packages + fi +} + +_spack_buildcache_install() { + if $list_options + then + SPACK_COMPREPLY="-h --help -f --force -m --multiple -a --allow-root -u --unsigned" + else + _all_packages + fi +} + +_spack_buildcache_list() { + if $list_options + then + SPACK_COMPREPLY="-h --help -l --long -L --very-long -v --variants -f --force" + else + _all_packages + fi +} + +_spack_buildcache_keys() { + SPACK_COMPREPLY="-h --help -i --install -t --trust -f --force" +} + +_spack_buildcache_preview() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _installed_packages + fi +} + +_spack_buildcache_check() { + SPACK_COMPREPLY="-h --help -m --mirror-url -o --output-file --scope -s --spec -y --spec-yaml --rebuild-on-error" +} + +_spack_buildcache_download() { + SPACK_COMPREPLY="-h --help -s --spec -y --spec-yaml -p --path -c --require-cdashid" +} + +_spack_buildcache_get_buildcache_name() { + SPACK_COMPREPLY="-h --help -s --spec -y --spec-yaml" +} + +_spack_buildcache_save_yaml() { + SPACK_COMPREPLY="-h --help --root-spec --root-spec-yaml -s --specs -y --yaml-dir" +} + +_spack_buildcache_copy() { + SPACK_COMPREPLY="-h --help --base-dir --spec-yaml --destination-url" +} + +_spack_buildcache_update_index() { + SPACK_COMPREPLY="-h --help -d --mirror-url" +} + +_spack_cd() { + if $list_options + then + SPACK_COMPREPLY="-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages -b --build-dir -e --env" + else + _all_packages + fi +} + +_spack_checksum() { + if $list_options + then + SPACK_COMPREPLY="-h --help --keep-stage" + else + _all_packages + fi +} + +_spack_ci() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="start generate pushyaml rebuild" + fi +} + +_spack_ci_start() { + SPACK_COMPREPLY="-h --help --output-file --copy-to --spack-repo --spack-ref --downstream-repo --branch-name --commit-sha" +} + +_spack_ci_generate() { + SPACK_COMPREPLY="-h --help --output-file --copy-to --spack-repo --spack-ref" +} + +_spack_ci_pushyaml() { + SPACK_COMPREPLY="-h --help --downstream-repo --branch-name --commit-sha" +} + +_spack_ci_rebuild() { + SPACK_COMPREPLY="-h --help" +} + +_spack_clean() { + if $list_options + then + SPACK_COMPREPLY="-h --help -s --stage -d --downloads -m --misc-cache -p --python-cache -a --all" + else + _all_packages + fi +} + +_spack_clone() { + if $list_options + then + SPACK_COMPREPLY="-h --help -r --remote" + else + SPACK_COMPREPLY="" + fi +} + +_spack_commands() { + if $list_options + then + SPACK_COMPREPLY="-h --help -a --aliases --format --header --update" + else + SPACK_COMPREPLY="" + fi +} + +_spack_compiler() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="find add remove rm list info" + fi +} + +_spack_compiler_find() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + SPACK_COMPREPLY="" + fi +} + +_spack_compiler_add() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + SPACK_COMPREPLY="" + fi +} + +_spack_compiler_remove() { + if $list_options + then + SPACK_COMPREPLY="-h --help -a --all --scope" + else + _installed_compilers + fi +} + +_spack_compiler_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help -a --all --scope" + else + _installed_compilers + fi +} + +_spack_compiler_list() { + SPACK_COMPREPLY="-h --help --scope" +} + +_spack_compiler_info() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + _installed_compilers + fi +} + +_spack_compilers() { + SPACK_COMPREPLY="-h --help --scope" +} + +_spack_concretize() { + SPACK_COMPREPLY="-h --help -f --force" +} + +_spack_config() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + SPACK_COMPREPLY="get blame edit" + fi +} + +_spack_config_get() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _config_sections + fi +} + +_spack_config_blame() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _config_sections + fi +} + +_spack_config_edit() { + if $list_options + then + SPACK_COMPREPLY="-h --help --print-file" + else + _config_sections + fi +} + +_spack_configure() { + if $list_options + then + SPACK_COMPREPLY="-h --help -v --verbose" + else + _all_packages + fi +} + +_spack_create() { + if $list_options + then + SPACK_COMPREPLY="-h --help --keep-stage -n --name -t --template -r --repo -N --namespace -f --force --skip-editor" + else + SPACK_COMPREPLY="" + fi +} + +_spack_deactivate() { + if $list_options + then + SPACK_COMPREPLY="-h --help -f --force -v --view -a --all" + else + _installed_packages + fi +} + +_spack_debug() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="create-db-tarball" + fi +} + +_spack_debug_create_db_tarball() { + SPACK_COMPREPLY="-h --help" +} + +_spack_dependencies() { + if $list_options + then + SPACK_COMPREPLY="-h --help -i --installed -t --transitive --deptype -V --no-expand-virtuals" + else + _all_packages + fi +} + +_spack_dependents() { + if $list_options + then + SPACK_COMPREPLY="-h --help -i --installed -t --transitive" + else + _all_packages + fi +} + +_spack_deprecate() { + if $list_options + then + SPACK_COMPREPLY="-h --help -y --yes-to-all -d --dependencies -D --no-dependencies -i --install-deprecator -I --no-install-deprecator -l --link-type" + else + _all_packages + fi +} + +_spack_dev_build() { + if $list_options + then + SPACK_COMPREPLY="-h --help -j --jobs -d --source-path -i --ignore-dependencies -n --no-checksum --keep-prefix --skip-patch -q --quiet -u --until --clean --dirty" + else + _all_packages + fi +} + +_spack_diy() { + if $list_options + then + SPACK_COMPREPLY="-h --help -j --jobs -d --source-path -i --ignore-dependencies -n --no-checksum --keep-prefix --skip-patch -q --quiet -u --until --clean --dirty" + else + _all_packages + fi +} + +_spack_docs() { + SPACK_COMPREPLY="-h --help" +} + +_spack_edit() { + if $list_options + then + SPACK_COMPREPLY="-h --help -b --build-system -c --command -d --docs -t --test -m --module -r --repo -N --namespace" + else + _all_packages + fi +} + +_spack_env() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="activate deactivate create remove rm list ls status st loads view" + fi +} + +_spack_env_activate() { + if $list_options + then + SPACK_COMPREPLY="-h --help --sh --csh -v --with-view -V --without-view -d --dir -p --prompt" + else + _environments + fi +} + +_spack_env_deactivate() { + SPACK_COMPREPLY="-h --help --sh --csh" +} + +_spack_env_create() { + if $list_options + then + SPACK_COMPREPLY="-h --help -d --dir --without-view --with-view" + else + _environments + fi +} + +_spack_env_remove() { + if $list_options + then + SPACK_COMPREPLY="-h --help -y --yes-to-all" + else + _environments + fi +} + +_spack_env_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help -y --yes-to-all" + else + _environments + fi +} + +_spack_env_list() { + SPACK_COMPREPLY="-h --help" +} + +_spack_env_ls() { + SPACK_COMPREPLY="-h --help" +} + +_spack_env_status() { + SPACK_COMPREPLY="-h --help" +} + +_spack_env_st() { + SPACK_COMPREPLY="-h --help" +} + +_spack_env_loads() { + if $list_options + then + SPACK_COMPREPLY="-h --help -m --module-type --input-only -p --prefix -x --exclude -r --dependencies" + else + _environments + fi +} + +_spack_env_view() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="" + fi +} + +_spack_extensions() { + if $list_options + then + SPACK_COMPREPLY="-h --help -l --long -L --very-long -d --deps -p --paths -s --show -v --view" + else + _extensions + fi +} + +_spack_fetch() { + if $list_options + then + SPACK_COMPREPLY="-h --help -n --no-checksum -m --missing -D --dependencies" + else + _all_packages + fi +} + +_spack_find() { + if $list_options + then + SPACK_COMPREPLY="-h --help --format --json -d --deps -p --paths --groups --no-groups -l --long -L --very-long -t --tags -c --show-concretized -f --show-flags --show-full-compiler -x --explicit -X --implicit -u --unknown -m --missing -v --variants -M --only-missing --deprecated --only-deprecated -N --namespace --start-date --end-date" + else + _installed_packages + fi +} + +_spack_flake8() { + if $list_options + then + SPACK_COMPREPLY="-h --help -b --base -k --keep-temp -a --all -o --output -r --root-relative -U --no-untracked" + else + SPACK_COMPREPLY="" + fi +} + +_spack_gc() { + SPACK_COMPREPLY="-h --help -y --yes-to-all" +} + +_spack_gpg() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="verify trust untrust sign create list init export" + fi +} + +_spack_gpg_verify() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _installed_packages + fi +} + +_spack_gpg_trust() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="" + fi +} + +_spack_gpg_untrust() { + if $list_options + then + SPACK_COMPREPLY="-h --help --signing" + else + _keys + fi +} + +_spack_gpg_sign() { + if $list_options + then + SPACK_COMPREPLY="-h --help --output --key --clearsign" + else + _installed_packages + fi +} + +_spack_gpg_create() { + if $list_options + then + SPACK_COMPREPLY="-h --help --comment --expires --export" + else + SPACK_COMPREPLY="" + fi +} + +_spack_gpg_list() { + SPACK_COMPREPLY="-h --help --trusted --signing" +} + +_spack_gpg_init() { + SPACK_COMPREPLY="-h --help --from" +} + +_spack_gpg_export() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _keys + fi +} + +_spack_graph() { + if $list_options + then + SPACK_COMPREPLY="-h --help -a --ascii -d --dot -s --static -i --installed --deptype" + else + _all_packages + fi +} + +_spack_help() { + if $list_options + then + SPACK_COMPREPLY="-h --help -a --all --spec" + else + _subcommands + fi +} + +_spack_info() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _all_packages + fi +} + +_spack_install() { + if $list_options + then + SPACK_COMPREPLY="-h --help --only -u --until -j --jobs --overwrite --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --show-log-on-error --source -n --no-checksum -v --verbose --fake --only-concrete -f --file --clean --dirty --test --run-tests --log-format --log-file --help-cdash --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp -y --yes-to-all" + else + _all_packages + fi +} + +_spack_license() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="list-files verify" + fi +} + +_spack_license_list_files() { + SPACK_COMPREPLY="-h --help" +} + +_spack_license_verify() { + SPACK_COMPREPLY="-h --help --root" +} + +_spack_list() { + if $list_options + then + SPACK_COMPREPLY="-h --help -d --search-description --format --update -t --tags" + else + _all_packages + fi +} + +_spack_load() { + if $list_options + then + SPACK_COMPREPLY="-h --help -r --dependencies" + else + _installed_packages + fi +} + +_spack_location() { + if $list_options + then + SPACK_COMPREPLY="-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages -b --build-dir -e --env" + else + _all_packages + fi +} + +_spack_log_parse() { + if $list_options + then + SPACK_COMPREPLY="-h --help --show -c --context -p --profile -w --width -j --jobs" + else + SPACK_COMPREPLY="" + fi +} + +_spack_maintainers() { + if $list_options + then + SPACK_COMPREPLY="-h --help --maintained --unmaintained -a --all --by-user" + else + _all_packages + fi +} + +_spack_mirror() { + if $list_options + then + SPACK_COMPREPLY="-h --help -n --no-checksum" + else + SPACK_COMPREPLY="create add remove rm set-url list" + fi +} + +_spack_mirror_create() { + if $list_options + then + SPACK_COMPREPLY="-h --help -d --directory -a --all -f --file -D --dependencies -n --versions-per-spec" + else + _all_packages + fi +} + +_spack_mirror_add() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + _mirrors + fi +} + +_spack_mirror_remove() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + _mirrors + fi +} + +_spack_mirror_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + _mirrors + fi +} + +_spack_mirror_set_url() { + if $list_options + then + SPACK_COMPREPLY="-h --help --push --scope" + else + _mirrors + fi +} + +_spack_mirror_list() { + SPACK_COMPREPLY="-h --help --scope" +} + +_spack_module() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="lmod tcl" + fi +} + +_spack_module_lmod() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="refresh find rm loads setdefault" + fi +} + +_spack_module_lmod_refresh() { + if $list_options + then + SPACK_COMPREPLY="-h --help --delete-tree --upstream-modules -y --yes-to-all" + else + _installed_packages + fi +} + +_spack_module_lmod_find() { + if $list_options + then + SPACK_COMPREPLY="-h --help --full-path -r --dependencies" + else + _installed_packages + fi +} + +_spack_module_lmod_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help -y --yes-to-all" + else + _installed_packages + fi +} + +_spack_module_lmod_loads() { + if $list_options + then + SPACK_COMPREPLY="-h --help --input-only -p --prefix -x --exclude -r --dependencies" + else + _installed_packages + fi +} + +_spack_module_lmod_setdefault() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _installed_packages + fi +} + +_spack_module_tcl() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="refresh find rm loads" + fi +} + +_spack_module_tcl_refresh() { + if $list_options + then + SPACK_COMPREPLY="-h --help --delete-tree --upstream-modules -y --yes-to-all" + else + _installed_packages + fi +} + +_spack_module_tcl_find() { + if $list_options + then + SPACK_COMPREPLY="-h --help --full-path -r --dependencies" + else + _installed_packages + fi +} + +_spack_module_tcl_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help -y --yes-to-all" + else + _installed_packages + fi +} + +_spack_module_tcl_loads() { + if $list_options + then + SPACK_COMPREPLY="-h --help --input-only -p --prefix -x --exclude -r --dependencies" + else + _installed_packages + fi +} + +_spack_patch() { + if $list_options + then + SPACK_COMPREPLY="-h --help -n --no-checksum" + else + _all_packages + fi +} + +_spack_pkg() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="add list diff added changed removed" + fi +} + +_spack_pkg_add() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _all_packages + fi +} + +_spack_pkg_list() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="" + fi +} + +_spack_pkg_diff() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="" + fi +} + +_spack_pkg_added() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="" + fi +} + +_spack_pkg_changed() { + if $list_options + then + SPACK_COMPREPLY="-h --help -t --type" + else + SPACK_COMPREPLY="" + fi +} + +_spack_pkg_removed() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="" + fi +} + +_spack_providers() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _providers + fi +} + +_spack_pydoc() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="" + fi +} + +_spack_python() { + if $list_options + then + SPACK_COMPREPLY="-h --help -c" + else + SPACK_COMPREPLY="" + fi +} + +_spack_reindex() { + SPACK_COMPREPLY="-h --help" +} + +_spack_remove() { + if $list_options + then + SPACK_COMPREPLY="-h --help -a --all -l --list-name -f --force" + else + _all_packages + fi +} + +_spack_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help -a --all -l --list-name -f --force" + else + _all_packages + fi +} + +_spack_repo() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="create list add remove rm" + fi +} + +_spack_repo_create() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _repos + fi +} + +_spack_repo_list() { + SPACK_COMPREPLY="-h --help --scope" +} + +_spack_repo_add() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + SPACK_COMPREPLY="" + fi +} + +_spack_repo_remove() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + _repos + fi +} + +_spack_repo_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help --scope" + else + _repos + fi +} + +_spack_resource() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="list show" + fi +} + +_spack_resource_list() { + SPACK_COMPREPLY="-h --help --only-hashes" +} + +_spack_resource_show() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _all_resource_hashes + fi +} + +_spack_restage() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _all_packages + fi +} + +_spack_setup() { + if $list_options + then + SPACK_COMPREPLY="-h --help -i --ignore-dependencies -n --no-checksum -v --verbose --clean --dirty" + else + _all_packages + fi +} + +_spack_spec() { + if $list_options + then + SPACK_COMPREPLY="-h --help -l --long -L --very-long -I --install-status -y --yaml -j --json -c --cover -N --namespaces -t --types" + else + _all_packages + fi +} + +_spack_stage() { + if $list_options + then + SPACK_COMPREPLY="-h --help -n --no-checksum -p --path" + else + _all_packages + fi +} + +_spack_test() { + if $list_options + then + SPACK_COMPREPLY="-h --help -H --pytest-help -l --list -L --list-long -N --list-names --extension -s -k --showlocals" + else + _tests + fi +} + +_spack_uninstall() { + if $list_options + then + SPACK_COMPREPLY="-h --help -f --force -R --dependents -y --yes-to-all -a --all" + else + _installed_packages + fi +} + +_spack_unload() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _installed_packages + fi +} + +_spack_upload_s3() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="spec index" + fi +} + +_spack_upload_s3_spec() { + SPACK_COMPREPLY="-h --help -s --spec -y --spec-yaml -b --base-dir -e --endpoint-url" +} + +_spack_upload_s3_index() { + SPACK_COMPREPLY="-h --help -e --endpoint-url" +} + +_spack_url() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + SPACK_COMPREPLY="parse list summary stats" + fi +} + +_spack_url_parse() { + if $list_options + then + SPACK_COMPREPLY="-h --help -s --spider" + else + SPACK_COMPREPLY="" + fi +} + +_spack_url_list() { + SPACK_COMPREPLY="-h --help -c --color -e --extrapolation -n --incorrect-name -N --correct-name -v --incorrect-version -V --correct-version" +} + +_spack_url_summary() { + SPACK_COMPREPLY="-h --help" +} + +_spack_url_stats() { + SPACK_COMPREPLY="-h --help" +} + +_spack_verify() { + if $list_options + then + SPACK_COMPREPLY="-h --help -l --local -j --json -a --all -s --specs -f --files" + else + _all_packages + fi +} + +_spack_versions() { + if $list_options + then + SPACK_COMPREPLY="-h --help -s --safe-only" + else + _all_packages + fi +} + +_spack_view() { + if $list_options + then + SPACK_COMPREPLY="-h --help -v --verbose -e --exclude -d --dependencies" + else + SPACK_COMPREPLY="symlink add soft hardlink hard remove rm statlink status check" + fi +} + +_spack_view_symlink() { + if $list_options + then + SPACK_COMPREPLY="-h --help --projection-file -i --ignore-conflicts" + else + _all_packages + fi +} + +_spack_view_add() { + if $list_options + then + SPACK_COMPREPLY="-h --help --projection-file -i --ignore-conflicts" + else + _all_packages + fi +} + +_spack_view_soft() { + if $list_options + then + SPACK_COMPREPLY="-h --help --projection-file -i --ignore-conflicts" + else + _all_packages + fi +} + +_spack_view_hardlink() { + if $list_options + then + SPACK_COMPREPLY="-h --help --projection-file -i --ignore-conflicts" + else + _all_packages + fi +} + +_spack_view_hard() { + if $list_options + then + SPACK_COMPREPLY="-h --help --projection-file -i --ignore-conflicts" + else + _all_packages + fi +} + +_spack_view_remove() { + if $list_options + then + SPACK_COMPREPLY="-h --help --no-remove-dependents -a --all" + else + _all_packages + fi +} + +_spack_view_rm() { + if $list_options + then + SPACK_COMPREPLY="-h --help --no-remove-dependents -a --all" + else + _all_packages + fi +} + +_spack_view_statlink() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _all_packages + fi +} + +_spack_view_status() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _all_packages + fi +} + +_spack_view_check() { + if $list_options + then + SPACK_COMPREPLY="-h --help" + else + _all_packages + fi +}