Source code for concoursetools.cli.docstring
# (C) Crown Copyright GCHQ
"""
Concourse Tools uses a custom CLI tool for easier management of command line functions.
"""
from __future__ import annotations
from collections.abc import Callable, Generator
from dataclasses import dataclass
import inspect
import re
from typing import TypeVar
RE_PARAM = re.compile(r":param (.*?):")
RE_WHITESPACE = re.compile(r"\s+")
CLIFunction = Callable[..., None]
CLIFunctionT = TypeVar("CLIFunctionT", bound=CLIFunction)
T = TypeVar("T")
[docs]
@dataclass(frozen=True)
class Docstring:
"""
Represents a function docstring.
:param first_line: The first line of the docstring (separated by double whitespace).
:param description: The remaining description before any parameters.
:param parameters: A mapping of parameter name to description, with newlines replaced with whitespace.
"""
first_line: str
description: str
parameters: dict[str, str]
[docs]
@classmethod
def from_object(cls, obj: object) -> Docstring:
"""
Parse an object with a docstring.
:param obj: An object which may have a docstring, such as a function or module.
"""
raw_docstring = inspect.getdoc(obj) or ""
return cls.from_string(raw_docstring)
[docs]
@classmethod
def from_string(cls, raw_docstring: str) -> Docstring:
"""
Parse a docstring.
:param raw_docstring: The raw docstring of an object.
"""
try:
first_line, remaining_lines = raw_docstring.split("\n\n", maxsplit=1)
except ValueError:
if RE_PARAM.match(raw_docstring.strip()): # all we have are params with no description
first_line = ""
remaining_lines = raw_docstring.strip()
else:
first_line = raw_docstring.strip()
return cls(first_line, "", {})
description, *remaining_params = RE_PARAM.split(remaining_lines.lstrip())
parameters = {param: " ".join(info.split()).strip() for param, info in _pair_up(remaining_params)}
return cls(first_line, description.strip(), parameters)
def _pair_up(data: list[str]) -> Generator[tuple[str, str]]:
"""
Pair up a list of items.
:param data: A list to be paired.
:raises ValueError: If there are an odd number of values in the list.
:Example:
>>> list(_pair_up([1, 2, 3, 4, 5, 6]))
[(1, 2), (3, 4), (5, 6)]
"""
for i in range(0, len(data), 2):
try:
yield data[i], data[i + 1]
except IndexError:
raise ValueError(f"Needed an even number of values, got {len(data)}")