Skip to content

Commit

Permalink
Parse function.__doc__
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanvugt committed Sep 19, 2019
1 parent 90ad843 commit bcd9d82
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
49 changes: 46 additions & 3 deletions auto_cli/parsing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import inspect
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import re
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple, Union

from .types import Command
from .utils import _print_and_quit
Expand Down Expand Up @@ -31,7 +32,8 @@ def add_param_transformer(self, param_name: str, transform: Callable) -> None:
def create_parser(command: Command) -> ArgumentParser:
"""Create a parser for the given command"""
function = command.function
parser = ArgumentParser(description=f"{command.name}: {function.__doc__}")
function_doc = _parse_function_doc(function)
parser = ArgumentParser(description=f"{command.name}: {function_doc.description}")
signature = inspect.signature(function)
parameters = dict(signature.parameters)
argspec = inspect.getfullargspec(function)
Expand All @@ -51,7 +53,9 @@ def create_parser(command: Command) -> ArgumentParser:
**_get_type_params(annotation, param_name, function),
}

parser.add_argument(f"--{param_name}", **kwargs)
parser.add_argument(
f"--{param_name}", help=function_doc.param_docs.get(param_name), **kwargs
)
if kwargs.get("nargs", "+") != "+": # is_tuple: TODO(joris): refactor
parser.add_param_transformer(param_name, tuple)
# TODO(joris): parse documentation of param for help
Expand Down Expand Up @@ -112,3 +116,42 @@ def _fail(message: str) -> None:
return {"action": "store_true", "default": False, "required": False}

return {"type": annotation}


class _FunctionDoc(NamedTuple):
description: str
param_docs: Dict[str, str]


def _parse_function_doc(function: Callable) -> _FunctionDoc:
"""Parse function documentation which adheres to the Sphinx standard
https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists
"""
if not hasattr(function, "__doc__"):
return _FunctionDoc("", {})

doc = function.__doc__
if not ":param" in doc:
# Doesn't conform to our standard, so the whole __doc__ is just
# the description of the function
return _FunctionDoc(doc, {})

# Now that we now doc conforms to our standard, we can parse out the
# parameter descriptions

params_docs = {}
for line in doc.splitlines():
line = line.strip()
if line.startswith(":param"):
line = line.lstrip(":param")
before, _, description = line.partition(":")
param_name = before.strip().split(" ")[-1]
params_docs[param_name] = description.strip()

fn_description, _, _ = doc.partition(":param")
fn_description = _normalize_whitespace(fn_description.strip())
return _FunctionDoc(fn_description, params_docs)


def _normalize_whitespace(text: str) -> str:
return re.sub(r"\s+", " ", text)
17 changes: 16 additions & 1 deletion auto_cli/tests/test_parsing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Tuple

from auto_cli.parsing import create_parser
from auto_cli.parsing import _parse_function_doc, create_parser
from auto_cli.types import Command


Expand Down Expand Up @@ -67,3 +67,18 @@ def func_to_test(a: int, b):
parser = create_parser(command)
args = parser.parse(["--a", "4", "--b", "5"])
assert args == {"a": 4, "b": 5}


def test_parse_function_doc() -> None:
def func_to_test(a: int, b: str) -> int:
"""This is a function
which does some interesting things
:param int a: the number that is returned
:param b: ignored
"""
return a

docs = _parse_function_doc(func_to_test)
assert docs.description == "This is a function which does some interesting things"
assert docs.param_docs == {"a": "the number that is returned", "b": "ignored"}

0 comments on commit bcd9d82

Please sign in to comment.