xref: /linux/tools/net/ynl/ynl-gen-rst.py (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
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
858c1b74a2SDonald Hunterdef rst_section(namespace: str, prefix: str, title: str) -> str:
86f061c9f7SBreno Leitao    """Add a section to the document"""
878c1b74a2SDonald Hunter    return f".. _{namespace}-{prefix}-{title}:\n\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
1058c1b74a2SDonald Hunterdef rst_ref(namespace: str, prefix: str, name: str) -> str:
1068c1b74a2SDonald Hunter    """Add a hyperlink to the document"""
1078c1b74a2SDonald Hunter    mappings = {'enum': 'definition',
1088c1b74a2SDonald Hunter                'fixed-header': 'definition',
1098c1b74a2SDonald Hunter                'nested-attributes': 'attribute-set',
1108c1b74a2SDonald Hunter                'struct': 'definition'}
1118c1b74a2SDonald Hunter    if prefix in mappings:
1128c1b74a2SDonald Hunter        prefix = mappings[prefix]
1138c1b74a2SDonald Hunter    return f":ref:`{namespace}-{prefix}-{name}`"
1148c1b74a2SDonald Hunter
1158c1b74a2SDonald Hunter
116f061c9f7SBreno Leitaodef rst_header() -> str:
117f061c9f7SBreno Leitao    """The headers for all the auto generated RST files"""
118f061c9f7SBreno Leitao    lines = []
119f061c9f7SBreno Leitao
120f061c9f7SBreno Leitao    lines.append(rst_paragraph(".. SPDX-License-Identifier: GPL-2.0"))
121f061c9f7SBreno Leitao    lines.append(rst_paragraph(".. NOTE: This document was auto-generated.\n\n"))
122f061c9f7SBreno Leitao
123f061c9f7SBreno Leitao    return "\n".join(lines)
124f061c9f7SBreno Leitao
125f061c9f7SBreno Leitao
126f061c9f7SBreno Leitaodef rst_toctree(maxdepth: int = 2) -> str:
127f061c9f7SBreno Leitao    """Generate a toctree RST primitive"""
128f061c9f7SBreno Leitao    lines = []
129f061c9f7SBreno Leitao
130f061c9f7SBreno Leitao    lines.append(".. toctree::")
131f061c9f7SBreno Leitao    lines.append(f"   :maxdepth: {maxdepth}\n\n")
132f061c9f7SBreno Leitao
133f061c9f7SBreno Leitao    return "\n".join(lines)
134f061c9f7SBreno Leitao
135f061c9f7SBreno Leitao
136e8c780a5SJakub Kicinskidef rst_label(title: str) -> str:
137e8c780a5SJakub Kicinski    """Return a formatted label"""
138e8c780a5SJakub Kicinski    return f".. _{title}:\n\n"
139e8c780a5SJakub Kicinski
140e8c780a5SJakub Kicinski
141f061c9f7SBreno Leitao# Parsers
142f061c9f7SBreno Leitao# =======
143f061c9f7SBreno Leitao
144f061c9f7SBreno Leitao
145f061c9f7SBreno Leitaodef parse_mcast_group(mcast_group: List[Dict[str, Any]]) -> str:
146f061c9f7SBreno Leitao    """Parse 'multicast' group list and return a formatted string"""
147f061c9f7SBreno Leitao    lines = []
148f061c9f7SBreno Leitao    for group in mcast_group:
149f061c9f7SBreno Leitao        lines.append(rst_bullet(group["name"]))
150f061c9f7SBreno Leitao
151f061c9f7SBreno Leitao    return "\n".join(lines)
152f061c9f7SBreno Leitao
153f061c9f7SBreno Leitao
154f061c9f7SBreno Leitaodef parse_do(do_dict: Dict[str, Any], level: int = 0) -> str:
155f061c9f7SBreno Leitao    """Parse 'do' section and return a formatted string"""
156f061c9f7SBreno Leitao    lines = []
157f061c9f7SBreno Leitao    for key in do_dict.keys():
158f061c9f7SBreno Leitao        lines.append(rst_paragraph(bold(key), level + 1))
159*9104feedSDonald Hunter        if key in ['request', 'reply']:
160f061c9f7SBreno Leitao            lines.append(parse_do_attributes(do_dict[key], level + 1) + "\n")
161*9104feedSDonald Hunter        else:
162*9104feedSDonald Hunter            lines.append(headroom(level + 2) + do_dict[key] + "\n")
163f061c9f7SBreno Leitao
164f061c9f7SBreno Leitao    return "\n".join(lines)
165f061c9f7SBreno Leitao
166f061c9f7SBreno Leitao
167f061c9f7SBreno Leitaodef parse_do_attributes(attrs: Dict[str, Any], level: int = 0) -> str:
168f061c9f7SBreno Leitao    """Parse 'attributes' section"""
169f061c9f7SBreno Leitao    if "attributes" not in attrs:
170f061c9f7SBreno Leitao        return ""
171f061c9f7SBreno Leitao    lines = [rst_fields("attributes", rst_list_inline(attrs["attributes"]), level + 1)]
172f061c9f7SBreno Leitao
173f061c9f7SBreno Leitao    return "\n".join(lines)
174f061c9f7SBreno Leitao
175f061c9f7SBreno Leitao
1768c1b74a2SDonald Hunterdef parse_operations(operations: List[Dict[str, Any]], namespace: str) -> str:
177f061c9f7SBreno Leitao    """Parse operations block"""
178cb7351acSDonald Hunter    preprocessed = ["name", "doc", "title", "do", "dump", "flags"]
1798c1b74a2SDonald Hunter    linkable = ["fixed-header", "attribute-set"]
180f061c9f7SBreno Leitao    lines = []
181f061c9f7SBreno Leitao
182f061c9f7SBreno Leitao    for operation in operations:
1838c1b74a2SDonald Hunter        lines.append(rst_section(namespace, 'operation', operation["name"]))
184ebf90041SDonald Hunter        lines.append(rst_paragraph(operation["doc"]) + "\n")
185f061c9f7SBreno Leitao
186f061c9f7SBreno Leitao        for key in operation.keys():
187f061c9f7SBreno Leitao            if key in preprocessed:
188f061c9f7SBreno Leitao                # Skip the special fields
189f061c9f7SBreno Leitao                continue
1908c1b74a2SDonald Hunter            value = operation[key]
1918c1b74a2SDonald Hunter            if key in linkable:
1928c1b74a2SDonald Hunter                value = rst_ref(namespace, key, value)
1938c1b74a2SDonald Hunter            lines.append(rst_fields(key, value, 0))
194cb7351acSDonald Hunter        if 'flags' in operation:
195cb7351acSDonald Hunter            lines.append(rst_fields('flags', rst_list_inline(operation['flags'])))
196f061c9f7SBreno Leitao
197f061c9f7SBreno Leitao        if "do" in operation:
198f061c9f7SBreno Leitao            lines.append(rst_paragraph(":do:", 0))
199f061c9f7SBreno Leitao            lines.append(parse_do(operation["do"], 0))
200f061c9f7SBreno Leitao        if "dump" in operation:
201f061c9f7SBreno Leitao            lines.append(rst_paragraph(":dump:", 0))
202f061c9f7SBreno Leitao            lines.append(parse_do(operation["dump"], 0))
203f061c9f7SBreno Leitao
204f061c9f7SBreno Leitao        # New line after fields
205f061c9f7SBreno Leitao        lines.append("\n")
206f061c9f7SBreno Leitao
207f061c9f7SBreno Leitao    return "\n".join(lines)
208f061c9f7SBreno Leitao
209f061c9f7SBreno Leitao
210f061c9f7SBreno Leitaodef parse_entries(entries: List[Dict[str, Any]], level: int) -> str:
211f061c9f7SBreno Leitao    """Parse a list of entries"""
212fe09ae5fSDonald Hunter    ignored = ["pad"]
213f061c9f7SBreno Leitao    lines = []
214f061c9f7SBreno Leitao    for entry in entries:
215f061c9f7SBreno Leitao        if isinstance(entry, dict):
216f061c9f7SBreno Leitao            # entries could be a list or a dictionary
217fe09ae5fSDonald Hunter            field_name = entry.get("name", "")
218fe09ae5fSDonald Hunter            if field_name in ignored:
219fe09ae5fSDonald Hunter                continue
220fe09ae5fSDonald Hunter            type_ = entry.get("type")
221fe09ae5fSDonald Hunter            if type_:
222fe09ae5fSDonald Hunter                field_name += f" ({inline(type_)})"
223f061c9f7SBreno Leitao            lines.append(
224fe09ae5fSDonald Hunter                rst_fields(field_name, sanitize(entry.get("doc", "")), level)
225f061c9f7SBreno Leitao            )
226f061c9f7SBreno Leitao        elif isinstance(entry, list):
227f061c9f7SBreno Leitao            lines.append(rst_list_inline(entry, level))
228f061c9f7SBreno Leitao        else:
229f061c9f7SBreno Leitao            lines.append(rst_bullet(inline(sanitize(entry)), level))
230f061c9f7SBreno Leitao
231f061c9f7SBreno Leitao    lines.append("\n")
232f061c9f7SBreno Leitao    return "\n".join(lines)
233f061c9f7SBreno Leitao
234f061c9f7SBreno Leitao
2358c1b74a2SDonald Hunterdef parse_definitions(defs: Dict[str, Any], namespace: str) -> str:
236f061c9f7SBreno Leitao    """Parse definitions section"""
237f061c9f7SBreno Leitao    preprocessed = ["name", "entries", "members"]
238f061c9f7SBreno Leitao    ignored = ["render-max"]  # This is not printed
239f061c9f7SBreno Leitao    lines = []
240f061c9f7SBreno Leitao
241f061c9f7SBreno Leitao    for definition in defs:
2428c1b74a2SDonald Hunter        lines.append(rst_section(namespace, 'definition', definition["name"]))
243f061c9f7SBreno Leitao        for k in definition.keys():
244f061c9f7SBreno Leitao            if k in preprocessed + ignored:
245f061c9f7SBreno Leitao                continue
246f061c9f7SBreno Leitao            lines.append(rst_fields(k, sanitize(definition[k]), 0))
247f061c9f7SBreno Leitao
248f061c9f7SBreno Leitao        # Field list needs to finish with a new line
249f061c9f7SBreno Leitao        lines.append("\n")
250f061c9f7SBreno Leitao        if "entries" in definition:
251f061c9f7SBreno Leitao            lines.append(rst_paragraph(":entries:", 0))
252f061c9f7SBreno Leitao            lines.append(parse_entries(definition["entries"], 1))
253f061c9f7SBreno Leitao        if "members" in definition:
254f061c9f7SBreno Leitao            lines.append(rst_paragraph(":members:", 0))
255f061c9f7SBreno Leitao            lines.append(parse_entries(definition["members"], 1))
256f061c9f7SBreno Leitao
257f061c9f7SBreno Leitao    return "\n".join(lines)
258f061c9f7SBreno Leitao
259f061c9f7SBreno Leitao
2608c1b74a2SDonald Hunterdef parse_attr_sets(entries: List[Dict[str, Any]], namespace: str) -> str:
261f061c9f7SBreno Leitao    """Parse attribute from attribute-set"""
262f061c9f7SBreno Leitao    preprocessed = ["name", "type"]
2638c1b74a2SDonald Hunter    linkable = ["enum", "nested-attributes", "struct", "sub-message"]
264f061c9f7SBreno Leitao    ignored = ["checks"]
265f061c9f7SBreno Leitao    lines = []
266f061c9f7SBreno Leitao
267f061c9f7SBreno Leitao    for entry in entries:
2688c1b74a2SDonald Hunter        lines.append(rst_section(namespace, 'attribute-set', entry["name"]))
269f061c9f7SBreno Leitao        for attr in entry["attributes"]:
270f061c9f7SBreno Leitao            type_ = attr.get("type")
271e9d7c592SDonald Hunter            attr_line = attr["name"]
272f061c9f7SBreno Leitao            if type_:
273f061c9f7SBreno Leitao                # Add the attribute type in the same line
274f061c9f7SBreno Leitao                attr_line += f" ({inline(type_)})"
275f061c9f7SBreno Leitao
276f061c9f7SBreno Leitao            lines.append(rst_subsubsection(attr_line))
277f061c9f7SBreno Leitao
278f061c9f7SBreno Leitao            for k in attr.keys():
279f061c9f7SBreno Leitao                if k in preprocessed + ignored:
280f061c9f7SBreno Leitao                    continue
2818c1b74a2SDonald Hunter                if k in linkable:
2828c1b74a2SDonald Hunter                    value = rst_ref(namespace, k, attr[k])
2838c1b74a2SDonald Hunter                else:
2848c1b74a2SDonald Hunter                    value = sanitize(attr[k])
2858c1b74a2SDonald Hunter                lines.append(rst_fields(k, value, 0))
286f061c9f7SBreno Leitao            lines.append("\n")
287f061c9f7SBreno Leitao
288f061c9f7SBreno Leitao    return "\n".join(lines)
289f061c9f7SBreno Leitao
290f061c9f7SBreno Leitao
2918c1b74a2SDonald Hunterdef parse_sub_messages(entries: List[Dict[str, Any]], namespace: str) -> str:
2926235b3d8SDonald Hunter    """Parse sub-message definitions"""
2936235b3d8SDonald Hunter    lines = []
2946235b3d8SDonald Hunter
2956235b3d8SDonald Hunter    for entry in entries:
2968c1b74a2SDonald Hunter        lines.append(rst_section(namespace, 'sub-message', entry["name"]))
2976235b3d8SDonald Hunter        for fmt in entry["formats"]:
2986235b3d8SDonald Hunter            value = fmt["value"]
2996235b3d8SDonald Hunter
3006235b3d8SDonald Hunter            lines.append(rst_bullet(bold(value)))
3016235b3d8SDonald Hunter            for attr in ['fixed-header', 'attribute-set']:
3026235b3d8SDonald Hunter                if attr in fmt:
3038c1b74a2SDonald Hunter                    lines.append(rst_fields(attr,
3048c1b74a2SDonald Hunter                                            rst_ref(namespace, attr, fmt[attr]),
3058c1b74a2SDonald Hunter                                            1))
3066235b3d8SDonald Hunter            lines.append("\n")
3076235b3d8SDonald Hunter
3086235b3d8SDonald Hunter    return "\n".join(lines)
3096235b3d8SDonald Hunter
3106235b3d8SDonald Hunter
311f061c9f7SBreno Leitaodef parse_yaml(obj: Dict[str, Any]) -> str:
312f061c9f7SBreno Leitao    """Format the whole YAML into a RST string"""
313f061c9f7SBreno Leitao    lines = []
314f061c9f7SBreno Leitao
315f061c9f7SBreno Leitao    # Main header
316f061c9f7SBreno Leitao
317f061c9f7SBreno Leitao    lines.append(rst_header())
318f061c9f7SBreno Leitao
3198c1b74a2SDonald Hunter    family = obj['name']
3208c1b74a2SDonald Hunter
3218c1b74a2SDonald Hunter    title = f"Family ``{family}`` netlink specification"
322f061c9f7SBreno Leitao    lines.append(rst_title(title))
3234cc1730aSDonald Hunter    lines.append(rst_paragraph(".. contents:: :depth: 3\n"))
324f061c9f7SBreno Leitao
325f061c9f7SBreno Leitao    if "doc" in obj:
326f061c9f7SBreno Leitao        lines.append(rst_subtitle("Summary"))
327f061c9f7SBreno Leitao        lines.append(rst_paragraph(obj["doc"], 0))
328f061c9f7SBreno Leitao
329f061c9f7SBreno Leitao    # Operations
330f061c9f7SBreno Leitao    if "operations" in obj:
331f061c9f7SBreno Leitao        lines.append(rst_subtitle("Operations"))
3328c1b74a2SDonald Hunter        lines.append(parse_operations(obj["operations"]["list"], family))
333f061c9f7SBreno Leitao
334f061c9f7SBreno Leitao    # Multicast groups
335f061c9f7SBreno Leitao    if "mcast-groups" in obj:
336f061c9f7SBreno Leitao        lines.append(rst_subtitle("Multicast groups"))
337f061c9f7SBreno Leitao        lines.append(parse_mcast_group(obj["mcast-groups"]["list"]))
338f061c9f7SBreno Leitao
339f061c9f7SBreno Leitao    # Definitions
340f061c9f7SBreno Leitao    if "definitions" in obj:
341f061c9f7SBreno Leitao        lines.append(rst_subtitle("Definitions"))
3428c1b74a2SDonald Hunter        lines.append(parse_definitions(obj["definitions"], family))
343f061c9f7SBreno Leitao
344f061c9f7SBreno Leitao    # Attributes set
345f061c9f7SBreno Leitao    if "attribute-sets" in obj:
346f061c9f7SBreno Leitao        lines.append(rst_subtitle("Attribute sets"))
3478c1b74a2SDonald Hunter        lines.append(parse_attr_sets(obj["attribute-sets"], family))
348f061c9f7SBreno Leitao
3496235b3d8SDonald Hunter    # Sub-messages
3506235b3d8SDonald Hunter    if "sub-messages" in obj:
3516235b3d8SDonald Hunter        lines.append(rst_subtitle("Sub-messages"))
3528c1b74a2SDonald Hunter        lines.append(parse_sub_messages(obj["sub-messages"], family))
3536235b3d8SDonald Hunter
354f061c9f7SBreno Leitao    return "\n".join(lines)
355f061c9f7SBreno Leitao
356f061c9f7SBreno Leitao
357f061c9f7SBreno Leitao# Main functions
358f061c9f7SBreno Leitao# ==============
359f061c9f7SBreno Leitao
360f061c9f7SBreno Leitao
361f061c9f7SBreno Leitaodef parse_arguments() -> argparse.Namespace:
362f061c9f7SBreno Leitao    """Parse arguments from user"""
363f061c9f7SBreno Leitao    parser = argparse.ArgumentParser(description="Netlink RST generator")
364f061c9f7SBreno Leitao
365f061c9f7SBreno Leitao    parser.add_argument("-v", "--verbose", action="store_true")
366f061c9f7SBreno Leitao    parser.add_argument("-o", "--output", help="Output file name")
367f061c9f7SBreno Leitao
368f061c9f7SBreno Leitao    # Index and input are mutually exclusive
369f061c9f7SBreno Leitao    group = parser.add_mutually_exclusive_group()
370f061c9f7SBreno Leitao    group.add_argument(
371f061c9f7SBreno Leitao        "-x", "--index", action="store_true", help="Generate the index page"
372f061c9f7SBreno Leitao    )
373f061c9f7SBreno Leitao    group.add_argument("-i", "--input", help="YAML file name")
374f061c9f7SBreno Leitao
375f061c9f7SBreno Leitao    args = parser.parse_args()
376f061c9f7SBreno Leitao
377f061c9f7SBreno Leitao    if args.verbose:
378f061c9f7SBreno Leitao        logging.basicConfig(level=logging.DEBUG)
379f061c9f7SBreno Leitao
380f061c9f7SBreno Leitao    if args.input and not os.path.isfile(args.input):
381f061c9f7SBreno Leitao        logging.warning("%s is not a valid file.", args.input)
382f061c9f7SBreno Leitao        sys.exit(-1)
383f061c9f7SBreno Leitao
384f061c9f7SBreno Leitao    if not args.output:
385f061c9f7SBreno Leitao        logging.error("No output file specified.")
386f061c9f7SBreno Leitao        sys.exit(-1)
387f061c9f7SBreno Leitao
388f061c9f7SBreno Leitao    if os.path.isfile(args.output):
389f061c9f7SBreno Leitao        logging.debug("%s already exists. Overwriting it.", args.output)
390f061c9f7SBreno Leitao
391f061c9f7SBreno Leitao    return args
392f061c9f7SBreno Leitao
393f061c9f7SBreno Leitao
394f061c9f7SBreno Leitaodef parse_yaml_file(filename: str) -> str:
395f061c9f7SBreno Leitao    """Transform the YAML specified by filename into a rst-formmated string"""
396f061c9f7SBreno Leitao    with open(filename, "r", encoding="utf-8") as spec_file:
397f061c9f7SBreno Leitao        yaml_data = yaml.safe_load(spec_file)
398f061c9f7SBreno Leitao        content = parse_yaml(yaml_data)
399f061c9f7SBreno Leitao
400f061c9f7SBreno Leitao    return content
401f061c9f7SBreno Leitao
402f061c9f7SBreno Leitao
403f061c9f7SBreno Leitaodef write_to_rstfile(content: str, filename: str) -> None:
404f061c9f7SBreno Leitao    """Write the generated content into an RST file"""
405f061c9f7SBreno Leitao    logging.debug("Saving RST file to %s", filename)
406f061c9f7SBreno Leitao
407f061c9f7SBreno Leitao    with open(filename, "w", encoding="utf-8") as rst_file:
408f061c9f7SBreno Leitao        rst_file.write(content)
409f061c9f7SBreno Leitao
410f061c9f7SBreno Leitao
411f061c9f7SBreno Leitaodef generate_main_index_rst(output: str) -> None:
412f061c9f7SBreno Leitao    """Generate the `networking_spec/index` content and write to the file"""
413f061c9f7SBreno Leitao    lines = []
414f061c9f7SBreno Leitao
415f061c9f7SBreno Leitao    lines.append(rst_header())
416e8c780a5SJakub Kicinski    lines.append(rst_label("specs"))
417e8c780a5SJakub Kicinski    lines.append(rst_title("Netlink Family Specifications"))
418f061c9f7SBreno Leitao    lines.append(rst_toctree(1))
419f061c9f7SBreno Leitao
420f061c9f7SBreno Leitao    index_dir = os.path.dirname(output)
421f061c9f7SBreno Leitao    logging.debug("Looking for .rst files in %s", index_dir)
422e8c32339SDonald Hunter    for filename in sorted(os.listdir(index_dir)):
423f061c9f7SBreno Leitao        if not filename.endswith(".rst") or filename == "index.rst":
424f061c9f7SBreno Leitao            continue
425f061c9f7SBreno Leitao        lines.append(f"   {filename.replace('.rst', '')}\n")
426f061c9f7SBreno Leitao
427f061c9f7SBreno Leitao    logging.debug("Writing an index file at %s", output)
428f061c9f7SBreno Leitao    write_to_rstfile("".join(lines), output)
429f061c9f7SBreno Leitao
430f061c9f7SBreno Leitao
431f061c9f7SBreno Leitaodef main() -> None:
432f061c9f7SBreno Leitao    """Main function that reads the YAML files and generates the RST files"""
433f061c9f7SBreno Leitao
434f061c9f7SBreno Leitao    args = parse_arguments()
435f061c9f7SBreno Leitao
436f061c9f7SBreno Leitao    if args.input:
437f061c9f7SBreno Leitao        logging.debug("Parsing %s", args.input)
438f061c9f7SBreno Leitao        try:
439f061c9f7SBreno Leitao            content = parse_yaml_file(os.path.join(args.input))
440f061c9f7SBreno Leitao        except Exception as exception:
441f061c9f7SBreno Leitao            logging.warning("Failed to parse %s.", args.input)
442f061c9f7SBreno Leitao            logging.warning(exception)
443f061c9f7SBreno Leitao            sys.exit(-1)
444f061c9f7SBreno Leitao
445f061c9f7SBreno Leitao        write_to_rstfile(content, args.output)
446f061c9f7SBreno Leitao
447f061c9f7SBreno Leitao    if args.index:
448f061c9f7SBreno Leitao        # Generate the index RST file
449f061c9f7SBreno Leitao        generate_main_index_rst(args.output)
450f061c9f7SBreno Leitao
451f061c9f7SBreno Leitao
452f061c9f7SBreno Leitaoif __name__ == "__main__":
453f061c9f7SBreno Leitao    main()
454