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