xref: /llvm-project/bolt/docs/generate_doc.py (revision 8bc02bf5c6e94489a79c8d924e5d9866fcc18417)
1#!/usr/bin/env python3
2# A tool to parse the output of `llvm-bolt --help-hidden` and update the
3# documentation in CommandLineArgumentReference.md automatically.
4# Run from the directory in which this file is located to update the docs.
5
6import subprocess
7from textwrap import wrap
8
9LINE_LIMIT = 80
10
11
12def wrap_text(text, indent, limit=LINE_LIMIT):
13    wrapped_lines = wrap(text, width=limit - len(indent))
14    wrapped_text = ("\n" + indent).join(wrapped_lines)
15    return wrapped_text
16
17
18def add_info(sections, section, option, description):
19    indent = "  "
20    wrapped_description = "\n".join(
21        [
22            wrap_text(line, indent) if len(line) > LINE_LIMIT else line
23            for line in description
24        ]
25    )
26    sections[section].append((option, indent + wrapped_description))
27
28
29def parse_bolt_options(output):
30    section_headers = [
31        "Generic options:",
32        "Output options:",
33        "BOLT generic options:",
34        "BOLT optimization options:",
35        "BOLT options in relocation mode:",
36        "BOLT instrumentation options:",
37        "BOLT printing options:",
38    ]
39
40    sections = {key: [] for key in section_headers}
41    current_section, prev_section = None, None
42    option, description = None, []
43
44    for line in output.split("\n"):
45        cleaned_line = line.strip()
46
47        if cleaned_line.casefold() in map(str.casefold, section_headers):
48            if prev_section is not None:  # Save last option from prev section
49                add_info(sections, current_section, option, description)
50                option, description = None, []
51
52            cleaned_line = cleaned_line.split()
53            # Apply lowercase to all words except the first one
54            cleaned_line = [cleaned_line[0]] + [
55                word.lower() for word in cleaned_line[1:]
56            ]
57            # Join the words back together into a string
58            cleaned_line = " ".join(cleaned_line)
59
60            current_section = cleaned_line
61            prev_section = current_section
62            continue
63
64        if cleaned_line.startswith("-"):
65            if option and description:
66                # Join description lines, adding an extra newline for
67                # sub-options that start with '='
68                add_info(sections, current_section, option, description)
69                option, description = None, []
70
71            parts = cleaned_line.split("  ", 1)
72            if len(parts) > 1:
73                option = parts[0].strip()
74                descr = parts[1].strip()
75                descr = descr[2].upper() + descr[3:]
76                description = [descr]
77                if option.startswith("--print") or option.startswith("--time"):
78                    current_section = "BOLT printing options:"
79                elif prev_section is not None:
80                    current_section = prev_section
81            continue
82
83        if cleaned_line.startswith("="):
84            parts = cleaned_line.split(maxsplit=1)
85            # Split into two parts: sub-option and description
86            if len(parts) == 2:
87                # Rejoin with a single space
88                cleaned_line = parts[0] + " " + parts[1].rstrip()
89            description.append(cleaned_line)
90        elif cleaned_line:  # Multiline description continuation
91            description.append(cleaned_line)
92
93    add_info(sections, current_section, option, description)
94    return sections
95
96
97def generate_markdown(sections):
98    markdown_lines = [
99        "# BOLT - a post-link optimizer developed to speed up large applications\n",
100        "## SYNOPSIS\n",
101        "`llvm-bolt <executable> [-o outputfile] <executable>.bolt "
102        "[-data=perf.fdata] [options]`\n",
103        "## OPTIONS",
104    ]
105
106    for section, options in sections.items():
107        markdown_lines.append(f"\n### {section}")
108        if section == "BOLT instrumentation options:":
109            markdown_lines.append(
110                f"\n`llvm-bolt <executable> -instrument"
111                " [-o outputfile] <instrumented-executable>`"
112            )
113        for option, desc in options:
114            markdown_lines.append(f"\n- `{option}`\n")
115            # Split description into lines to handle sub-options
116            desc_lines = desc.split("\n")
117            for line in desc_lines:
118                if line.startswith("="):
119                    # Sub-option: correct formatting with bullet
120                    sub_option, sub_desc = line[1:].split(" ", 1)
121                    markdown_lines.append(f"  - `{sub_option}`: {sub_desc[4:]}")
122                else:
123                    # Regular line of description
124                    if line[2:].startswith("<"):
125                        line = line.replace("<", "").replace(">", "")
126                    markdown_lines.append(f"{line}")
127
128    return "\n".join(markdown_lines)
129
130
131def main():
132    try:
133        help_output = subprocess.run(
134            ["llvm-bolt", "--help-hidden"], capture_output=True, text=True, check=True
135        ).stdout
136    except subprocess.CalledProcessError as e:
137        print("Failed to execute llvm-bolt --help:")
138        print(e)
139        return
140
141    sections = parse_bolt_options(help_output)
142    markdown = generate_markdown(sections)
143
144    with open("CommandLineArgumentReference.md", "w") as md_file:
145        md_file.write(markdown)
146
147
148if __name__ == "__main__":
149    main()
150