File: generate_stdlib_docs.py

package info (click to toggle)
bpftrace 0.24.1-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,496 kB
  • sloc: cpp: 60,982; ansic: 10,952; python: 953; yacc: 665; sh: 536; lex: 295; makefile: 22
file content (191 lines) | stat: -rwxr-xr-x 6,495 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/usr/bin/env python3
"""
Python script to generate documentation in stdlib.md
"""

import glob
import os
import sys
import re
from typing import Optional

class Helper:
    name: str = ""
    variants: list[str] = []
    deprecated_variants: list[str] = []
    description: str = ""

    def __init__(self):
        self.variants = []
        self.deprecated_variants = []

def parse_variant_string(line: str) -> Optional[str]:
    pattern = r':variant\s+(.+)'

    match = re.match(pattern, line.strip())
    if match:
        return match.group(1).strip()

    return None

def parse_deprecated_variant_string(line: str) -> Optional[str]:
    pattern = r':deprecated_variant\s+(.+)'

    match = re.match(pattern, line.strip())
    if match:
        return match.group(1).strip()

    return None

def variant_has_no_args(variant: str) -> bool:
    return "()" in variant

def parse_function_name_string(line: str) -> Optional[str]:
    pattern = r':function\s+(.+)'

    match = re.match(pattern, line.strip())
    if match:
        return match.group(1).strip()

    return None

def parse_macro_name(line: str) -> Optional[str]:
    # Pattern to match "macro name("
    pattern = r'macro\s+([^(]+)\('

    match = re.match(pattern, line.strip())
    if match:
        return match.group(1).strip()

    return None

def read_file_lines(file_path: str) -> Optional[list[Helper]]:
    helpers: list[Helper] = []
    try:
        if not os.path.exists(file_path):
            print(f"Error: File '{file_path}' not found.")
            return None

        current = Helper()
        with open(file_path, 'r', encoding='utf-8') as file:
            for line in file:
                line_content = line.lstrip().rstrip()
                if line_content.startswith("//"):
                    # Don't strip, because we may have markdown with meaningful
                    # whitespace at the beginning of the line that we preserve.
                    line_content = line_content[2:]
                    if len(line_content) > 0 and line_content[:1].isspace():
                        line_content = line_content[1:]
                    if line_content.startswith(":variant"):
                        parsed_variant = parse_variant_string(line_content)
                        if parsed_variant:
                            current.variants.append(parsed_variant)
                            if variant_has_no_args(parsed_variant):
                                current.variants.append(parsed_variant.replace("()", ""))
                    elif line_content.startswith(":deprecated_variant"):
                        parsed_variant = parse_deprecated_variant_string(line_content)
                        if parsed_variant:
                            current.deprecated_variants.append(parsed_variant)

                    elif line_content.startswith(":function"):
                        parsed_function_name = parse_function_name_string(line_content)
                        if parsed_function_name:
                            current.name = parsed_function_name
                    elif line_content or current.description:
                        current.description += line_content + "\n"
                elif line_content.startswith("macro"):
                    parsed_name = parse_macro_name(line_content)
                    if parsed_name:
                        current.name = parsed_name
                        # There must at least be a description or it's an
                        # undocumented macro.
                        if current.description:
                            helpers.append(current)
                        else:
                            print(f"Warning: Helper '{current.name}' will not be added to the docs.")
                    current = Helper()
                else:
                    if current.name and current.description:
                        helpers.append(current)
                    current = Helper()

        if current.name and current.description:
            helpers.append(current)

        return helpers

    except PermissionError:
        print(f"Error: Permission denied to read '{file_path}'.")
        return None
    except UnicodeDecodeError:
        print(f"Error: Unable to decode '{file_path}' as UTF-8.")
        return None
    except Exception as e:
        print(f"Error reading file '{file_path}': {e}")
        return None

def write_markdown_doc(helpers: list[Helper]):
    print("Writing markdown file to docs/stdlib.md")

    current_lines = []
    with open("docs/stdlib.md", "r", encoding="utf-8") as file:
        current_lines = file.readlines()

    in_helpers_section = False
    cleaned_lines = []
    for raw_line in current_lines:
        line = raw_line.strip()
        if line == "## Helpers":
            in_helpers_section = True
        elif line == "## Map Value Functions":
            in_helpers_section = False
        elif in_helpers_section:
            continue

        cleaned_lines.append(raw_line)

    updated_lines = []
    for cleaned_line in cleaned_lines:
        line = cleaned_line.strip()
        if line == "## Helpers":
            updated_lines.append(cleaned_line)
            updated_lines.append("\n")

            for helper in helpers:
                updated_lines.append(f"### {helper.name}\n")
                for variant in helper.variants:
                    updated_lines.append(f"- `{variant}`\n")
                for variant in helper.deprecated_variants:
                    updated_lines.append(f"- deprecated `{variant}`\n")
                updated_lines.append("\n")
                updated_lines.append(f"{helper.description}\n")
                updated_lines.append("\n")
        else:
            updated_lines.append(cleaned_line)

    with open("docs/stdlib.md", "w", encoding="utf-8") as file:
        file.writelines(updated_lines)

def main():
    stdlib_files = glob.glob("src/stdlib/**/*.bt", recursive=True)

    if len(stdlib_files) == 0:
        print("Didn't find any stdlib files")
        sys.exit(1)
    else:
        print(f"All stdlib files: {stdlib_files}")

    all_helpers: list[Helper] = []
    for file in stdlib_files:
        helpers = read_file_lines(file)
        if helpers:
            all_helpers += helpers

    if len(all_helpers) > 0:
        sorted_by_name = sorted(all_helpers, key=lambda h: h.name)
        write_markdown_doc(sorted_by_name)
    else:
        sys.exit(1)

if __name__ == "__main__":
    main()