• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oir / startle / 17195282036

24 Aug 2025 11:40PM UTC coverage: 98.525% (-0.2%) from 98.703%
17195282036

Pull #103

github

web-flow
Merge 09048dc1d into 20093f4ef
Pull Request #103: Render brief as markdown when printing help

192 of 192 branches covered (100.0%)

Branch coverage included in aggregate %.

877 of 893 relevant lines covered (98.21%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.68
startle/_docstr.py
1
import inspect
1✔
2
import re
1✔
3
from dataclasses import dataclass
1✔
4
from textwrap import dedent
1✔
5
from typing import Callable, Literal
1✔
6

7

8
@dataclass
1✔
9
class _DocstrParam:
1✔
10
    desc: str = ""
1✔
11
    short_name: str | None = None
1✔
12

13

14
_DocstrParams = dict[str, _DocstrParam]
1✔
15

16

17
class _DocstrParts:
1✔
18
    function_params_headers = ["Args:", "Arguments:"]
1✔
19
    class_params_headers = ["Attributes:"]
1✔
20
    brief_enders = [
1✔
21
        "Args:",
22
        "Arguments:",
23
        "Returns:",
24
        "Yields:",
25
        "Raises:",
26
        "Attributes:",
27
    ]
28

29
    param_pattern = re.compile(r"(\S+)(?:\s+(.*?))?:(.*)")
1✔
30
    # "param_name annotation: description", annotation optional
31

32
    short_name_pattern = re.compile(r"(?:(?<=^)|(?<=\s))\[(\S)\](?:(?=\s)|(?=$))")
1✔
33
    # "[a]", "... [a] ...", etc
34

35

36
def _parse_docstring(
1✔
37
    docstring: str, kind: Literal["function", "class"]
38
) -> tuple[str, _DocstrParams]:
39
    params_headers = (
1✔
40
        _DocstrParts.function_params_headers
41
        if kind == "function"
42
        else _DocstrParts.class_params_headers
43
    )
44

45
    brief = ""
1✔
46
    arg_helps: _DocstrParams = {}
1✔
47

48
    if docstring:
1✔
49
        lines = docstring.split("\n")
1✔
50

51
        # first, find the brief
52
        i = 0
1✔
53
        while i < len(lines) and lines[i].strip() not in _DocstrParts.brief_enders:
1✔
54
            brief += lines[i].rstrip() + "\n"
1✔
55
            i += 1
1✔
56

57
        brief = brief.rstrip()
1✔
58

59
        # then, find the Args section
60
        args_section = ""
1✔
61
        i = 0
1✔
62
        while lines[i].strip() not in params_headers:  # find the parameters section
1✔
63
            i += 1
1✔
64
            if i >= len(lines):
1✔
65
                break
1✔
66
        i += 1
1✔
67

68
        # then run through the lines until we find the first non-indented or empty line
69
        while i < len(lines) and lines[i].startswith(" ") and lines[i].strip() != "":
1✔
70
            args_section += lines[i] + "\n"
1✔
71
            i += 1
1✔
72

73
        if args_section:
1✔
74
            args_section = dedent(args_section).strip()
1✔
75

76
            # then, merge indented lines together
77
            merged_lines: list[str] = []
1✔
78
            for line in args_section.split("\n"):
1✔
79
                # if a line is indented, merge it with the previous line
80
                if line.lstrip() != line:
1✔
81
                    if not merged_lines:
1✔
82
                        return brief, {}
×
83
                    merged_lines[-1] += " " + line.strip()
1✔
84
                else:
85
                    merged_lines.append(line.strip())
1✔
86

87
            # now each line should be an arg description
88
            for line in merged_lines:
1✔
89
                # attempt to parse like "param_name annotation: description"
90
                if args_desc := _DocstrParts.param_pattern.search(line):
1✔
91
                    param, annot, desc = args_desc.groups()
1✔
92
                    param = param.strip()
1✔
93
                    annot = annot.strip() if annot else ""
1✔
94
                    desc = desc.strip()
1✔
95
                    short_name: str | None = None
1✔
96
                    if short_name_match := _DocstrParts.short_name_pattern.search(
1✔
97
                        annot
98
                    ):
99
                        short_name = short_name_match.group(1)
1✔
100
                    arg_helps[param] = _DocstrParam(desc=desc, short_name=short_name)
1✔
101

102
    return brief, arg_helps
1✔
103

104

105
def _parse_func_docstring(func: Callable) -> tuple[str, _DocstrParams]:
1✔
106
    """
107
    Parse the docstring of a function and return the brief and the arg descriptions.
108
    """
109
    docstring = inspect.getdoc(func) or ""
1✔
110

111
    return _parse_docstring(docstring, "function")
1✔
112

113

114
def _parse_class_docstring(cls: type) -> _DocstrParams:
1✔
115
    """
116
    Parse the docstring of a class and return the arg descriptions.
117
    """
118
    docstring = inspect.getdoc(cls) or ""
1✔
119

120
    _, arg_helps = _parse_docstring(docstring, "class")
1✔
121

122
    return arg_helps
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc