spack commands: add type hints and docstrings (#38705)
This commit is contained in:
		| @@ -3,31 +3,42 @@ | ||||
| # | ||||
| # SPDX-License-Identifier: (Apache-2.0 OR MIT) | ||||
| 
 | ||||
| import abc | ||||
| import argparse | ||||
| import errno | ||||
| import io | ||||
| import re | ||||
| 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. | ||||
| 
 | ||||
|     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) | ||||
|     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. | ||||
|     """ | ||||
| 
 | ||||
|     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.description = description | ||||
|         self.usage = usage | ||||
| @@ -36,35 +47,34 @@ def __init__(self, prog, description, usage, positionals, optionals, subcommands | ||||
|         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.""" | ||||
| # 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, abc.ABC): | ||||
|     """Analyze an argparse ArgumentParser for easy generation of help.""" | ||||
| 
 | ||||
|     def __init__(self, prog, out=None, aliases=False): | ||||
|         """Initializes a new ArgparseWriter instance. | ||||
|     def __init__(self, prog: str, out: IO = sys.stdout, aliases: bool = False) -> None: | ||||
|         """Initialize a new ArgparseWriter instance. | ||||
| 
 | ||||
|         Parameters: | ||||
|             prog (str): the program name | ||||
|             out (file object): the file to write to (default sys.stdout) | ||||
|             aliases (bool): whether or not to include subparsers for aliases | ||||
|         Args: | ||||
|             prog: Program name. | ||||
|             out: File object to write to. | ||||
|             aliases: Whether or not to include subparsers for aliases. | ||||
|         """ | ||||
|         super(ArgparseWriter, self).__init__(prog) | ||||
|         super().__init__(prog) | ||||
|         self.level = 0 | ||||
|         self.prog = prog | ||||
|         self.out = sys.stdout if out is None else out | ||||
|         self.out = out | ||||
|         self.aliases = aliases | ||||
| 
 | ||||
|     def parse(self, parser, prog): | ||||
|         """Parses the parser object and returns the relavent components. | ||||
|     def parse(self, parser: ArgumentParser, prog: str) -> Command: | ||||
|         """Parse the parser object and return the relavent components. | ||||
| 
 | ||||
|         Parameters: | ||||
|             parser (argparse.ArgumentParser): the parser | ||||
|             prog (str): the command name | ||||
|         Args: | ||||
|             parser: Command parser. | ||||
|             prog: Program name. | ||||
| 
 | ||||
|         Returns: | ||||
|             (Command) information about the command from the parser | ||||
|             Information about the command from the parser. | ||||
|         """ | ||||
|         self.parser = parser | ||||
| 
 | ||||
| @@ -78,8 +88,7 @@ def parse(self, parser, prog): | ||||
|         groups = parser._mutually_exclusive_groups | ||||
|         usage = fmt._format_usage(None, actions, groups, "").strip() | ||||
| 
 | ||||
|         # Go through actions and split them into optionals, positionals, | ||||
|         # and subcommands | ||||
|         # Go through actions and split them into optionals, positionals, and subcommands | ||||
|         optionals = [] | ||||
|         positionals = [] | ||||
|         subcommands = [] | ||||
| @@ -96,7 +105,7 @@ def parse(self, parser, prog): | ||||
|                     subcommands.append((subparser, subaction.dest)) | ||||
| 
 | ||||
|                     # 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) | ||||
|                         if match: | ||||
|                             aliases = match.group(2).split(", ") | ||||
| @@ -111,28 +120,26 @@ def parse(self, parser, prog): | ||||
| 
 | ||||
|         return Command(prog, description, usage, positionals, optionals, subcommands) | ||||
| 
 | ||||
|     def format(self, cmd): | ||||
|         """Returns the string representation of a single node in the | ||||
|         parser tree. | ||||
|     @abc.abstractmethod | ||||
|     def format(self, cmd: Command) -> str: | ||||
|         """Return the string representation of a single node in the parser tree. | ||||
| 
 | ||||
|         Override this in subclasses to define how each subcommand | ||||
|         should be displayed. | ||||
|         Override this in subclasses to define how each subcommand should be displayed. | ||||
| 
 | ||||
|         Parameters: | ||||
|             (Command): parsed information about a command or subcommand | ||||
|         Args: | ||||
|             cmd: Parsed information about a command or subcommand. | ||||
| 
 | ||||
|         Returns: | ||||
|             str: the string representation of this subcommand | ||||
|             String representation of this subcommand. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     def _write(self, parser, prog, level=0): | ||||
|         """Recursively writes a parser. | ||||
|     def _write(self, parser: ArgumentParser, prog: str, level: int = 0) -> None: | ||||
|         """Recursively write a parser. | ||||
| 
 | ||||
|         Parameters: | ||||
|             parser (argparse.ArgumentParser): the parser | ||||
|             prog (str): the command name | ||||
|             level (int): the current level | ||||
|         Args: | ||||
|             parser: Command parser. | ||||
|             prog: Program name. | ||||
|             level: Current level. | ||||
|         """ | ||||
|         self.level = level | ||||
| 
 | ||||
| @@ -142,19 +149,17 @@ def _write(self, parser, prog, level=0): | ||||
|         for subparser, prog in cmd.subcommands: | ||||
|             self._write(subparser, prog, level=level + 1) | ||||
| 
 | ||||
|     def write(self, parser): | ||||
|     def write(self, parser: ArgumentParser) -> None: | ||||
|         """Write out details about an ArgumentParser. | ||||
| 
 | ||||
|         Args: | ||||
|             parser (argparse.ArgumentParser): the parser | ||||
|             parser: Command parser. | ||||
|         """ | ||||
|         try: | ||||
|             self._write(parser, self.prog) | ||||
|         except IOError as e: | ||||
|         except BrokenPipeError: | ||||
|             # Swallow pipe errors | ||||
|             # Raises IOError in Python 2 and BrokenPipeError in Python 3 | ||||
|             if e.errno != errno.EPIPE: | ||||
|                 raise | ||||
|             pass | ||||
| 
 | ||||
| 
 | ||||
| _rst_levels = ["=", "-", "^", "~", ":", "`"] | ||||
| @@ -163,21 +168,33 @@ def write(self, parser): | ||||
| class ArgparseRstWriter(ArgparseWriter): | ||||
|     """Write argparse output as rst sections.""" | ||||
| 
 | ||||
|     def __init__(self, prog, out=None, aliases=False, rst_levels=_rst_levels): | ||||
|         """Create a new ArgparseRstWriter. | ||||
|     def __init__( | ||||
|         self, | ||||
|         prog: str, | ||||
|         out: IO = sys.stdout, | ||||
|         aliases: bool = False, | ||||
|         rst_levels: Sequence[str] = _rst_levels, | ||||
|     ) -> None: | ||||
|         """Initialize a new ArgparseRstWriter instance. | ||||
| 
 | ||||
|         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 | ||||
|         Args: | ||||
|             prog: Program name. | ||||
|             out: File object to write to. | ||||
|             aliases: Whether or not to include subparsers for aliases. | ||||
|             rst_levels: List of characters for rst section headings. | ||||
|         """ | ||||
|         out = sys.stdout if out is None else out | ||||
|         super(ArgparseRstWriter, self).__init__(prog, out, aliases) | ||||
|         super().__init__(prog, out, aliases) | ||||
|         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.write(self.begin_command(cmd.prog)) | ||||
| 
 | ||||
| @@ -203,7 +220,15 @@ def format(self, cmd): | ||||
| 
 | ||||
|         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 """ | ||||
| ---- | ||||
| 
 | ||||
| @@ -216,10 +241,26 @@ def begin_command(self, 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" | ||||
| 
 | ||||
|     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 """\ | ||||
| .. code-block:: console | ||||
| 
 | ||||
| @@ -229,10 +270,24 @@ def usage(self, 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" | ||||
| 
 | ||||
|     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 """\ | ||||
| {0} | ||||
|   {1} | ||||
| @@ -241,13 +296,32 @@ def positional(self, name, help): | ||||
|             name, help | ||||
|         ) | ||||
| 
 | ||||
|     def end_positionals(self): | ||||
|     def end_positionals(self) -> str: | ||||
|         """Text to print after positional arguments. | ||||
| 
 | ||||
|         Returns: | ||||
|             Positional arguments footer. | ||||
|         """ | ||||
|         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" | ||||
| 
 | ||||
|     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 """\ | ||||
| ``{0}`` | ||||
|   {1} | ||||
| @@ -256,10 +330,23 @@ def optional(self, opts, help): | ||||
|             opts, help | ||||
|         ) | ||||
| 
 | ||||
|     def end_optionals(self): | ||||
|     def end_optionals(self) -> str: | ||||
|         """Text to print after optional arguments. | ||||
| 
 | ||||
|         Returns: | ||||
|             Optional arguments footer. | ||||
|         """ | ||||
|         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 = """ | ||||
| **Subcommands** | ||||
| 
 | ||||
| @@ -278,29 +365,25 @@ def begin_subcommands(self, subcommands): | ||||
| class ArgparseCompletionWriter(ArgparseWriter): | ||||
|     """Write argparse output as shell programmable tab completion functions.""" | ||||
| 
 | ||||
|     def format(self, cmd): | ||||
|         """Returns the string representation of a single node in the | ||||
|         parser tree. | ||||
|     def format(self, cmd: Command) -> str: | ||||
|         """Return 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 | ||||
|         Args: | ||||
|             cmd: Parsed information about a command or subcommand. | ||||
| 
 | ||||
|         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 not (cmd.positionals and cmd.subcommands)  # one or the other | ||||
| 
 | ||||
|         # We only care about the arguments/flags, not the help messages | ||||
|         positionals = [] | ||||
|         positionals: Tuple[str, ...] = () | ||||
|         if cmd.positionals: | ||||
|             positionals, _ = zip(*cmd.positionals) | ||||
|         optionals, _, _ = zip(*cmd.optionals) | ||||
|         subcommands = [] | ||||
|         subcommands: Tuple[str, ...] = () | ||||
|         if cmd.subcommands: | ||||
|             _, subcommands = zip(*cmd.subcommands) | ||||
| 
 | ||||
| @@ -313,71 +396,73 @@ def format(self, cmd): | ||||
|             + self.end_function(cmd.prog) | ||||
|         ) | ||||
| 
 | ||||
|     def start_function(self, prog): | ||||
|         """Returns the syntax needed to begin a function definition. | ||||
|     def start_function(self, prog: str) -> str: | ||||
|         """Return the syntax needed to begin a function definition. | ||||
| 
 | ||||
|         Parameters: | ||||
|             prog (str): the command name | ||||
|         Args: | ||||
|             prog: Program name. | ||||
| 
 | ||||
|         Returns: | ||||
|             str: the function definition beginning | ||||
|             Function definition beginning. | ||||
|         """ | ||||
|         name = prog.replace("-", "_").replace(" ", "_") | ||||
|         return "\n_{0}() {{".format(name) | ||||
| 
 | ||||
|     def end_function(self, prog=None): | ||||
|         """Returns the syntax needed to end a function definition. | ||||
|     def end_function(self, prog: str) -> str: | ||||
|         """Return the syntax needed to end a function definition. | ||||
| 
 | ||||
|         Parameters: | ||||
|             prog (str or None): the command name | ||||
|         Args: | ||||
|             prog: Program name | ||||
| 
 | ||||
|         Returns: | ||||
|             str: the function definition ending | ||||
|             Function definition ending. | ||||
|         """ | ||||
|         return "}\n" | ||||
| 
 | ||||
|     def body(self, positionals, optionals, subcommands): | ||||
|         """Returns the body of the function. | ||||
|     def body( | ||||
|         self, positionals: Sequence[str], optionals: Sequence[str], subcommands: Sequence[str] | ||||
|     ) -> str: | ||||
|         """Return the body of the function. | ||||
| 
 | ||||
|         Parameters: | ||||
|             positionals (list): list of positional arguments | ||||
|             optionals (list): list of optional arguments | ||||
|             subcommands (list): list of subcommand parsers | ||||
|         Args: | ||||
|             positionals: List of positional arguments. | ||||
|             optionals: List of optional arguments. | ||||
|             subcommands: List of subcommand parsers. | ||||
| 
 | ||||
|         Returns: | ||||
|             str: the function body | ||||
|             Function body. | ||||
|         """ | ||||
|         return "" | ||||
| 
 | ||||
|     def positionals(self, positionals): | ||||
|         """Returns the syntax for reporting positional arguments. | ||||
|     def positionals(self, positionals: Sequence[str]) -> str: | ||||
|         """Return the syntax for reporting positional arguments. | ||||
| 
 | ||||
|         Parameters: | ||||
|             positionals (list): list of positional arguments | ||||
|         Args: | ||||
|             positionals: List of positional arguments. | ||||
| 
 | ||||
|         Returns: | ||||
|             str: the syntax for positional arguments | ||||
|             Syntax for positional arguments. | ||||
|         """ | ||||
|         return "" | ||||
| 
 | ||||
|     def optionals(self, optionals): | ||||
|         """Returns the syntax for reporting optional flags. | ||||
|     def optionals(self, optionals: Sequence[str]) -> str: | ||||
|         """Return the syntax for reporting optional flags. | ||||
| 
 | ||||
|         Parameters: | ||||
|             optionals (list): list of optional arguments | ||||
|         Args: | ||||
|             optionals: List of optional arguments. | ||||
| 
 | ||||
|         Returns: | ||||
|             str: the syntax for optional flags | ||||
|             Syntax for optional flags. | ||||
|         """ | ||||
|         return "" | ||||
| 
 | ||||
|     def subcommands(self, subcommands): | ||||
|         """Returns the syntax for reporting subcommands. | ||||
|     def subcommands(self, subcommands: Sequence[str]) -> str: | ||||
|         """Return the syntax for reporting subcommands. | ||||
| 
 | ||||
|         Parameters: | ||||
|             subcommands (list): list of subcommand parsers | ||||
|         Args: | ||||
|             subcommands: List of subcommand parsers. | ||||
| 
 | ||||
|         Returns: | ||||
|             str: the syntax for subcommand parsers | ||||
|             Syntax for subcommand parsers | ||||
|         """ | ||||
|         return "" | ||||
|   | ||||
| @@ -8,10 +8,17 @@ | ||||
| import os | ||||
| import re | ||||
| 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.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 | ||||
| 
 | ||||
| import spack.cmd | ||||
| @@ -25,12 +32,12 @@ | ||||
| 
 | ||||
| 
 | ||||
| #: list of command formatters | ||||
| formatters = {} | ||||
| formatters: Dict[str, Callable[[Namespace, IO], None]] = {} | ||||
| 
 | ||||
| 
 | ||||
| #: standard arguments for updating completion scripts | ||||
| #: we iterate through these when called with --update-completion | ||||
| update_completion_args = { | ||||
| update_completion_args: Dict[str, Dict[str, Any]] = { | ||||
|     "bash": { | ||||
|         "aliases": True, | ||||
|         "format": "bash", | ||||
| @@ -40,13 +47,25 @@ | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def formatter(func): | ||||
|     """Decorator used to register formatters""" | ||||
| def formatter(func: Callable[[Namespace, IO], None]) -> Callable[[Namespace, IO], None]: | ||||
|     """Decorator used to register formatters. | ||||
| 
 | ||||
|     Args: | ||||
|         func: Formatting function. | ||||
| 
 | ||||
|     Returns: | ||||
|         The same function. | ||||
|     """ | ||||
|     formatters[func.__name__] = 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( | ||||
|         "--update-completion", | ||||
|         action="store_true", | ||||
| @@ -89,18 +108,34 @@ class SpackArgparseRstWriter(ArgparseRstWriter): | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, | ||||
|         prog, | ||||
|         out=None, | ||||
|         aliases=False, | ||||
|         documented_commands=[], | ||||
|         rst_levels=["-", "-", "^", "~", ":", "`"], | ||||
|         prog: str, | ||||
|         out: IO = sys.stdout, | ||||
|         aliases: bool = False, | ||||
|         documented_commands: Set[str] = set(), | ||||
|         rst_levels: Sequence[str] = ["-", "-", "^", "~", ":", "`"], | ||||
|     ): | ||||
|         out = sys.stdout if out is None else out | ||||
|         super(SpackArgparseRstWriter, self).__init__(prog, out, aliases, rst_levels) | ||||
|         """Initialize a new SpackArgparseRstWriter instance. | ||||
| 
 | ||||
|         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 | ||||
| 
 | ||||
|     def usage(self, *args): | ||||
|         string = super(SpackArgparseRstWriter, self).usage(*args) | ||||
|     def usage(self, usage: str) -> str: | ||||
|         """Example usage of a command. | ||||
| 
 | ||||
|         Args: | ||||
|             usage: Command usage. | ||||
| 
 | ||||
|         Returns: | ||||
|             Usage of a command. | ||||
|         """ | ||||
|         string = super().usage(usage) | ||||
| 
 | ||||
|         cmd = self.parser.prog.replace(" ", "-") | ||||
|         if cmd in self.documented: | ||||
| @@ -110,11 +145,21 @@ def usage(self, *args): | ||||
| 
 | ||||
| 
 | ||||
| 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" | ||||
| 
 | ||||
| 
 | ||||
| _positional_to_subroutine = { | ||||
| _positional_to_subroutine: Dict[str, str] = { | ||||
|     "package": "_all_packages", | ||||
|     "spec": "_all_packages", | ||||
|     "filter": "_all_packages", | ||||
| @@ -136,7 +181,19 @@ def format(self, cmd): | ||||
| class BashCompletionWriter(ArgparseCompletionWriter): | ||||
|     """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: | ||||
|             return """ | ||||
|     if $list_options | ||||
| @@ -166,7 +223,15 @@ def body(self, positionals, optionals, subcommands): | ||||
|                 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 | ||||
|         for positional in positionals: | ||||
|             for key, value in _positional_to_subroutine.items(): | ||||
| @@ -176,22 +241,49 @@ def positionals(self, positionals): | ||||
|         # If no matches found, return empty list | ||||
|         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)) | ||||
| 
 | ||||
|     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)) | ||||
| 
 | ||||
| 
 | ||||
| @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() | ||||
|     spack.main.add_all_commands(parser) | ||||
|     writer = SubcommandWriter(parser.prog, out, args.aliases) | ||||
|     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") | ||||
| 
 | ||||
|     index = spack.main.index_commands() | ||||
| @@ -219,13 +311,19 @@ def rst_index(out): | ||||
| 
 | ||||
| 
 | ||||
| @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 | ||||
|     parser = spack.main.make_argument_parser() | ||||
|     spack.main.add_all_commands(parser) | ||||
| 
 | ||||
|     # 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: | ||||
|         with open(filename) as f: | ||||
|             for line in f: | ||||
| @@ -243,7 +341,13 @@ def rst(args, out): | ||||
| 
 | ||||
| 
 | ||||
| @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()) | ||||
| 
 | ||||
|     if args.aliases: | ||||
| @@ -253,7 +357,13 @@ def names(args, out): | ||||
| 
 | ||||
| 
 | ||||
| @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() | ||||
|     spack.main.add_all_commands(parser) | ||||
| 
 | ||||
| @@ -261,7 +371,13 @@ def bash(args, out): | ||||
|     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: | ||||
|         return | ||||
| 
 | ||||
| @@ -269,10 +385,14 @@ def prepend_header(args, out): | ||||
|         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. | ||||
| 
 | ||||
|     See ``commands()`` below for ``--update-completion`` handling. | ||||
| 
 | ||||
|     Args: | ||||
|         parser: Argument parser. | ||||
|         args: Command-line arguments. | ||||
|     """ | ||||
|     formatter = formatters[args.format] | ||||
| 
 | ||||
| @@ -294,12 +414,15 @@ def _commands(parser, args): | ||||
|         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. | ||||
| 
 | ||||
|     This is a convenience method to avoid calling this command many | ||||
|     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 attr, value in shell_args.items(): | ||||
| @@ -307,14 +430,20 @@ def update_completion(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.format != "names" or any([args.aliases, args.update, args.header]): | ||||
|             tty.die("--update-completion can only be specified alone.") | ||||
| 
 | ||||
|         # this runs the command multiple times with different arguments | ||||
|         return update_completion(parser, args) | ||||
|         update_completion(parser, args) | ||||
| 
 | ||||
|     else: | ||||
|         # run commands normally | ||||
|         return _commands(parser, args) | ||||
|         _commands(parser, args) | ||||
|   | ||||
| @@ -20,10 +20,8 @@ | ||||
| 
 | ||||
| 
 | ||||
| def test_format_not_overridden(): | ||||
|     writer = aw.ArgparseWriter("spack") | ||||
| 
 | ||||
|     with pytest.raises(NotImplementedError): | ||||
|         writer.write(parser) | ||||
|     with pytest.raises(TypeError): | ||||
|         aw.ArgparseWriter("spack") | ||||
| 
 | ||||
| 
 | ||||
| def test_completion_format_not_overridden(): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Adam J. Stewart
					Adam J. Stewart