spack commands: add type hints and docstrings (#38705)
This commit is contained in:
parent
d35149d174
commit
2978911520
@ -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 ""
|
||||||
|
@ -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)
|
||||||
|
@ -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():
|
||||||
|
Loading…
Reference in New Issue
Block a user