summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAdam J. Stewart <ajstewart426@gmail.com>2020-01-05 23:35:23 -0800
committerTodd Gamblin <tgamblin@llnl.gov>2020-01-22 21:31:12 -0800
commit11f2b612612748ee57728693c7745e3af92e9d54 (patch)
tree656c2ba1de69064c62af1fbd4f43469f7c36cac9 /lib
parent8011fedd9ca47578e8da37a9060407c6784d7015 (diff)
downloadspack-11f2b612612748ee57728693c7745e3af92e9d54.tar.gz
spack-11f2b612612748ee57728693c7745e3af92e9d54.tar.bz2
spack-11f2b612612748ee57728693c7745e3af92e9d54.tar.xz
spack-11f2b612612748ee57728693c7745e3af92e9d54.zip
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 <tgamblin@llnl.gov>
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/llnl/util/argparsewriter.py415
-rw-r--r--lib/spack/spack/cmd/activate.py7
-rw-r--r--lib/spack/spack/cmd/add.py6
-rw-r--r--lib/spack/spack/cmd/blame.py8
-rw-r--r--lib/spack/spack/cmd/build_env.py2
-rw-r--r--lib/spack/spack/cmd/buildcache.py32
-rw-r--r--lib/spack/spack/cmd/checksum.py5
-rw-r--r--lib/spack/spack/cmd/clean.py7
-rw-r--r--lib/spack/spack/cmd/commands.py125
-rw-r--r--lib/spack/spack/cmd/common/arguments.py46
-rw-r--r--lib/spack/spack/cmd/config.py6
-rw-r--r--lib/spack/spack/cmd/configure.py13
-rw-r--r--lib/spack/spack/cmd/deactivate.py6
-rw-r--r--lib/spack/spack/cmd/dependencies.py5
-rw-r--r--lib/spack/spack/cmd/dependents.py8
-rw-r--r--lib/spack/spack/cmd/dev_build.py7
-rw-r--r--lib/spack/spack/cmd/edit.py5
-rw-r--r--lib/spack/spack/cmd/env.py4
-rw-r--r--lib/spack/spack/cmd/extensions.py2
-rw-r--r--lib/spack/spack/cmd/fetch.py12
-rw-r--r--lib/spack/spack/cmd/gpg.py15
-rw-r--r--lib/spack/spack/cmd/graph.py7
-rw-r--r--lib/spack/spack/cmd/info.py6
-rw-r--r--lib/spack/spack/cmd/install.py13
-rw-r--r--lib/spack/spack/cmd/load.py8
-rw-r--r--lib/spack/spack/cmd/location.py6
-rw-r--r--lib/spack/spack/cmd/maintainers.py14
-rw-r--r--lib/spack/spack/cmd/mirror.py14
-rw-r--r--lib/spack/spack/cmd/patch.py13
-rw-r--r--lib/spack/spack/cmd/pkg.py5
-rw-r--r--lib/spack/spack/cmd/remove.py6
-rw-r--r--lib/spack/spack/cmd/repo.py12
-rw-r--r--lib/spack/spack/cmd/restage.py10
-rw-r--r--lib/spack/spack/cmd/setup.py5
-rw-r--r--lib/spack/spack/cmd/spec.py4
-rw-r--r--lib/spack/spack/cmd/stage.py7
-rw-r--r--lib/spack/spack/cmd/uninstall.py18
-rw-r--r--lib/spack/spack/cmd/unload.py7
-rw-r--r--lib/spack/spack/cmd/verify.py12
-rw-r--r--lib/spack/spack/cmd/versions.py7
-rw-r--r--lib/spack/spack/reporters/cdash.py4
-rw-r--r--lib/spack/spack/test/cmd/commands.py170
-rw-r--r--lib/spack/spack/test/llnl/util/argparsewriter.py37
43 files changed, 751 insertions, 370 deletions
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,201 +4,376 @@
# 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 parse(self, parser, prog):
+ """Parses the parser object and returns the relavent components.
- def _write(self, parser, root=True, level=0):
+ 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
- actions = parser._actions
- # allow root level to be flattened with rest of commands
- if type(root) == int:
- self.level = root
- root = True
+ 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()
- # 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)
+ args = fmt._format_action_invocation(action)
+ help = self._expand_help(action) if action.help else ''
+ help = help.replace('\n', ' ')
+ positionals.append((args, help))
- groups = parser._mutually_exclusive_groups
- fmt = parser._get_formatter()
- description = parser.description
+ return Command(
+ prog, description, usage, positionals, optionals, subcommands)
- def action_group(function, actions):
- for action in actions:
- arg = fmt._format_action_invocation(action)
- help = self._expand_help(action) if action.help else ''
- function(arg, re.sub('\n', ' ', help))
+ def format(self, cmd):
+ """Returns the string representation of a single node in the
+ parser tree.
- if root:
- self.begin_command(parser.prog)
+ Override this in subclasses to define how each subcommand
+ should be displayed.
- if description:
- self.description(parser.description)
+ Parameters:
+ (Command): parsed information about a command or subcommand
- usage = fmt._format_usage(None, actions, groups, '').strip()
- self.usage(usage)
+ Returns:
+ str: the string representation of this subcommand
+ """
+ raise NotImplementedError
- if positionals:
- self.begin_positionals()
- action_group(self.positional, positionals)
- self.end_positionals()
+ def _write(self, parser, prog, level=0):
+ """Recursively writes a parser.
- if optionals:
- self.begin_optionals()
- action_group(self.optional, optionals)
- self.end_optionals()
+ Parameters:
+ parser (argparse.ArgumentParser): the parser
+ prog (str): the command name
+ level (int): the current level
+ """
+ self.level = level
- if subcommands:
- self.begin_subcommands(subcommands)
- for subparser in subcommands:
- self._write(subparser, root=True, level=level + 1)
- self.end_subcommands(subcommands)
+ cmd = self.parse(parser, prog)
+ self.out.write(self.format(cmd))
- if root:
- self.end_command(parser.prog)
+ for subparser, prog in cmd.subcommands:
+ self._write(subparser, prog, level=level + 1)
- def write(self, parser, root=True):
+ 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
+
+_rst_levels = ['=', '-', '^', '~', ':', '`']
+
+
+class ArgparseRstWriter(ArgparseWriter):
+ """Write argparse output as rst sections."""
+
+ def __init__(self, prog, out=sys.stdout, aliases=False,
+ rst_levels=_rst_levels):
+ """Create a new ArgparseRstWriter.
+
+ 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
+ """
+ super(ArgparseRstWriter, self).__init__(prog, out, aliases)
+ self.rst_levels = rst_levels
+
+ 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):
- pass
+ return """
+----
+
+.. _{0}:
- def end_command(self, prog):
- pass
+{1}
+{2}
+
+""".format(prog.replace(' ', '-'), prog,
+ self.rst_levels[self.level] * len(prog))
def description(self, description):
- pass
+ return description + '\n\n'
def usage(self, usage):
- pass
+ return """\
+.. code-block:: console
+
+ {0}
+
+""".format(usage)
def begin_positionals(self):
- pass
+ return '\n**Positional arguments**\n\n'
def positional(self, name, help):
- pass
+ return """\
+{0}
+ {1}
+
+""".format(name, help)
def end_positionals(self):
- pass
+ return ''
def begin_optionals(self):
- pass
+ return '\n**Optional arguments**\n\n'
+
+ def optional(self, opts, help):
+ return """\
+``{0}``
+ {1}
- def optional(self, option, help):
- pass
+""".format(opts, help)
def end_optionals(self):
- pass
+ return ''
def begin_subcommands(self, subcommands):
- pass
+ string = """
+**Subcommands**
- def end_subcommands(self, subcommands):
- pass
+.. hlist::
+ :columns: 4
+"""
-_rst_levels = ['=', '-', '^', '~', ':', '`']
+ for cmd, _ in subcommands:
+ prog = re.sub(r'^[^ ]* ', '', cmd.prog)
+ string += ' * :ref:`{0} <{1}>`\n'.format(
+ prog, cmd.prog.replace(' ', '-'))
+ return string + '\n'
-class ArgparseRstWriter(ArgparseWriter):
- """Write argparse output as rst sections."""
- def __init__(self, out=sys.stdout, rst_levels=_rst_levels,
- strip_root_prog=True):
- """Create a new ArgparseRstWriter.
+class ArgparseCompletionWriter(ArgparseWriter):
+ """Write argparse output as shell programmable tab completion functions."""
- Args:
- out (file object): file to write to
- 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
+ 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
"""
- super(ArgparseRstWriter, self).__init__(out)
- self.rst_levels = rst_levels
- self.strip_root_prog = strip_root_prog
- def line(self, string=''):
- self.out.write('%s\n' % string)
+ assert cmd.optionals # we should always at least have -h, --help
+ assert not (cmd.positionals and cmd.subcommands) # one or the other
- 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')
+ # 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)
- def description(self, description):
- self.line('%s\n' % description)
+ # Flatten lists of lists
+ optionals = [x for xx in optionals for x in xx]
- def usage(self, usage):
- self.line('.. code-block:: console\n')
- self.line(' %s\n' % usage)
+ return (self.start_function(cmd.prog) +
+ self.body(positionals, optionals, subcommands) +
+ self.end_function(cmd.prog))
- def begin_positionals(self):
- self.line()
- self.line('**Positional arguments**\n')
+ def start_function(self, prog):
+ """Returns the syntax needed to begin a function definition.
- def positional(self, name, help):
- self.line(name)
- self.line(' %s\n' % help)
+ Parameters:
+ prog (str): the command name
- def begin_optionals(self):
- self.line()
- self.line('**Optional arguments**\n')
+ Returns:
+ str: the function definition beginning
+ """
+ name = prog.replace('-', '_').replace(' ', '_')
+ return '\n_{0}() {{'.format(name)
- def optional(self, opts, help):
- self.line('``%s``' % opts)
- self.line(' %s\n' % help)
+ def end_function(self, prog=None):
+ """Returns the syntax needed to end a function definition.
- def begin_subcommands(self, subcommands):
- self.line()
- self.line('**Subcommands**\n')
- self.line('.. hlist::')
- self.line(' :columns: 4\n')
-
- for cmd in subcommands:
- prog = cmd.prog
- if self.strip_root_prog:
- prog = re.sub(r'^[^ ]* ', '', prog)
-
- self.line(' * :ref:`%s <%s>`'
- % (prog, cmd.prog.replace(' ', '-')))
- self.line()
+ 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 argparse
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
@@ -23,11 +24,9 @@ level = "long"
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 llnl.util.tty as tty
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
@@ -36,6 +39,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)')
subparser.add_argument(
@@ -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-%s>`' % cmd)
+ string += '\n:ref:`More documentation <cmd-{0}>`\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 @@ class SetParallelJobs(argparse.Action):
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 @@ class DeptypeAction(argparse.Action):
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 argparse
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
@@ -37,15 +38,11 @@ build_system_to_phase = {
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 @@ from six.moves import zip_longest
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 padder(str_list, extra=0):
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 @@ the dependencies"""
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 @@ packages. If neither are chosen, don't run tests for any packages."""
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 @@ environment variables:
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 @@ environment variables:
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 @@ environment variables:
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 @@ level = "short"
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
@@ -40,9 +39,6 @@ def setup_parser(subparser):
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"
" in the current environment if there is an active environment"
@@ -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 @@ level = "long"
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.tty.colify import colify
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 @@ level = "long"
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 @@ for further documentation regarding the spec syntax, see:
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 @@ level = "long"
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 @@ display_args = {
}
-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 @@ level = "short"
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 @@ level = "long"
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 @@ class CDash(Reporter):
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 pytest
+import filecmp
+import os
+import subprocess
-from llnl.util.argparsewriter import ArgparseWriter
+import pytest
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 @@ parser = spack.main.make_argument_parser()
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
-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
+ out3 = commands('--format=names').strip().split('\n')
+ assert out1 == out3
- class Subcommands(ArgparseWriter):
- def begin_command(self, prog):
- assert prog in out
- Subcommands().write(parser)
+def test_subcommands():
+ """Test subcommand traversal."""
+ 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
+
+ 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')
-
- class Subcommands(ArgparseWriter):
- def begin_command(self, prog):
- assert prog in out
- assert re.sub(r' ', '-', prog) in out
- Subcommands().write(parser)
+ 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
+
+ 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)