spack commands: add type hints and docstrings (#38705)

This commit is contained in:
Adam J. Stewart 2023-07-04 15:43:02 -05:00 committed by GitHub
parent d35149d174
commit 2978911520
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 371 additions and 159 deletions

View File

@ -3,31 +3,42 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import abc
import argparse import argparse
import errno
import io import io
import re import re
import sys import sys
from argparse import ArgumentParser
from typing import IO, Optional, Sequence, Tuple
class Command(object): class Command:
"""Parsed representation of a command from argparse. """Parsed representation of a command from argparse.
This is a single command from an argparse parser. ``ArgparseWriter`` This is a single command from an argparse parser. ``ArgparseWriter`` creates these and returns
creates these and returns them from ``parse()``, and it passes one of them from ``parse()``, and it passes one of these to each call to ``format()`` so that we can
these to each call to ``format()`` so that we can take an action for take an action for a single command.
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): def __init__(
self,
prog: str,
description: Optional[str],
usage: str,
positionals: Sequence[Tuple[str, str]],
optionals: Sequence[Tuple[Sequence[str], str, str]],
subcommands: Sequence[Tuple[ArgumentParser, str]],
) -> None:
"""Initialize a new Command instance.
Args:
prog: Program name.
description: Command description.
usage: Command usage.
positionals: List of positional arguments.
optionals: List of optional arguments.
subcommands: List of subcommand parsers.
"""
self.prog = prog self.prog = prog
self.description = description self.description = description
self.usage = usage self.usage = usage
@ -36,35 +47,34 @@ def __init__(self, prog, description, usage, positionals, optionals, subcommands
self.subcommands = subcommands self.subcommands = subcommands
# NOTE: The only reason we subclass argparse.HelpFormatter is to get access # NOTE: The only reason we subclass argparse.HelpFormatter is to get access to self._expand_help(),
# to self._expand_help(), ArgparseWriter is not intended to be used as a # ArgparseWriter is not intended to be used as a formatter_class.
# formatter_class. class ArgparseWriter(argparse.HelpFormatter, abc.ABC):
class ArgparseWriter(argparse.HelpFormatter): """Analyze an argparse ArgumentParser for easy generation of help."""
"""Analyzes an argparse ArgumentParser for easy generation of help."""
def __init__(self, prog, out=None, aliases=False): def __init__(self, prog: str, out: IO = sys.stdout, aliases: bool = False) -> None:
"""Initializes a new ArgparseWriter instance. """Initialize a new ArgparseWriter instance.
Parameters: Args:
prog (str): the program name prog: Program name.
out (file object): the file to write to (default sys.stdout) out: File object to write to.
aliases (bool): whether or not to include subparsers for aliases aliases: Whether or not to include subparsers for aliases.
""" """
super(ArgparseWriter, self).__init__(prog) super().__init__(prog)
self.level = 0 self.level = 0
self.prog = prog self.prog = prog
self.out = sys.stdout if out is None else out self.out = out
self.aliases = aliases self.aliases = aliases
def parse(self, parser, prog): def parse(self, parser: ArgumentParser, prog: str) -> Command:
"""Parses the parser object and returns the relavent components. """Parse the parser object and return the relavent components.
Parameters: Args:
parser (argparse.ArgumentParser): the parser parser: Command parser.
prog (str): the command name prog: Program name.
Returns: Returns:
(Command) information about the command from the parser Information about the command from the parser.
""" """
self.parser = parser self.parser = parser
@ -78,8 +88,7 @@ def parse(self, parser, prog):
groups = parser._mutually_exclusive_groups groups = parser._mutually_exclusive_groups
usage = fmt._format_usage(None, actions, groups, "").strip() 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
# and subcommands
optionals = [] optionals = []
positionals = [] positionals = []
subcommands = [] subcommands = []
@ -96,7 +105,7 @@ def parse(self, parser, prog):
subcommands.append((subparser, subaction.dest)) subcommands.append((subparser, subaction.dest))
# Look for aliases of the form 'name (alias, ...)' # Look for aliases of the form 'name (alias, ...)'
if self.aliases: if self.aliases and isinstance(subaction.metavar, str):
match = re.match(r"(.*) \((.*)\)", subaction.metavar) match = re.match(r"(.*) \((.*)\)", subaction.metavar)
if match: if match:
aliases = match.group(2).split(", ") aliases = match.group(2).split(", ")
@ -111,28 +120,26 @@ def parse(self, parser, prog):
return Command(prog, description, usage, positionals, optionals, subcommands) return Command(prog, description, usage, positionals, optionals, subcommands)
def format(self, cmd): @abc.abstractmethod
"""Returns the string representation of a single node in the def format(self, cmd: Command) -> str:
parser tree. """Return the string representation of a single node in the parser tree.
Override this in subclasses to define how each subcommand Override this in subclasses to define how each subcommand should be displayed.
should be displayed.
Parameters: Args:
(Command): parsed information about a command or subcommand cmd: Parsed information about a command or subcommand.
Returns: Returns:
str: the string representation of this subcommand String representation of this subcommand.
""" """
raise NotImplementedError
def _write(self, parser, prog, level=0): def _write(self, parser: ArgumentParser, prog: str, level: int = 0) -> None:
"""Recursively writes a parser. """Recursively write a parser.
Parameters: Args:
parser (argparse.ArgumentParser): the parser parser: Command parser.
prog (str): the command name prog: Program name.
level (int): the current level level: Current level.
""" """
self.level = level self.level = level
@ -142,19 +149,17 @@ def _write(self, parser, prog, level=0):
for subparser, prog in cmd.subcommands: for subparser, prog in cmd.subcommands:
self._write(subparser, prog, level=level + 1) self._write(subparser, prog, level=level + 1)
def write(self, parser): def write(self, parser: ArgumentParser) -> None:
"""Write out details about an ArgumentParser. """Write out details about an ArgumentParser.
Args: Args:
parser (argparse.ArgumentParser): the parser parser: Command parser.
""" """
try: try:
self._write(parser, self.prog) self._write(parser, self.prog)
except IOError as e: except BrokenPipeError:
# Swallow pipe errors # Swallow pipe errors
# Raises IOError in Python 2 and BrokenPipeError in Python 3 pass
if e.errno != errno.EPIPE:
raise
_rst_levels = ["=", "-", "^", "~", ":", "`"] _rst_levels = ["=", "-", "^", "~", ":", "`"]
@ -163,21 +168,33 @@ def write(self, parser):
class ArgparseRstWriter(ArgparseWriter): class ArgparseRstWriter(ArgparseWriter):
"""Write argparse output as rst sections.""" """Write argparse output as rst sections."""
def __init__(self, prog, out=None, aliases=False, rst_levels=_rst_levels): def __init__(
"""Create a new ArgparseRstWriter. self,
prog: str,
out: IO = sys.stdout,
aliases: bool = False,
rst_levels: Sequence[str] = _rst_levels,
) -> None:
"""Initialize a new ArgparseRstWriter instance.
Parameters: Args:
prog (str): program name prog: Program name.
out (file object): file to write to out: File object to write to.
aliases (bool): whether or not to include subparsers for aliases aliases: Whether or not to include subparsers for aliases.
rst_levels (list of str): list of characters rst_levels: List of characters for rst section headings.
for rst section headings
""" """
out = sys.stdout if out is None else out super().__init__(prog, out, aliases)
super(ArgparseRstWriter, self).__init__(prog, out, aliases)
self.rst_levels = rst_levels self.rst_levels = rst_levels
def format(self, cmd): def format(self, cmd: Command) -> str:
"""Return the string representation of a single node in the parser tree.
Args:
cmd: Parsed information about a command or subcommand.
Returns:
String representation of a node.
"""
string = io.StringIO() string = io.StringIO()
string.write(self.begin_command(cmd.prog)) string.write(self.begin_command(cmd.prog))
@ -203,7 +220,15 @@ def format(self, cmd):
return string.getvalue() return string.getvalue()
def begin_command(self, prog): def begin_command(self, prog: str) -> str:
"""Text to print before a command.
Args:
prog: Program name.
Returns:
Text before a command.
"""
return """ return """
---- ----
@ -216,10 +241,26 @@ def begin_command(self, prog):
prog.replace(" ", "-"), prog, self.rst_levels[self.level] * len(prog) prog.replace(" ", "-"), prog, self.rst_levels[self.level] * len(prog)
) )
def description(self, description): def description(self, description: str) -> str:
"""Description of a command.
Args:
description: Command description.
Returns:
Description of a command.
"""
return description + "\n\n" return description + "\n\n"
def usage(self, usage): def usage(self, usage: str) -> str:
"""Example usage of a command.
Args:
usage: Command usage.
Returns:
Usage of a command.
"""
return """\ return """\
.. code-block:: console .. code-block:: console
@ -229,10 +270,24 @@ def usage(self, usage):
usage usage
) )
def begin_positionals(self): def begin_positionals(self) -> str:
"""Text to print before positional arguments.
Returns:
Positional arguments header.
"""
return "\n**Positional arguments**\n\n" return "\n**Positional arguments**\n\n"
def positional(self, name, help): def positional(self, name: str, help: str) -> str:
"""Description of a positional argument.
Args:
name: Argument name.
help: Help text.
Returns:
Positional argument description.
"""
return """\ return """\
{0} {0}
{1} {1}
@ -241,13 +296,32 @@ def positional(self, name, help):
name, help name, help
) )
def end_positionals(self): def end_positionals(self) -> str:
"""Text to print after positional arguments.
Returns:
Positional arguments footer.
"""
return "" return ""
def begin_optionals(self): def begin_optionals(self) -> str:
"""Text to print before optional arguments.
Returns:
Optional arguments header.
"""
return "\n**Optional arguments**\n\n" return "\n**Optional arguments**\n\n"
def optional(self, opts, help): def optional(self, opts: str, help: str) -> str:
"""Description of an optional argument.
Args:
opts: Optional argument.
help: Help text.
Returns:
Optional argument description.
"""
return """\ return """\
``{0}`` ``{0}``
{1} {1}
@ -256,10 +330,23 @@ def optional(self, opts, help):
opts, help opts, help
) )
def end_optionals(self): def end_optionals(self) -> str:
"""Text to print after optional arguments.
Returns:
Optional arguments footer.
"""
return "" return ""
def begin_subcommands(self, subcommands): def begin_subcommands(self, subcommands: Sequence[Tuple[ArgumentParser, str]]) -> str:
"""Table with links to other subcommands.
Arguments:
subcommands: List of subcommands.
Returns:
Subcommand linking text.
"""
string = """ string = """
**Subcommands** **Subcommands**
@ -278,29 +365,25 @@ def begin_subcommands(self, subcommands):
class ArgparseCompletionWriter(ArgparseWriter): class ArgparseCompletionWriter(ArgparseWriter):
"""Write argparse output as shell programmable tab completion functions.""" """Write argparse output as shell programmable tab completion functions."""
def format(self, cmd): def format(self, cmd: Command) -> str:
"""Returns the string representation of a single node in the """Return the string representation of a single node in the parser tree.
parser tree.
Override this in subclasses to define how each subcommand Args:
should be displayed. cmd: Parsed information about a command or subcommand.
Parameters:
(Command): parsed information about a command or subcommand
Returns: Returns:
str: the string representation of this subcommand String representation of this subcommand.
""" """
assert cmd.optionals # we should always at least have -h, --help assert cmd.optionals # we should always at least have -h, --help
assert not (cmd.positionals and cmd.subcommands) # one or the other assert not (cmd.positionals and cmd.subcommands) # one or the other
# We only care about the arguments/flags, not the help messages # We only care about the arguments/flags, not the help messages
positionals = [] positionals: Tuple[str, ...] = ()
if cmd.positionals: if cmd.positionals:
positionals, _ = zip(*cmd.positionals) positionals, _ = zip(*cmd.positionals)
optionals, _, _ = zip(*cmd.optionals) optionals, _, _ = zip(*cmd.optionals)
subcommands = [] subcommands: Tuple[str, ...] = ()
if cmd.subcommands: if cmd.subcommands:
_, subcommands = zip(*cmd.subcommands) _, subcommands = zip(*cmd.subcommands)
@ -313,71 +396,73 @@ def format(self, cmd):
+ self.end_function(cmd.prog) + self.end_function(cmd.prog)
) )
def start_function(self, prog): def start_function(self, prog: str) -> str:
"""Returns the syntax needed to begin a function definition. """Return the syntax needed to begin a function definition.
Parameters: Args:
prog (str): the command name prog: Program name.
Returns: Returns:
str: the function definition beginning Function definition beginning.
""" """
name = prog.replace("-", "_").replace(" ", "_") name = prog.replace("-", "_").replace(" ", "_")
return "\n_{0}() {{".format(name) return "\n_{0}() {{".format(name)
def end_function(self, prog=None): def end_function(self, prog: str) -> str:
"""Returns the syntax needed to end a function definition. """Return the syntax needed to end a function definition.
Parameters: Args:
prog (str or None): the command name prog: Program name
Returns: Returns:
str: the function definition ending Function definition ending.
""" """
return "}\n" return "}\n"
def body(self, positionals, optionals, subcommands): def body(
"""Returns the body of the function. self, positionals: Sequence[str], optionals: Sequence[str], subcommands: Sequence[str]
) -> str:
"""Return the body of the function.
Parameters: Args:
positionals (list): list of positional arguments positionals: List of positional arguments.
optionals (list): list of optional arguments optionals: List of optional arguments.
subcommands (list): list of subcommand parsers subcommands: List of subcommand parsers.
Returns: Returns:
str: the function body Function body.
""" """
return "" return ""
def positionals(self, positionals): def positionals(self, positionals: Sequence[str]) -> str:
"""Returns the syntax for reporting positional arguments. """Return the syntax for reporting positional arguments.
Parameters: Args:
positionals (list): list of positional arguments positionals: List of positional arguments.
Returns: Returns:
str: the syntax for positional arguments Syntax for positional arguments.
""" """
return "" return ""
def optionals(self, optionals): def optionals(self, optionals: Sequence[str]) -> str:
"""Returns the syntax for reporting optional flags. """Return the syntax for reporting optional flags.
Parameters: Args:
optionals (list): list of optional arguments optionals: List of optional arguments.
Returns: Returns:
str: the syntax for optional flags Syntax for optional flags.
""" """
return "" return ""
def subcommands(self, subcommands): def subcommands(self, subcommands: Sequence[str]) -> str:
"""Returns the syntax for reporting subcommands. """Return the syntax for reporting subcommands.
Parameters: Args:
subcommands (list): list of subcommand parsers subcommands: List of subcommand parsers.
Returns: Returns:
str: the syntax for subcommand parsers Syntax for subcommand parsers
""" """
return "" return ""

View File

@ -8,10 +8,17 @@
import os import os
import re import re
import sys import sys
from argparse import ArgumentParser, Namespace
from typing import IO, Any, Callable, Dict, Sequence, Set
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.argparsewriter import ArgparseCompletionWriter, ArgparseRstWriter, ArgparseWriter from llnl.util.argparsewriter import (
ArgparseCompletionWriter,
ArgparseRstWriter,
ArgparseWriter,
Command,
)
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
import spack.cmd import spack.cmd
@ -25,12 +32,12 @@
#: list of command formatters #: list of command formatters
formatters = {} formatters: Dict[str, Callable[[Namespace, IO], None]] = {}
#: standard arguments for updating completion scripts #: standard arguments for updating completion scripts
#: we iterate through these when called with --update-completion #: we iterate through these when called with --update-completion
update_completion_args = { update_completion_args: Dict[str, Dict[str, Any]] = {
"bash": { "bash": {
"aliases": True, "aliases": True,
"format": "bash", "format": "bash",
@ -40,13 +47,25 @@
} }
def formatter(func): def formatter(func: Callable[[Namespace, IO], None]) -> Callable[[Namespace, IO], None]:
"""Decorator used to register formatters""" """Decorator used to register formatters.
Args:
func: Formatting function.
Returns:
The same function.
"""
formatters[func.__name__] = func formatters[func.__name__] = func
return func return func
def setup_parser(subparser): def setup_parser(subparser: ArgumentParser) -> None:
"""Set up the argument parser.
Args:
subparser: Preliminary argument parser.
"""
subparser.add_argument( subparser.add_argument(
"--update-completion", "--update-completion",
action="store_true", action="store_true",
@ -89,18 +108,34 @@ class SpackArgparseRstWriter(ArgparseRstWriter):
def __init__( def __init__(
self, self,
prog, prog: str,
out=None, out: IO = sys.stdout,
aliases=False, aliases: bool = False,
documented_commands=[], documented_commands: Set[str] = set(),
rst_levels=["-", "-", "^", "~", ":", "`"], rst_levels: Sequence[str] = ["-", "-", "^", "~", ":", "`"],
): ):
out = sys.stdout if out is None else out """Initialize a new SpackArgparseRstWriter instance.
super(SpackArgparseRstWriter, self).__init__(prog, out, aliases, rst_levels)
Args:
prog: Program name.
out: File object to write to.
aliases: Whether or not to include subparsers for aliases.
documented_commands: Set of commands with additional documentation.
rst_levels: List of characters for rst section headings.
"""
super().__init__(prog, out, aliases, rst_levels)
self.documented = documented_commands self.documented = documented_commands
def usage(self, *args): def usage(self, usage: str) -> str:
string = super(SpackArgparseRstWriter, self).usage(*args) """Example usage of a command.
Args:
usage: Command usage.
Returns:
Usage of a command.
"""
string = super().usage(usage)
cmd = self.parser.prog.replace(" ", "-") cmd = self.parser.prog.replace(" ", "-")
if cmd in self.documented: if cmd in self.documented:
@ -110,11 +145,21 @@ def usage(self, *args):
class SubcommandWriter(ArgparseWriter): class SubcommandWriter(ArgparseWriter):
def format(self, cmd): """Write argparse output as a list of subcommands."""
def format(self, cmd: Command) -> str:
"""Return the string representation of a single node in the parser tree.
Args:
cmd: Parsed information about a command or subcommand.
Returns:
String representation of this subcommand.
"""
return " " * self.level + cmd.prog + "\n" return " " * self.level + cmd.prog + "\n"
_positional_to_subroutine = { _positional_to_subroutine: Dict[str, str] = {
"package": "_all_packages", "package": "_all_packages",
"spec": "_all_packages", "spec": "_all_packages",
"filter": "_all_packages", "filter": "_all_packages",
@ -136,7 +181,19 @@ def format(self, cmd):
class BashCompletionWriter(ArgparseCompletionWriter): class BashCompletionWriter(ArgparseCompletionWriter):
"""Write argparse output as bash programmable tab completion.""" """Write argparse output as bash programmable tab completion."""
def body(self, positionals, optionals, subcommands): def body(
self, positionals: Sequence[str], optionals: Sequence[str], subcommands: Sequence[str]
) -> str:
"""Return the body of the function.
Args:
positionals: List of positional arguments.
optionals: List of optional arguments.
subcommands: List of subcommand parsers.
Returns:
Function body.
"""
if positionals: if positionals:
return """ return """
if $list_options if $list_options
@ -166,7 +223,15 @@ def body(self, positionals, optionals, subcommands):
self.optionals(optionals) self.optionals(optionals)
) )
def positionals(self, positionals): def positionals(self, positionals: Sequence[str]) -> str:
"""Return the syntax for reporting positional arguments.
Args:
positionals: List of positional arguments.
Returns:
Syntax for positional arguments.
"""
# If match found, return function name # If match found, return function name
for positional in positionals: for positional in positionals:
for key, value in _positional_to_subroutine.items(): for key, value in _positional_to_subroutine.items():
@ -176,22 +241,49 @@ def positionals(self, positionals):
# If no matches found, return empty list # If no matches found, return empty list
return 'SPACK_COMPREPLY=""' return 'SPACK_COMPREPLY=""'
def optionals(self, optionals): def optionals(self, optionals: Sequence[str]) -> str:
"""Return the syntax for reporting optional flags.
Args:
optionals: List of optional arguments.
Returns:
Syntax for optional flags.
"""
return 'SPACK_COMPREPLY="{0}"'.format(" ".join(optionals)) return 'SPACK_COMPREPLY="{0}"'.format(" ".join(optionals))
def subcommands(self, subcommands): def subcommands(self, subcommands: Sequence[str]) -> str:
"""Return the syntax for reporting subcommands.
Args:
subcommands: List of subcommand parsers.
Returns:
Syntax for subcommand parsers
"""
return 'SPACK_COMPREPLY="{0}"'.format(" ".join(subcommands)) return 'SPACK_COMPREPLY="{0}"'.format(" ".join(subcommands))
@formatter @formatter
def subcommands(args, out): def subcommands(args: Namespace, out: IO) -> None:
"""Hierarchical tree of subcommands.
args:
args: Command-line arguments.
out: File object to write to.
"""
parser = spack.main.make_argument_parser() parser = spack.main.make_argument_parser()
spack.main.add_all_commands(parser) spack.main.add_all_commands(parser)
writer = SubcommandWriter(parser.prog, out, args.aliases) writer = SubcommandWriter(parser.prog, out, args.aliases)
writer.write(parser) writer.write(parser)
def rst_index(out): def rst_index(out: IO) -> None:
"""Generate an index of all commands.
Args:
out: File object to write to.
"""
out.write("\n") out.write("\n")
index = spack.main.index_commands() index = spack.main.index_commands()
@ -219,13 +311,19 @@ def rst_index(out):
@formatter @formatter
def rst(args, out): def rst(args: Namespace, out: IO) -> None:
"""ReStructuredText documentation of subcommands.
args:
args: Command-line arguments.
out: File object to write to.
"""
# create a parser with all commands # create a parser with all commands
parser = spack.main.make_argument_parser() parser = spack.main.make_argument_parser()
spack.main.add_all_commands(parser) spack.main.add_all_commands(parser)
# extract cross-refs of the form `_cmd-spack-<cmd>:` from rst files # extract cross-refs of the form `_cmd-spack-<cmd>:` from rst files
documented_commands = set() documented_commands: Set[str] = set()
for filename in args.rst_files: for filename in args.rst_files:
with open(filename) as f: with open(filename) as f:
for line in f: for line in f:
@ -243,7 +341,13 @@ def rst(args, out):
@formatter @formatter
def names(args, out): def names(args: Namespace, out: IO) -> None:
"""Simple list of top-level commands.
args:
args: Command-line arguments.
out: File object to write to.
"""
commands = copy.copy(spack.cmd.all_commands()) commands = copy.copy(spack.cmd.all_commands())
if args.aliases: if args.aliases:
@ -253,7 +357,13 @@ def names(args, out):
@formatter @formatter
def bash(args, out): def bash(args: Namespace, out: IO) -> None:
"""Bash tab-completion script.
args:
args: Command-line arguments.
out: File object to write to.
"""
parser = spack.main.make_argument_parser() parser = spack.main.make_argument_parser()
spack.main.add_all_commands(parser) spack.main.add_all_commands(parser)
@ -261,7 +371,13 @@ def bash(args, out):
writer.write(parser) writer.write(parser)
def prepend_header(args, out): def prepend_header(args: Namespace, out: IO) -> None:
"""Prepend header text at the beginning of a file.
Args:
args: Command-line arguments.
out: File object to write to.
"""
if not args.header: if not args.header:
return return
@ -269,10 +385,14 @@ def prepend_header(args, out):
out.write(header.read()) out.write(header.read())
def _commands(parser, args): def _commands(parser: ArgumentParser, args: Namespace) -> None:
"""This is the 'regular' command, which can be called multiple times. """This is the 'regular' command, which can be called multiple times.
See ``commands()`` below for ``--update-completion`` handling. See ``commands()`` below for ``--update-completion`` handling.
Args:
parser: Argument parser.
args: Command-line arguments.
""" """
formatter = formatters[args.format] formatter = formatters[args.format]
@ -294,12 +414,15 @@ def _commands(parser, args):
formatter(args, sys.stdout) formatter(args, sys.stdout)
def update_completion(parser, args): def update_completion(parser: ArgumentParser, args: Namespace) -> None:
"""Iterate through the shells and update the standard completion files. """Iterate through the shells and update the standard completion files.
This is a convenience method to avoid calling this command many This is a convenience method to avoid calling this command many
times, and to simplify completion update for developers. times, and to simplify completion update for developers.
Args:
parser: Argument parser.
args: Command-line arguments.
""" """
for shell, shell_args in update_completion_args.items(): for shell, shell_args in update_completion_args.items():
for attr, value in shell_args.items(): for attr, value in shell_args.items():
@ -307,14 +430,20 @@ def update_completion(parser, args):
_commands(parser, args) _commands(parser, args)
def commands(parser, args): def commands(parser: ArgumentParser, args: Namespace) -> None:
"""Main function that calls formatter functions.
Args:
parser: Argument parser.
args: Command-line arguments.
"""
if args.update_completion: if args.update_completion:
if args.format != "names" or any([args.aliases, args.update, args.header]): if args.format != "names" or any([args.aliases, args.update, args.header]):
tty.die("--update-completion can only be specified alone.") tty.die("--update-completion can only be specified alone.")
# this runs the command multiple times with different arguments # this runs the command multiple times with different arguments
return update_completion(parser, args) update_completion(parser, args)
else: else:
# run commands normally # run commands normally
return _commands(parser, args) _commands(parser, args)

View File

@ -20,10 +20,8 @@
def test_format_not_overridden(): def test_format_not_overridden():
writer = aw.ArgparseWriter("spack") with pytest.raises(TypeError):
aw.ArgparseWriter("spack")
with pytest.raises(NotImplementedError):
writer.write(parser)
def test_completion_format_not_overridden(): def test_completion_format_not_overridden():