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(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"\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: << 106 """Add a hyperlink to the document""" << 107 mappings = {'enum': 'definition', << 108 'fixed-header': 'definition', << 109 'nested-attributes': 'attribut << 110 'struct': 'definition'} << 111 if prefix in mappings: << 112 prefix = mappings[prefix] << 113 return f":ref:`{namespace}-{prefix}-{name} << 114 << 115 << 116 def rst_header() -> str: 105 def rst_header() -> str: 117 """The headers for all the auto generated 106 """The headers for all the auto generated RST files""" 118 lines = [] 107 lines = [] 119 108 120 lines.append(rst_paragraph(".. SPDX-Licens 109 lines.append(rst_paragraph(".. SPDX-License-Identifier: GPL-2.0")) 121 lines.append(rst_paragraph(".. NOTE: This 110 lines.append(rst_paragraph(".. NOTE: This document was auto-generated.\n\n")) 122 111 123 return "\n".join(lines) 112 return "\n".join(lines) 124 113 125 114 126 def rst_toctree(maxdepth: int = 2) -> str: 115 def rst_toctree(maxdepth: int = 2) -> str: 127 """Generate a toctree RST primitive""" 116 """Generate a toctree RST primitive""" 128 lines = [] 117 lines = [] 129 118 130 lines.append(".. toctree::") 119 lines.append(".. toctree::") 131 lines.append(f" :maxdepth: {maxdepth}\n\ 120 lines.append(f" :maxdepth: {maxdepth}\n\n") 132 121 133 return "\n".join(lines) 122 return "\n".join(lines) 134 123 135 124 136 def rst_label(title: str) -> str: 125 def rst_label(title: str) -> str: 137 """Return a formatted label""" 126 """Return a formatted label""" 138 return f".. _{title}:\n\n" 127 return f".. _{title}:\n\n" 139 128 140 129 141 # Parsers 130 # Parsers 142 # ======= 131 # ======= 143 132 144 133 145 def parse_mcast_group(mcast_group: List[Dict[s 134 def parse_mcast_group(mcast_group: List[Dict[str, Any]]) -> str: 146 """Parse 'multicast' group list and return 135 """Parse 'multicast' group list and return a formatted string""" 147 lines = [] 136 lines = [] 148 for group in mcast_group: 137 for group in mcast_group: 149 lines.append(rst_bullet(group["name"]) 138 lines.append(rst_bullet(group["name"])) 150 139 151 return "\n".join(lines) 140 return "\n".join(lines) 152 141 153 142 154 def parse_do(do_dict: Dict[str, Any], level: i 143 def parse_do(do_dict: Dict[str, Any], level: int = 0) -> str: 155 """Parse 'do' section and return a formatt 144 """Parse 'do' section and return a formatted string""" 156 lines = [] 145 lines = [] 157 for key in do_dict.keys(): 146 for key in do_dict.keys(): 158 lines.append(rst_paragraph(bold(key), 147 lines.append(rst_paragraph(bold(key), level + 1)) 159 if key in ['request', 'reply']: !! 148 lines.append(parse_do_attributes(do_dict[key], level + 1) + "\n") 160 lines.append(parse_do_attributes(d << 161 else: << 162 lines.append(headroom(level + 2) + << 163 149 164 return "\n".join(lines) 150 return "\n".join(lines) 165 151 166 152 167 def parse_do_attributes(attrs: Dict[str, Any], 153 def parse_do_attributes(attrs: Dict[str, Any], level: int = 0) -> str: 168 """Parse 'attributes' section""" 154 """Parse 'attributes' section""" 169 if "attributes" not in attrs: 155 if "attributes" not in attrs: 170 return "" 156 return "" 171 lines = [rst_fields("attributes", rst_list 157 lines = [rst_fields("attributes", rst_list_inline(attrs["attributes"]), level + 1)] 172 158 173 return "\n".join(lines) 159 return "\n".join(lines) 174 160 175 161 176 def parse_operations(operations: List[Dict[str !! 162 def parse_operations(operations: List[Dict[str, Any]]) -> str: 177 """Parse operations block""" 163 """Parse operations block""" 178 preprocessed = ["name", "doc", "title", "d !! 164 preprocessed = ["name", "doc", "title", "do", "dump"] 179 linkable = ["fixed-header", "attribute-set << 180 lines = [] 165 lines = [] 181 166 182 for operation in operations: 167 for operation in operations: 183 lines.append(rst_section(namespace, 'o !! 168 lines.append(rst_section(operation["name"])) 184 lines.append(rst_paragraph(operation[" !! 169 lines.append(rst_paragraph(sanitize(operation["doc"])) + "\n") 185 170 186 for key in operation.keys(): 171 for key in operation.keys(): 187 if key in preprocessed: 172 if key in preprocessed: 188 # Skip the special fields 173 # Skip the special fields 189 continue 174 continue 190 value = operation[key] !! 175 lines.append(rst_fields(key, operation[key], 0)) 191 if key in linkable: << 192 value = rst_ref(namespace, key << 193 lines.append(rst_fields(key, value << 194 if 'flags' in operation: << 195 lines.append(rst_fields('flags', r << 196 176 197 if "do" in operation: 177 if "do" in operation: 198 lines.append(rst_paragraph(":do:", 178 lines.append(rst_paragraph(":do:", 0)) 199 lines.append(parse_do(operation["d 179 lines.append(parse_do(operation["do"], 0)) 200 if "dump" in operation: 180 if "dump" in operation: 201 lines.append(rst_paragraph(":dump: 181 lines.append(rst_paragraph(":dump:", 0)) 202 lines.append(parse_do(operation["d 182 lines.append(parse_do(operation["dump"], 0)) 203 183 204 # New line after fields 184 # New line after fields 205 lines.append("\n") 185 lines.append("\n") 206 186 207 return "\n".join(lines) 187 return "\n".join(lines) 208 188 209 189 210 def parse_entries(entries: List[Dict[str, Any] 190 def parse_entries(entries: List[Dict[str, Any]], level: int) -> str: 211 """Parse a list of entries""" 191 """Parse a list of entries""" 212 ignored = ["pad"] 192 ignored = ["pad"] 213 lines = [] 193 lines = [] 214 for entry in entries: 194 for entry in entries: 215 if isinstance(entry, dict): 195 if isinstance(entry, dict): 216 # entries could be a list or a dic 196 # entries could be a list or a dictionary 217 field_name = entry.get("name", "") 197 field_name = entry.get("name", "") 218 if field_name in ignored: 198 if field_name in ignored: 219 continue 199 continue 220 type_ = entry.get("type") 200 type_ = entry.get("type") 221 if type_: 201 if type_: 222 field_name += f" ({inline(type 202 field_name += f" ({inline(type_)})" 223 lines.append( 203 lines.append( 224 rst_fields(field_name, sanitiz 204 rst_fields(field_name, sanitize(entry.get("doc", "")), level) 225 ) 205 ) 226 elif isinstance(entry, list): 206 elif isinstance(entry, list): 227 lines.append(rst_list_inline(entry 207 lines.append(rst_list_inline(entry, level)) 228 else: 208 else: 229 lines.append(rst_bullet(inline(san 209 lines.append(rst_bullet(inline(sanitize(entry)), level)) 230 210 231 lines.append("\n") 211 lines.append("\n") 232 return "\n".join(lines) 212 return "\n".join(lines) 233 213 234 214 235 def parse_definitions(defs: Dict[str, Any], na !! 215 def parse_definitions(defs: Dict[str, Any]) -> str: 236 """Parse definitions section""" 216 """Parse definitions section""" 237 preprocessed = ["name", "entries", "member 217 preprocessed = ["name", "entries", "members"] 238 ignored = ["render-max"] # This is not pr 218 ignored = ["render-max"] # This is not printed 239 lines = [] 219 lines = [] 240 220 241 for definition in defs: 221 for definition in defs: 242 lines.append(rst_section(namespace, 'd !! 222 lines.append(rst_section(definition["name"])) 243 for k in definition.keys(): 223 for k in definition.keys(): 244 if k in preprocessed + ignored: 224 if k in preprocessed + ignored: 245 continue 225 continue 246 lines.append(rst_fields(k, sanitiz 226 lines.append(rst_fields(k, sanitize(definition[k]), 0)) 247 227 248 # Field list needs to finish with a ne 228 # Field list needs to finish with a new line 249 lines.append("\n") 229 lines.append("\n") 250 if "entries" in definition: 230 if "entries" in definition: 251 lines.append(rst_paragraph(":entri 231 lines.append(rst_paragraph(":entries:", 0)) 252 lines.append(parse_entries(definit 232 lines.append(parse_entries(definition["entries"], 1)) 253 if "members" in definition: 233 if "members" in definition: 254 lines.append(rst_paragraph(":membe 234 lines.append(rst_paragraph(":members:", 0)) 255 lines.append(parse_entries(definit 235 lines.append(parse_entries(definition["members"], 1)) 256 236 257 return "\n".join(lines) 237 return "\n".join(lines) 258 238 259 239 260 def parse_attr_sets(entries: List[Dict[str, An !! 240 def parse_attr_sets(entries: List[Dict[str, Any]]) -> str: 261 """Parse attribute from attribute-set""" 241 """Parse attribute from attribute-set""" 262 preprocessed = ["name", "type"] 242 preprocessed = ["name", "type"] 263 linkable = ["enum", "nested-attributes", " << 264 ignored = ["checks"] 243 ignored = ["checks"] 265 lines = [] 244 lines = [] 266 245 267 for entry in entries: 246 for entry in entries: 268 lines.append(rst_section(namespace, 'a !! 247 lines.append(rst_section(entry["name"])) 269 for attr in entry["attributes"]: 248 for attr in entry["attributes"]: 270 type_ = attr.get("type") 249 type_ = attr.get("type") 271 attr_line = attr["name"] 250 attr_line = attr["name"] 272 if type_: 251 if type_: 273 # Add the attribute type in th 252 # Add the attribute type in the same line 274 attr_line += f" ({inline(type_ 253 attr_line += f" ({inline(type_)})" 275 254 276 lines.append(rst_subsubsection(att 255 lines.append(rst_subsubsection(attr_line)) 277 256 278 for k in attr.keys(): 257 for k in attr.keys(): 279 if k in preprocessed + ignored 258 if k in preprocessed + ignored: 280 continue 259 continue 281 if k in linkable: !! 260 lines.append(rst_fields(k, sanitize(attr[k]), 0)) 282 value = rst_ref(namespace, << 283 else: << 284 value = sanitize(attr[k]) << 285 lines.append(rst_fields(k, val << 286 lines.append("\n") 261 lines.append("\n") 287 262 288 return "\n".join(lines) 263 return "\n".join(lines) 289 264 290 265 291 def parse_sub_messages(entries: List[Dict[str, !! 266 def parse_sub_messages(entries: List[Dict[str, Any]]) -> str: 292 """Parse sub-message definitions""" 267 """Parse sub-message definitions""" 293 lines = [] 268 lines = [] 294 269 295 for entry in entries: 270 for entry in entries: 296 lines.append(rst_section(namespace, 's !! 271 lines.append(rst_section(entry["name"])) 297 for fmt in entry["formats"]: 272 for fmt in entry["formats"]: 298 value = fmt["value"] 273 value = fmt["value"] 299 274 300 lines.append(rst_bullet(bold(value 275 lines.append(rst_bullet(bold(value))) 301 for attr in ['fixed-header', 'attr 276 for attr in ['fixed-header', 'attribute-set']: 302 if attr in fmt: 277 if attr in fmt: 303 lines.append(rst_fields(at !! 278 lines.append(rst_fields(attr, fmt[attr], 1)) 304 rs << 305 1) << 306 lines.append("\n") 279 lines.append("\n") 307 280 308 return "\n".join(lines) 281 return "\n".join(lines) 309 282 310 283 311 def parse_yaml(obj: Dict[str, Any]) -> str: 284 def parse_yaml(obj: Dict[str, Any]) -> str: 312 """Format the whole YAML into a RST string 285 """Format the whole YAML into a RST string""" 313 lines = [] 286 lines = [] 314 287 315 # Main header 288 # Main header 316 289 317 lines.append(rst_header()) 290 lines.append(rst_header()) 318 291 319 family = obj['name'] !! 292 title = f"Family ``{obj['name']}`` netlink specification" 320 << 321 title = f"Family ``{family}`` netlink spec << 322 lines.append(rst_title(title)) 293 lines.append(rst_title(title)) 323 lines.append(rst_paragraph(".. contents:: !! 294 lines.append(rst_paragraph(".. contents::\n")) 324 295 325 if "doc" in obj: 296 if "doc" in obj: 326 lines.append(rst_subtitle("Summary")) 297 lines.append(rst_subtitle("Summary")) 327 lines.append(rst_paragraph(obj["doc"], 298 lines.append(rst_paragraph(obj["doc"], 0)) 328 299 329 # Operations 300 # Operations 330 if "operations" in obj: 301 if "operations" in obj: 331 lines.append(rst_subtitle("Operations" 302 lines.append(rst_subtitle("Operations")) 332 lines.append(parse_operations(obj["ope !! 303 lines.append(parse_operations(obj["operations"]["list"])) 333 304 334 # Multicast groups 305 # Multicast groups 335 if "mcast-groups" in obj: 306 if "mcast-groups" in obj: 336 lines.append(rst_subtitle("Multicast g 307 lines.append(rst_subtitle("Multicast groups")) 337 lines.append(parse_mcast_group(obj["mc 308 lines.append(parse_mcast_group(obj["mcast-groups"]["list"])) 338 309 339 # Definitions 310 # Definitions 340 if "definitions" in obj: 311 if "definitions" in obj: 341 lines.append(rst_subtitle("Definitions 312 lines.append(rst_subtitle("Definitions")) 342 lines.append(parse_definitions(obj["de !! 313 lines.append(parse_definitions(obj["definitions"])) 343 314 344 # Attributes set 315 # Attributes set 345 if "attribute-sets" in obj: 316 if "attribute-sets" in obj: 346 lines.append(rst_subtitle("Attribute s 317 lines.append(rst_subtitle("Attribute sets")) 347 lines.append(parse_attr_sets(obj["attr !! 318 lines.append(parse_attr_sets(obj["attribute-sets"])) 348 319 349 # Sub-messages 320 # Sub-messages 350 if "sub-messages" in obj: 321 if "sub-messages" in obj: 351 lines.append(rst_subtitle("Sub-message 322 lines.append(rst_subtitle("Sub-messages")) 352 lines.append(parse_sub_messages(obj["s !! 323 lines.append(parse_sub_messages(obj["sub-messages"])) 353 324 354 return "\n".join(lines) 325 return "\n".join(lines) 355 326 356 327 357 # Main functions 328 # Main functions 358 # ============== 329 # ============== 359 330 360 331 361 def parse_arguments() -> argparse.Namespace: 332 def parse_arguments() -> argparse.Namespace: 362 """Parse arguments from user""" 333 """Parse arguments from user""" 363 parser = argparse.ArgumentParser(descripti 334 parser = argparse.ArgumentParser(description="Netlink RST generator") 364 335 365 parser.add_argument("-v", "--verbose", act 336 parser.add_argument("-v", "--verbose", action="store_true") 366 parser.add_argument("-o", "--output", help 337 parser.add_argument("-o", "--output", help="Output file name") 367 338 368 # Index and input are mutually exclusive 339 # Index and input are mutually exclusive 369 group = parser.add_mutually_exclusive_grou 340 group = parser.add_mutually_exclusive_group() 370 group.add_argument( 341 group.add_argument( 371 "-x", "--index", action="store_true", 342 "-x", "--index", action="store_true", help="Generate the index page" 372 ) 343 ) 373 group.add_argument("-i", "--input", help=" 344 group.add_argument("-i", "--input", help="YAML file name") 374 345 375 args = parser.parse_args() 346 args = parser.parse_args() 376 347 377 if args.verbose: 348 if args.verbose: 378 logging.basicConfig(level=logging.DEBU 349 logging.basicConfig(level=logging.DEBUG) 379 350 380 if args.input and not os.path.isfile(args. 351 if args.input and not os.path.isfile(args.input): 381 logging.warning("%s is not a valid fil 352 logging.warning("%s is not a valid file.", args.input) 382 sys.exit(-1) 353 sys.exit(-1) 383 354 384 if not args.output: 355 if not args.output: 385 logging.error("No output file specifie 356 logging.error("No output file specified.") 386 sys.exit(-1) 357 sys.exit(-1) 387 358 388 if os.path.isfile(args.output): 359 if os.path.isfile(args.output): 389 logging.debug("%s already exists. Over 360 logging.debug("%s already exists. Overwriting it.", args.output) 390 361 391 return args 362 return args 392 363 393 364 394 def parse_yaml_file(filename: str) -> str: 365 def parse_yaml_file(filename: str) -> str: 395 """Transform the YAML specified by filenam 366 """Transform the YAML specified by filename into a rst-formmated string""" 396 with open(filename, "r", encoding="utf-8") 367 with open(filename, "r", encoding="utf-8") as spec_file: 397 yaml_data = yaml.safe_load(spec_file) 368 yaml_data = yaml.safe_load(spec_file) 398 content = parse_yaml(yaml_data) 369 content = parse_yaml(yaml_data) 399 370 400 return content 371 return content 401 372 402 373 403 def write_to_rstfile(content: str, filename: s 374 def write_to_rstfile(content: str, filename: str) -> None: 404 """Write the generated content into an RST 375 """Write the generated content into an RST file""" 405 logging.debug("Saving RST file to %s", fil 376 logging.debug("Saving RST file to %s", filename) 406 377 407 with open(filename, "w", encoding="utf-8") 378 with open(filename, "w", encoding="utf-8") as rst_file: 408 rst_file.write(content) 379 rst_file.write(content) 409 380 410 381 411 def generate_main_index_rst(output: str) -> No 382 def generate_main_index_rst(output: str) -> None: 412 """Generate the `networking_spec/index` co 383 """Generate the `networking_spec/index` content and write to the file""" 413 lines = [] 384 lines = [] 414 385 415 lines.append(rst_header()) 386 lines.append(rst_header()) 416 lines.append(rst_label("specs")) 387 lines.append(rst_label("specs")) 417 lines.append(rst_title("Netlink Family Spe 388 lines.append(rst_title("Netlink Family Specifications")) 418 lines.append(rst_toctree(1)) 389 lines.append(rst_toctree(1)) 419 390 420 index_dir = os.path.dirname(output) 391 index_dir = os.path.dirname(output) 421 logging.debug("Looking for .rst files in % 392 logging.debug("Looking for .rst files in %s", index_dir) 422 for filename in sorted(os.listdir(index_di 393 for filename in sorted(os.listdir(index_dir)): 423 if not filename.endswith(".rst") or fi 394 if not filename.endswith(".rst") or filename == "index.rst": 424 continue 395 continue 425 lines.append(f" {filename.replace('. 396 lines.append(f" {filename.replace('.rst', '')}\n") 426 397 427 logging.debug("Writing an index file at %s 398 logging.debug("Writing an index file at %s", output) 428 write_to_rstfile("".join(lines), output) 399 write_to_rstfile("".join(lines), output) 429 400 430 401 431 def main() -> None: 402 def main() -> None: 432 """Main function that reads the YAML files 403 """Main function that reads the YAML files and generates the RST files""" 433 404 434 args = parse_arguments() 405 args = parse_arguments() 435 406 436 if args.input: 407 if args.input: 437 logging.debug("Parsing %s", args.input 408 logging.debug("Parsing %s", args.input) 438 try: 409 try: 439 content = parse_yaml_file(os.path. 410 content = parse_yaml_file(os.path.join(args.input)) 440 except Exception as exception: 411 except Exception as exception: 441 logging.warning("Failed to parse % 412 logging.warning("Failed to parse %s.", args.input) 442 logging.warning(exception) 413 logging.warning(exception) 443 sys.exit(-1) 414 sys.exit(-1) 444 415 445 write_to_rstfile(content, args.output) 416 write_to_rstfile(content, args.output) 446 417 447 if args.index: 418 if args.index: 448 # Generate the index RST file 419 # Generate the index RST file 449 generate_main_index_rst(args.output) 420 generate_main_index_rst(args.output) 450 421 451 422 452 if __name__ == "__main__": 423 if __name__ == "__main__": 453 main() 424 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.