xref: /linux/tools/net/ynl/ynl-gen-rst.py (revision fe09ae5fb93be371a72d1b2ac62fb3eead51a728)
1f061c9f7SBreno Leitao#!/usr/bin/env python3
2f061c9f7SBreno Leitao# SPDX-License-Identifier: GPL-2.0
3f061c9f7SBreno Leitao# -*- coding: utf-8; mode: python -*-
4f061c9f7SBreno Leitao
5f061c9f7SBreno Leitao"""
6f061c9f7SBreno Leitao    Script to auto generate the documentation for Netlink specifications.
7f061c9f7SBreno Leitao
8f061c9f7SBreno Leitao    :copyright:  Copyright (C) 2023  Breno Leitao <leitao@debian.org>
9f061c9f7SBreno Leitao    :license:    GPL Version 2, June 1991 see linux/COPYING for details.
10f061c9f7SBreno Leitao
11f061c9f7SBreno Leitao    This script performs extensive parsing to the Linux kernel's netlink YAML
12f061c9f7SBreno Leitao    spec files, in an effort to avoid needing to heavily mark up the original
13f061c9f7SBreno Leitao    YAML file.
14f061c9f7SBreno Leitao
15f061c9f7SBreno Leitao    This code is split in three big parts:
16f061c9f7SBreno Leitao        1) RST formatters: Use to convert a string to a RST output
17f061c9f7SBreno Leitao        2) Parser helpers: Functions to parse the YAML data structure
18f061c9f7SBreno Leitao        3) Main function and small helpers
19f061c9f7SBreno Leitao"""
20f061c9f7SBreno Leitao
21f061c9f7SBreno Leitaofrom typing import Any, Dict, List
22f061c9f7SBreno Leitaoimport os.path
23f061c9f7SBreno Leitaoimport sys
24f061c9f7SBreno Leitaoimport argparse
25f061c9f7SBreno Leitaoimport logging
26f061c9f7SBreno Leitaoimport yaml
27f061c9f7SBreno Leitao
28f061c9f7SBreno Leitao
29f061c9f7SBreno LeitaoSPACE_PER_LEVEL = 4
30f061c9f7SBreno Leitao
31f061c9f7SBreno Leitao
32f061c9f7SBreno Leitao# RST Formatters
33f061c9f7SBreno Leitao# ==============
34f061c9f7SBreno Leitaodef headroom(level: int) -> str:
35f061c9f7SBreno Leitao    """Return space to format"""
36f061c9f7SBreno Leitao    return " " * (level * SPACE_PER_LEVEL)
37f061c9f7SBreno Leitao
38f061c9f7SBreno Leitao
39f061c9f7SBreno Leitaodef bold(text: str) -> str:
40f061c9f7SBreno Leitao    """Format bold text"""
41f061c9f7SBreno Leitao    return f"**{text}**"
42f061c9f7SBreno Leitao
43f061c9f7SBreno Leitao
44f061c9f7SBreno Leitaodef inline(text: str) -> str:
45f061c9f7SBreno Leitao    """Format inline text"""
46f061c9f7SBreno Leitao    return f"``{text}``"
47f061c9f7SBreno Leitao
48f061c9f7SBreno Leitao
49f061c9f7SBreno Leitaodef sanitize(text: str) -> str:
50f061c9f7SBreno Leitao    """Remove newlines and multiple spaces"""
51f061c9f7SBreno Leitao    # This is useful for some fields that are spread across multiple lines
52f061c9f7SBreno Leitao    return str(text).replace("\n", "").strip()
53f061c9f7SBreno Leitao
54f061c9f7SBreno Leitao
55f061c9f7SBreno Leitaodef rst_fields(key: str, value: str, level: int = 0) -> str:
56f061c9f7SBreno Leitao    """Return a RST formatted field"""
57f061c9f7SBreno Leitao    return headroom(level) + f":{key}: {value}"
58f061c9f7SBreno Leitao
59f061c9f7SBreno Leitao
60f061c9f7SBreno Leitaodef rst_definition(key: str, value: Any, level: int = 0) -> str:
61f061c9f7SBreno Leitao    """Format a single rst definition"""
62f061c9f7SBreno Leitao    return headroom(level) + key + "\n" + headroom(level + 1) + str(value)
63f061c9f7SBreno Leitao
64f061c9f7SBreno Leitao
65f061c9f7SBreno Leitaodef rst_paragraph(paragraph: str, level: int = 0) -> str:
66f061c9f7SBreno Leitao    """Return a formatted paragraph"""
67f061c9f7SBreno Leitao    return headroom(level) + paragraph
68f061c9f7SBreno Leitao
69f061c9f7SBreno Leitao
70f061c9f7SBreno Leitaodef rst_bullet(item: str, level: int = 0) -> str:
71f061c9f7SBreno Leitao    """Return a formatted a bullet"""
72f061c9f7SBreno Leitao    return headroom(level) + f"- {item}"
73f061c9f7SBreno Leitao
74f061c9f7SBreno Leitao
75f061c9f7SBreno Leitaodef rst_subsection(title: str) -> str:
76f061c9f7SBreno Leitao    """Add a sub-section to the document"""
77f061c9f7SBreno Leitao    return f"{title}\n" + "-" * len(title)
78f061c9f7SBreno Leitao
79f061c9f7SBreno Leitao
80f061c9f7SBreno Leitaodef rst_subsubsection(title: str) -> str:
81f061c9f7SBreno Leitao    """Add a sub-sub-section to the document"""
82f061c9f7SBreno Leitao    return f"{title}\n" + "~" * len(title)
83f061c9f7SBreno Leitao
84f061c9f7SBreno Leitao
85f061c9f7SBreno Leitaodef rst_section(title: str) -> str:
86f061c9f7SBreno Leitao    """Add a section to the document"""
87f061c9f7SBreno Leitao    return f"\n{title}\n" + "=" * len(title)
88f061c9f7SBreno Leitao
89f061c9f7SBreno Leitao
90f061c9f7SBreno Leitaodef rst_subtitle(title: str) -> str:
91f061c9f7SBreno Leitao    """Add a subtitle to the document"""
92f061c9f7SBreno Leitao    return "\n" + "-" * len(title) + f"\n{title}\n" + "-" * len(title) + "\n\n"
93f061c9f7SBreno Leitao
94f061c9f7SBreno Leitao
95f061c9f7SBreno Leitaodef rst_title(title: str) -> str:
96f061c9f7SBreno Leitao    """Add a title to the document"""
97f061c9f7SBreno Leitao    return "=" * len(title) + f"\n{title}\n" + "=" * len(title) + "\n\n"
98f061c9f7SBreno Leitao
99f061c9f7SBreno Leitao
100f061c9f7SBreno Leitaodef rst_list_inline(list_: List[str], level: int = 0) -> str:
101f061c9f7SBreno Leitao    """Format a list using inlines"""
102f061c9f7SBreno Leitao    return headroom(level) + "[" + ", ".join(inline(i) for i in list_) + "]"
103f061c9f7SBreno Leitao
104f061c9f7SBreno Leitao
105f061c9f7SBreno Leitaodef rst_header() -> str:
106f061c9f7SBreno Leitao    """The headers for all the auto generated RST files"""
107f061c9f7SBreno Leitao    lines = []
108f061c9f7SBreno Leitao
109f061c9f7SBreno Leitao    lines.append(rst_paragraph(".. SPDX-License-Identifier: GPL-2.0"))
110f061c9f7SBreno Leitao    lines.append(rst_paragraph(".. NOTE: This document was auto-generated.\n\n"))
111f061c9f7SBreno Leitao
112f061c9f7SBreno Leitao    return "\n".join(lines)
113f061c9f7SBreno Leitao
114f061c9f7SBreno Leitao
115f061c9f7SBreno Leitaodef rst_toctree(maxdepth: int = 2) -> str:
116f061c9f7SBreno Leitao    """Generate a toctree RST primitive"""
117f061c9f7SBreno Leitao    lines = []
118f061c9f7SBreno Leitao
119f061c9f7SBreno Leitao    lines.append(".. toctree::")
120f061c9f7SBreno Leitao    lines.append(f"   :maxdepth: {maxdepth}\n\n")
121f061c9f7SBreno Leitao
122f061c9f7SBreno Leitao    return "\n".join(lines)
123f061c9f7SBreno Leitao
124f061c9f7SBreno Leitao
125e8c780a5SJakub Kicinskidef rst_label(title: str) -> str:
126e8c780a5SJakub Kicinski    """Return a formatted label"""
127e8c780a5SJakub Kicinski    return f".. _{title}:\n\n"
128e8c780a5SJakub Kicinski
129e8c780a5SJakub Kicinski
130f061c9f7SBreno Leitao# Parsers
131f061c9f7SBreno Leitao# =======
132f061c9f7SBreno Leitao
133f061c9f7SBreno Leitao
134f061c9f7SBreno Leitaodef parse_mcast_group(mcast_group: List[Dict[str, Any]]) -> str:
135f061c9f7SBreno Leitao    """Parse 'multicast' group list and return a formatted string"""
136f061c9f7SBreno Leitao    lines = []
137f061c9f7SBreno Leitao    for group in mcast_group:
138f061c9f7SBreno Leitao        lines.append(rst_bullet(group["name"]))
139f061c9f7SBreno Leitao
140f061c9f7SBreno Leitao    return "\n".join(lines)
141f061c9f7SBreno Leitao
142f061c9f7SBreno Leitao
143f061c9f7SBreno Leitaodef parse_do(do_dict: Dict[str, Any], level: int = 0) -> str:
144f061c9f7SBreno Leitao    """Parse 'do' section and return a formatted string"""
145f061c9f7SBreno Leitao    lines = []
146f061c9f7SBreno Leitao    for key in do_dict.keys():
147f061c9f7SBreno Leitao        lines.append(rst_paragraph(bold(key), level + 1))
148f061c9f7SBreno Leitao        lines.append(parse_do_attributes(do_dict[key], level + 1) + "\n")
149f061c9f7SBreno Leitao
150f061c9f7SBreno Leitao    return "\n".join(lines)
151f061c9f7SBreno Leitao
152f061c9f7SBreno Leitao
153f061c9f7SBreno Leitaodef parse_do_attributes(attrs: Dict[str, Any], level: int = 0) -> str:
154f061c9f7SBreno Leitao    """Parse 'attributes' section"""
155f061c9f7SBreno Leitao    if "attributes" not in attrs:
156f061c9f7SBreno Leitao        return ""
157f061c9f7SBreno Leitao    lines = [rst_fields("attributes", rst_list_inline(attrs["attributes"]), level + 1)]
158f061c9f7SBreno Leitao
159f061c9f7SBreno Leitao    return "\n".join(lines)
160f061c9f7SBreno Leitao
161f061c9f7SBreno Leitao
162f061c9f7SBreno Leitaodef parse_operations(operations: List[Dict[str, Any]]) -> str:
163f061c9f7SBreno Leitao    """Parse operations block"""
164f061c9f7SBreno Leitao    preprocessed = ["name", "doc", "title", "do", "dump"]
165f061c9f7SBreno Leitao    lines = []
166f061c9f7SBreno Leitao
167f061c9f7SBreno Leitao    for operation in operations:
168f061c9f7SBreno Leitao        lines.append(rst_section(operation["name"]))
169f061c9f7SBreno Leitao        lines.append(rst_paragraph(sanitize(operation["doc"])) + "\n")
170f061c9f7SBreno Leitao
171f061c9f7SBreno Leitao        for key in operation.keys():
172f061c9f7SBreno Leitao            if key in preprocessed:
173f061c9f7SBreno Leitao                # Skip the special fields
174f061c9f7SBreno Leitao                continue
175f061c9f7SBreno Leitao            lines.append(rst_fields(key, operation[key], 0))
176f061c9f7SBreno Leitao
177f061c9f7SBreno Leitao        if "do" in operation:
178f061c9f7SBreno Leitao            lines.append(rst_paragraph(":do:", 0))
179f061c9f7SBreno Leitao            lines.append(parse_do(operation["do"], 0))
180f061c9f7SBreno Leitao        if "dump" in operation:
181f061c9f7SBreno Leitao            lines.append(rst_paragraph(":dump:", 0))
182f061c9f7SBreno Leitao            lines.append(parse_do(operation["dump"], 0))
183f061c9f7SBreno Leitao
184f061c9f7SBreno Leitao        # New line after fields
185f061c9f7SBreno Leitao        lines.append("\n")
186f061c9f7SBreno Leitao
187f061c9f7SBreno Leitao    return "\n".join(lines)
188f061c9f7SBreno Leitao
189f061c9f7SBreno Leitao
190f061c9f7SBreno Leitaodef parse_entries(entries: List[Dict[str, Any]], level: int) -> str:
191f061c9f7SBreno Leitao    """Parse a list of entries"""
192*fe09ae5fSDonald Hunter    ignored = ["pad"]
193f061c9f7SBreno Leitao    lines = []
194f061c9f7SBreno Leitao    for entry in entries:
195f061c9f7SBreno Leitao        if isinstance(entry, dict):
196f061c9f7SBreno Leitao            # entries could be a list or a dictionary
197*fe09ae5fSDonald Hunter            field_name = entry.get("name", "")
198*fe09ae5fSDonald Hunter            if field_name in ignored:
199*fe09ae5fSDonald Hunter                continue
200*fe09ae5fSDonald Hunter            type_ = entry.get("type")
201*fe09ae5fSDonald Hunter            if type_:
202*fe09ae5fSDonald Hunter                field_name += f" ({inline(type_)})"
203f061c9f7SBreno Leitao            lines.append(
204*fe09ae5fSDonald Hunter                rst_fields(field_name, sanitize(entry.get("doc", "")), level)
205f061c9f7SBreno Leitao            )
206f061c9f7SBreno Leitao        elif isinstance(entry, list):
207f061c9f7SBreno Leitao            lines.append(rst_list_inline(entry, level))
208f061c9f7SBreno Leitao        else:
209f061c9f7SBreno Leitao            lines.append(rst_bullet(inline(sanitize(entry)), level))
210f061c9f7SBreno Leitao
211f061c9f7SBreno Leitao    lines.append("\n")
212f061c9f7SBreno Leitao    return "\n".join(lines)
213f061c9f7SBreno Leitao
214f061c9f7SBreno Leitao
215f061c9f7SBreno Leitaodef parse_definitions(defs: Dict[str, Any]) -> str:
216f061c9f7SBreno Leitao    """Parse definitions section"""
217f061c9f7SBreno Leitao    preprocessed = ["name", "entries", "members"]
218f061c9f7SBreno Leitao    ignored = ["render-max"]  # This is not printed
219f061c9f7SBreno Leitao    lines = []
220f061c9f7SBreno Leitao
221f061c9f7SBreno Leitao    for definition in defs:
222f061c9f7SBreno Leitao        lines.append(rst_section(definition["name"]))
223f061c9f7SBreno Leitao        for k in definition.keys():
224f061c9f7SBreno Leitao            if k in preprocessed + ignored:
225f061c9f7SBreno Leitao                continue
226f061c9f7SBreno Leitao            lines.append(rst_fields(k, sanitize(definition[k]), 0))
227f061c9f7SBreno Leitao
228f061c9f7SBreno Leitao        # Field list needs to finish with a new line
229f061c9f7SBreno Leitao        lines.append("\n")
230f061c9f7SBreno Leitao        if "entries" in definition:
231f061c9f7SBreno Leitao            lines.append(rst_paragraph(":entries:", 0))
232f061c9f7SBreno Leitao            lines.append(parse_entries(definition["entries"], 1))
233f061c9f7SBreno Leitao        if "members" in definition:
234f061c9f7SBreno Leitao            lines.append(rst_paragraph(":members:", 0))
235f061c9f7SBreno Leitao            lines.append(parse_entries(definition["members"], 1))
236f061c9f7SBreno Leitao
237f061c9f7SBreno Leitao    return "\n".join(lines)
238f061c9f7SBreno Leitao
239f061c9f7SBreno Leitao
240f061c9f7SBreno Leitaodef parse_attr_sets(entries: List[Dict[str, Any]]) -> str:
241f061c9f7SBreno Leitao    """Parse attribute from attribute-set"""
242f061c9f7SBreno Leitao    preprocessed = ["name", "type"]
243f061c9f7SBreno Leitao    ignored = ["checks"]
244f061c9f7SBreno Leitao    lines = []
245f061c9f7SBreno Leitao
246f061c9f7SBreno Leitao    for entry in entries:
247f061c9f7SBreno Leitao        lines.append(rst_section(entry["name"]))
248f061c9f7SBreno Leitao        for attr in entry["attributes"]:
249f061c9f7SBreno Leitao            type_ = attr.get("type")
250e9d7c592SDonald Hunter            attr_line = attr["name"]
251f061c9f7SBreno Leitao            if type_:
252f061c9f7SBreno Leitao                # Add the attribute type in the same line
253f061c9f7SBreno Leitao                attr_line += f" ({inline(type_)})"
254f061c9f7SBreno Leitao
255f061c9f7SBreno Leitao            lines.append(rst_subsubsection(attr_line))
256f061c9f7SBreno Leitao
257f061c9f7SBreno Leitao            for k in attr.keys():
258f061c9f7SBreno Leitao                if k in preprocessed + ignored:
259f061c9f7SBreno Leitao                    continue
2609b0aa224SDonald Hunter                lines.append(rst_fields(k, sanitize(attr[k]), 0))
261f061c9f7SBreno Leitao            lines.append("\n")
262f061c9f7SBreno Leitao
263f061c9f7SBreno Leitao    return "\n".join(lines)
264f061c9f7SBreno Leitao
265f061c9f7SBreno Leitao
2666235b3d8SDonald Hunterdef parse_sub_messages(entries: List[Dict[str, Any]]) -> str:
2676235b3d8SDonald Hunter    """Parse sub-message definitions"""
2686235b3d8SDonald Hunter    lines = []
2696235b3d8SDonald Hunter
2706235b3d8SDonald Hunter    for entry in entries:
2716235b3d8SDonald Hunter        lines.append(rst_section(entry["name"]))
2726235b3d8SDonald Hunter        for fmt in entry["formats"]:
2736235b3d8SDonald Hunter            value = fmt["value"]
2746235b3d8SDonald Hunter
2756235b3d8SDonald Hunter            lines.append(rst_bullet(bold(value)))
2766235b3d8SDonald Hunter            for attr in ['fixed-header', 'attribute-set']:
2776235b3d8SDonald Hunter                if attr in fmt:
2789b0aa224SDonald Hunter                    lines.append(rst_fields(attr, fmt[attr], 1))
2796235b3d8SDonald Hunter            lines.append("\n")
2806235b3d8SDonald Hunter
2816235b3d8SDonald Hunter    return "\n".join(lines)
2826235b3d8SDonald Hunter
2836235b3d8SDonald Hunter
284f061c9f7SBreno Leitaodef parse_yaml(obj: Dict[str, Any]) -> str:
285f061c9f7SBreno Leitao    """Format the whole YAML into a RST string"""
286f061c9f7SBreno Leitao    lines = []
287f061c9f7SBreno Leitao
288f061c9f7SBreno Leitao    # Main header
289f061c9f7SBreno Leitao
290f061c9f7SBreno Leitao    lines.append(rst_header())
291f061c9f7SBreno Leitao
292f061c9f7SBreno Leitao    title = f"Family ``{obj['name']}`` netlink specification"
293f061c9f7SBreno Leitao    lines.append(rst_title(title))
294f061c9f7SBreno Leitao    lines.append(rst_paragraph(".. contents::\n"))
295f061c9f7SBreno Leitao
296f061c9f7SBreno Leitao    if "doc" in obj:
297f061c9f7SBreno Leitao        lines.append(rst_subtitle("Summary"))
298f061c9f7SBreno Leitao        lines.append(rst_paragraph(obj["doc"], 0))
299f061c9f7SBreno Leitao
300f061c9f7SBreno Leitao    # Operations
301f061c9f7SBreno Leitao    if "operations" in obj:
302f061c9f7SBreno Leitao        lines.append(rst_subtitle("Operations"))
303f061c9f7SBreno Leitao        lines.append(parse_operations(obj["operations"]["list"]))
304f061c9f7SBreno Leitao
305f061c9f7SBreno Leitao    # Multicast groups
306f061c9f7SBreno Leitao    if "mcast-groups" in obj:
307f061c9f7SBreno Leitao        lines.append(rst_subtitle("Multicast groups"))
308f061c9f7SBreno Leitao        lines.append(parse_mcast_group(obj["mcast-groups"]["list"]))
309f061c9f7SBreno Leitao
310f061c9f7SBreno Leitao    # Definitions
311f061c9f7SBreno Leitao    if "definitions" in obj:
312f061c9f7SBreno Leitao        lines.append(rst_subtitle("Definitions"))
313f061c9f7SBreno Leitao        lines.append(parse_definitions(obj["definitions"]))
314f061c9f7SBreno Leitao
315f061c9f7SBreno Leitao    # Attributes set
316f061c9f7SBreno Leitao    if "attribute-sets" in obj:
317f061c9f7SBreno Leitao        lines.append(rst_subtitle("Attribute sets"))
318f061c9f7SBreno Leitao        lines.append(parse_attr_sets(obj["attribute-sets"]))
319f061c9f7SBreno Leitao
3206235b3d8SDonald Hunter    # Sub-messages
3216235b3d8SDonald Hunter    if "sub-messages" in obj:
3226235b3d8SDonald Hunter        lines.append(rst_subtitle("Sub-messages"))
3236235b3d8SDonald Hunter        lines.append(parse_sub_messages(obj["sub-messages"]))
3246235b3d8SDonald Hunter
325f061c9f7SBreno Leitao    return "\n".join(lines)
326f061c9f7SBreno Leitao
327f061c9f7SBreno Leitao
328f061c9f7SBreno Leitao# Main functions
329f061c9f7SBreno Leitao# ==============
330f061c9f7SBreno Leitao
331f061c9f7SBreno Leitao
332f061c9f7SBreno Leitaodef parse_arguments() -> argparse.Namespace:
333f061c9f7SBreno Leitao    """Parse arguments from user"""
334f061c9f7SBreno Leitao    parser = argparse.ArgumentParser(description="Netlink RST generator")
335f061c9f7SBreno Leitao
336f061c9f7SBreno Leitao    parser.add_argument("-v", "--verbose", action="store_true")
337f061c9f7SBreno Leitao    parser.add_argument("-o", "--output", help="Output file name")
338f061c9f7SBreno Leitao
339f061c9f7SBreno Leitao    # Index and input are mutually exclusive
340f061c9f7SBreno Leitao    group = parser.add_mutually_exclusive_group()
341f061c9f7SBreno Leitao    group.add_argument(
342f061c9f7SBreno Leitao        "-x", "--index", action="store_true", help="Generate the index page"
343f061c9f7SBreno Leitao    )
344f061c9f7SBreno Leitao    group.add_argument("-i", "--input", help="YAML file name")
345f061c9f7SBreno Leitao
346f061c9f7SBreno Leitao    args = parser.parse_args()
347f061c9f7SBreno Leitao
348f061c9f7SBreno Leitao    if args.verbose:
349f061c9f7SBreno Leitao        logging.basicConfig(level=logging.DEBUG)
350f061c9f7SBreno Leitao
351f061c9f7SBreno Leitao    if args.input and not os.path.isfile(args.input):
352f061c9f7SBreno Leitao        logging.warning("%s is not a valid file.", args.input)
353f061c9f7SBreno Leitao        sys.exit(-1)
354f061c9f7SBreno Leitao
355f061c9f7SBreno Leitao    if not args.output:
356f061c9f7SBreno Leitao        logging.error("No output file specified.")
357f061c9f7SBreno Leitao        sys.exit(-1)
358f061c9f7SBreno Leitao
359f061c9f7SBreno Leitao    if os.path.isfile(args.output):
360f061c9f7SBreno Leitao        logging.debug("%s already exists. Overwriting it.", args.output)
361f061c9f7SBreno Leitao
362f061c9f7SBreno Leitao    return args
363f061c9f7SBreno Leitao
364f061c9f7SBreno Leitao
365f061c9f7SBreno Leitaodef parse_yaml_file(filename: str) -> str:
366f061c9f7SBreno Leitao    """Transform the YAML specified by filename into a rst-formmated string"""
367f061c9f7SBreno Leitao    with open(filename, "r", encoding="utf-8") as spec_file:
368f061c9f7SBreno Leitao        yaml_data = yaml.safe_load(spec_file)
369f061c9f7SBreno Leitao        content = parse_yaml(yaml_data)
370f061c9f7SBreno Leitao
371f061c9f7SBreno Leitao    return content
372f061c9f7SBreno Leitao
373f061c9f7SBreno Leitao
374f061c9f7SBreno Leitaodef write_to_rstfile(content: str, filename: str) -> None:
375f061c9f7SBreno Leitao    """Write the generated content into an RST file"""
376f061c9f7SBreno Leitao    logging.debug("Saving RST file to %s", filename)
377f061c9f7SBreno Leitao
378f061c9f7SBreno Leitao    with open(filename, "w", encoding="utf-8") as rst_file:
379f061c9f7SBreno Leitao        rst_file.write(content)
380f061c9f7SBreno Leitao
381f061c9f7SBreno Leitao
382f061c9f7SBreno Leitaodef generate_main_index_rst(output: str) -> None:
383f061c9f7SBreno Leitao    """Generate the `networking_spec/index` content and write to the file"""
384f061c9f7SBreno Leitao    lines = []
385f061c9f7SBreno Leitao
386f061c9f7SBreno Leitao    lines.append(rst_header())
387e8c780a5SJakub Kicinski    lines.append(rst_label("specs"))
388e8c780a5SJakub Kicinski    lines.append(rst_title("Netlink Family Specifications"))
389f061c9f7SBreno Leitao    lines.append(rst_toctree(1))
390f061c9f7SBreno Leitao
391f061c9f7SBreno Leitao    index_dir = os.path.dirname(output)
392f061c9f7SBreno Leitao    logging.debug("Looking for .rst files in %s", index_dir)
393e8c32339SDonald Hunter    for filename in sorted(os.listdir(index_dir)):
394f061c9f7SBreno Leitao        if not filename.endswith(".rst") or filename == "index.rst":
395f061c9f7SBreno Leitao            continue
396f061c9f7SBreno Leitao        lines.append(f"   {filename.replace('.rst', '')}\n")
397f061c9f7SBreno Leitao
398f061c9f7SBreno Leitao    logging.debug("Writing an index file at %s", output)
399f061c9f7SBreno Leitao    write_to_rstfile("".join(lines), output)
400f061c9f7SBreno Leitao
401f061c9f7SBreno Leitao
402f061c9f7SBreno Leitaodef main() -> None:
403f061c9f7SBreno Leitao    """Main function that reads the YAML files and generates the RST files"""
404f061c9f7SBreno Leitao
405f061c9f7SBreno Leitao    args = parse_arguments()
406f061c9f7SBreno Leitao
407f061c9f7SBreno Leitao    if args.input:
408f061c9f7SBreno Leitao        logging.debug("Parsing %s", args.input)
409f061c9f7SBreno Leitao        try:
410f061c9f7SBreno Leitao            content = parse_yaml_file(os.path.join(args.input))
411f061c9f7SBreno Leitao        except Exception as exception:
412f061c9f7SBreno Leitao            logging.warning("Failed to parse %s.", args.input)
413f061c9f7SBreno Leitao            logging.warning(exception)
414f061c9f7SBreno Leitao            sys.exit(-1)
415f061c9f7SBreno Leitao
416f061c9f7SBreno Leitao        write_to_rstfile(content, args.output)
417f061c9f7SBreno Leitao
418f061c9f7SBreno Leitao    if args.index:
419f061c9f7SBreno Leitao        # Generate the index RST file
420f061c9f7SBreno Leitao        generate_main_index_rst(args.output)
421f061c9f7SBreno Leitao
422f061c9f7SBreno Leitao
423f061c9f7SBreno Leitaoif __name__ == "__main__":
424f061c9f7SBreno Leitao    main()
425