1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. 4# 5# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917 6 7""" 8Implement output filters to print kernel-doc documentation. 9 10The implementation uses a virtual base class (OutputFormat) which 11contains a dispatches to virtual methods, and some code to filter 12out output messages. 13 14The actual implementation is done on one separate class per each type 15of output. Currently, there are output classes for ReST and man/troff. 16""" 17 18import os 19import re 20from datetime import datetime 21 22from kdoc_parser import KernelDoc, type_param 23from kdoc_re import KernRe 24 25 26function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False) 27 28# match expressions used to find embedded type information 29type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False) 30type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False) 31type_func = KernRe(r"(\w+)\(\)", cache=False) 32type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) 33 34# Special RST handling for func ptr params 35type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False) 36 37# Special RST handling for structs with func ptr params 38type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False) 39 40type_env = KernRe(r"(\$\w+)", cache=False) 41type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False) 42type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False) 43type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False) 44type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False) 45type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False) 46type_fallback = KernRe(r"\&([_\w]+)", cache=False) 47type_member_func = type_member + KernRe(r"\(\)", cache=False) 48 49 50class OutputFormat: 51 """ 52 Base class for OutputFormat. If used as-is, it means that only 53 warnings will be displayed. 54 """ 55 56 # output mode. 57 OUTPUT_ALL = 0 # output all symbols and doc sections 58 OUTPUT_INCLUDE = 1 # output only specified symbols 59 OUTPUT_EXPORTED = 2 # output exported symbols 60 OUTPUT_INTERNAL = 3 # output non-exported symbols 61 62 # Virtual member to be overriden at the inherited classes 63 highlights = [] 64 65 def __init__(self): 66 """Declare internal vars and set mode to OUTPUT_ALL""" 67 68 self.out_mode = self.OUTPUT_ALL 69 self.enable_lineno = None 70 self.nosymbol = {} 71 self.symbol = None 72 self.function_table = None 73 self.config = None 74 self.no_doc_sections = False 75 76 self.data = "" 77 78 def set_config(self, config): 79 """ 80 Setup global config variables used by both parser and output. 81 """ 82 83 self.config = config 84 85 def set_filter(self, export, internal, symbol, nosymbol, function_table, 86 enable_lineno, no_doc_sections): 87 """ 88 Initialize filter variables according with the requested mode. 89 90 Only one choice is valid between export, internal and symbol. 91 92 The nosymbol filter can be used on all modes. 93 """ 94 95 self.enable_lineno = enable_lineno 96 self.no_doc_sections = no_doc_sections 97 self.function_table = function_table 98 99 if symbol: 100 self.out_mode = self.OUTPUT_INCLUDE 101 elif export: 102 self.out_mode = self.OUTPUT_EXPORTED 103 elif internal: 104 self.out_mode = self.OUTPUT_INTERNAL 105 else: 106 self.out_mode = self.OUTPUT_ALL 107 108 if nosymbol: 109 self.nosymbol = set(nosymbol) 110 111 112 def highlight_block(self, block): 113 """ 114 Apply the RST highlights to a sub-block of text. 115 """ 116 117 for r, sub in self.highlights: 118 block = r.sub(sub, block) 119 120 return block 121 122 def out_warnings(self, args): 123 """ 124 Output warnings for identifiers that will be displayed. 125 """ 126 127 warnings = args.get('warnings', []) 128 129 for log_msg in warnings: 130 self.config.warning(log_msg) 131 132 def check_doc(self, name, args): 133 """Check if DOC should be output""" 134 135 if self.no_doc_sections: 136 return False 137 138 if name in self.nosymbol: 139 return False 140 141 if self.out_mode == self.OUTPUT_ALL: 142 self.out_warnings(args) 143 return True 144 145 if self.out_mode == self.OUTPUT_INCLUDE: 146 if name in self.function_table: 147 self.out_warnings(args) 148 return True 149 150 return False 151 152 def check_declaration(self, dtype, name, args): 153 """ 154 Checks if a declaration should be output or not based on the 155 filtering criteria. 156 """ 157 158 if name in self.nosymbol: 159 return False 160 161 if self.out_mode == self.OUTPUT_ALL: 162 self.out_warnings(args) 163 return True 164 165 if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]: 166 if name in self.function_table: 167 return True 168 169 if self.out_mode == self.OUTPUT_INTERNAL: 170 if dtype != "function": 171 self.out_warnings(args) 172 return True 173 174 if name not in self.function_table: 175 self.out_warnings(args) 176 return True 177 178 return False 179 180 def msg(self, fname, name, args): 181 """ 182 Handles a single entry from kernel-doc parser 183 """ 184 185 self.data = "" 186 187 dtype = args.get('type', "") 188 189 if dtype == "doc": 190 self.out_doc(fname, name, args) 191 return self.data 192 193 if not self.check_declaration(dtype, name, args): 194 return self.data 195 196 if dtype == "function": 197 self.out_function(fname, name, args) 198 return self.data 199 200 if dtype == "enum": 201 self.out_enum(fname, name, args) 202 return self.data 203 204 if dtype == "typedef": 205 self.out_typedef(fname, name, args) 206 return self.data 207 208 if dtype in ["struct", "union"]: 209 self.out_struct(fname, name, args) 210 return self.data 211 212 # Warn if some type requires an output logic 213 self.config.log.warning("doesn't now how to output '%s' block", 214 dtype) 215 216 return None 217 218 # Virtual methods to be overridden by inherited classes 219 # At the base class, those do nothing. 220 def out_doc(self, fname, name, args): 221 """Outputs a DOC block""" 222 223 def out_function(self, fname, name, args): 224 """Outputs a function""" 225 226 def out_enum(self, fname, name, args): 227 """Outputs an enum""" 228 229 def out_typedef(self, fname, name, args): 230 """Outputs a typedef""" 231 232 def out_struct(self, fname, name, args): 233 """Outputs a struct""" 234 235 236class RestFormat(OutputFormat): 237 """Consts and functions used by ReST output""" 238 239 highlights = [ 240 (type_constant, r"``\1``"), 241 (type_constant2, r"``\1``"), 242 243 # Note: need to escape () to avoid func matching later 244 (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"), 245 (type_member, r":c:type:`\1\2\3 <\1>`"), 246 (type_fp_param, r"**\1\\(\\)**"), 247 (type_fp_param2, r"**\1\\(\\)**"), 248 (type_func, r"\1()"), 249 (type_enum, r":c:type:`\1 <\2>`"), 250 (type_struct, r":c:type:`\1 <\2>`"), 251 (type_typedef, r":c:type:`\1 <\2>`"), 252 (type_union, r":c:type:`\1 <\2>`"), 253 254 # in rst this can refer to any type 255 (type_fallback, r":c:type:`\1`"), 256 (type_param_ref, r"**\1\2**") 257 ] 258 blankline = "\n" 259 260 sphinx_literal = KernRe(r'^[^.].*::$', cache=False) 261 sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False) 262 263 def __init__(self): 264 """ 265 Creates class variables. 266 267 Not really mandatory, but it is a good coding style and makes 268 pylint happy. 269 """ 270 271 super().__init__() 272 self.lineprefix = "" 273 274 def print_lineno(self, ln): 275 """Outputs a line number""" 276 277 if self.enable_lineno and ln is not None: 278 ln += 1 279 self.data += f".. LINENO {ln}\n" 280 281 def output_highlight(self, args): 282 """ 283 Outputs a C symbol that may require being converted to ReST using 284 the self.highlights variable 285 """ 286 287 input_text = args 288 output = "" 289 in_literal = False 290 litprefix = "" 291 block = "" 292 293 for line in input_text.strip("\n").split("\n"): 294 295 # If we're in a literal block, see if we should drop out of it. 296 # Otherwise, pass the line straight through unmunged. 297 if in_literal: 298 if line.strip(): # If the line is not blank 299 # If this is the first non-blank line in a literal block, 300 # figure out the proper indent. 301 if not litprefix: 302 r = KernRe(r'^(\s*)') 303 if r.match(line): 304 litprefix = '^' + r.group(1) 305 else: 306 litprefix = "" 307 308 output += line + "\n" 309 elif not KernRe(litprefix).match(line): 310 in_literal = False 311 else: 312 output += line + "\n" 313 else: 314 output += line + "\n" 315 316 # Not in a literal block (or just dropped out) 317 if not in_literal: 318 block += line + "\n" 319 if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line): 320 in_literal = True 321 litprefix = "" 322 output += self.highlight_block(block) 323 block = "" 324 325 # Handle any remaining block 326 if block: 327 output += self.highlight_block(block) 328 329 # Print the output with the line prefix 330 for line in output.strip("\n").split("\n"): 331 self.data += self.lineprefix + line + "\n" 332 333 def out_section(self, args, out_docblock=False): 334 """ 335 Outputs a block section. 336 337 This could use some work; it's used to output the DOC: sections, and 338 starts by putting out the name of the doc section itself, but that 339 tends to duplicate a header already in the template file. 340 """ 341 for section, text in args.sections.items(): 342 # Skip sections that are in the nosymbol_table 343 if section in self.nosymbol: 344 continue 345 346 if out_docblock: 347 if not self.out_mode == self.OUTPUT_INCLUDE: 348 self.data += f".. _{section}:\n\n" 349 self.data += f'{self.lineprefix}**{section}**\n\n' 350 else: 351 self.data += f'{self.lineprefix}**{section}**\n\n' 352 353 self.print_lineno(args.section_start_lines.get(section, 0)) 354 self.output_highlight(text) 355 self.data += "\n" 356 self.data += "\n" 357 358 def out_doc(self, fname, name, args): 359 if not self.check_doc(name, args): 360 return 361 self.out_section(args, out_docblock=True) 362 363 def out_function(self, fname, name, args): 364 365 oldprefix = self.lineprefix 366 signature = "" 367 368 func_macro = args.get('func_macro', False) 369 if func_macro: 370 signature = args['function'] 371 else: 372 if args.get('functiontype'): 373 signature = args['functiontype'] + " " 374 signature += args['function'] + " (" 375 376 ln = args.get('declaration_start_line', 0) 377 count = 0 378 for parameter in args.parameterlist: 379 if count != 0: 380 signature += ", " 381 count += 1 382 dtype = args.parametertypes.get(parameter, "") 383 384 if function_pointer.search(dtype): 385 signature += function_pointer.group(1) + parameter + function_pointer.group(3) 386 else: 387 signature += dtype 388 389 if not func_macro: 390 signature += ")" 391 392 self.print_lineno(ln) 393 if args.get('typedef') or not args.get('functiontype'): 394 self.data += f".. c:macro:: {args['function']}\n\n" 395 396 if args.get('typedef'): 397 self.data += " **Typedef**: " 398 self.lineprefix = "" 399 self.output_highlight(args.get('purpose', "")) 400 self.data += "\n\n**Syntax**\n\n" 401 self.data += f" ``{signature}``\n\n" 402 else: 403 self.data += f"``{signature}``\n\n" 404 else: 405 self.data += f".. c:function:: {signature}\n\n" 406 407 if not args.get('typedef'): 408 self.print_lineno(ln) 409 self.lineprefix = " " 410 self.output_highlight(args.get('purpose', "")) 411 self.data += "\n" 412 413 # Put descriptive text into a container (HTML <div>) to help set 414 # function prototypes apart 415 self.lineprefix = " " 416 417 if args.parameterlist: 418 self.data += ".. container:: kernelindent\n\n" 419 self.data += f"{self.lineprefix}**Parameters**\n\n" 420 421 for parameter in args.parameterlist: 422 parameter_name = KernRe(r'\[.*').sub('', parameter) 423 dtype = args.parametertypes.get(parameter, "") 424 425 if dtype: 426 self.data += f"{self.lineprefix}``{dtype}``\n" 427 else: 428 self.data += f"{self.lineprefix}``{parameter}``\n" 429 430 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 431 432 self.lineprefix = " " 433 if parameter_name in args.parameterdescs and \ 434 args.parameterdescs[parameter_name] != KernelDoc.undescribed: 435 436 self.output_highlight(args.parameterdescs[parameter_name]) 437 self.data += "\n" 438 else: 439 self.data += f"{self.lineprefix}*undescribed*\n\n" 440 self.lineprefix = " " 441 442 self.out_section(args) 443 self.lineprefix = oldprefix 444 445 def out_enum(self, fname, name, args): 446 447 oldprefix = self.lineprefix 448 name = args.get('enum', '') 449 ln = args.get('declaration_start_line', 0) 450 451 self.data += f"\n\n.. c:enum:: {name}\n\n" 452 453 self.print_lineno(ln) 454 self.lineprefix = " " 455 self.output_highlight(args.get('purpose', '')) 456 self.data += "\n" 457 458 self.data += ".. container:: kernelindent\n\n" 459 outer = self.lineprefix + " " 460 self.lineprefix = outer + " " 461 self.data += f"{outer}**Constants**\n\n" 462 463 for parameter in args.parameterlist: 464 self.data += f"{outer}``{parameter}``\n" 465 466 if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed: 467 self.output_highlight(args.parameterdescs[parameter]) 468 else: 469 self.data += f"{self.lineprefix}*undescribed*\n\n" 470 self.data += "\n" 471 472 self.lineprefix = oldprefix 473 self.out_section(args) 474 475 def out_typedef(self, fname, name, args): 476 477 oldprefix = self.lineprefix 478 name = args.get('typedef', '') 479 ln = args.get('declaration_start_line', 0) 480 481 self.data += f"\n\n.. c:type:: {name}\n\n" 482 483 self.print_lineno(ln) 484 self.lineprefix = " " 485 486 self.output_highlight(args.get('purpose', '')) 487 488 self.data += "\n" 489 490 self.lineprefix = oldprefix 491 self.out_section(args) 492 493 def out_struct(self, fname, name, args): 494 495 name = args.get('struct', "") 496 purpose = args.get('purpose', "") 497 declaration = args.get('definition', "") 498 dtype = args.get('type', "struct") 499 ln = args.get('declaration_start_line', 0) 500 501 self.data += f"\n\n.. c:{dtype}:: {name}\n\n" 502 503 self.print_lineno(ln) 504 505 oldprefix = self.lineprefix 506 self.lineprefix += " " 507 508 self.output_highlight(purpose) 509 self.data += "\n" 510 511 self.data += ".. container:: kernelindent\n\n" 512 self.data += f"{self.lineprefix}**Definition**::\n\n" 513 514 self.lineprefix = self.lineprefix + " " 515 516 declaration = declaration.replace("\t", self.lineprefix) 517 518 self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" 519 self.data += f"{declaration}{self.lineprefix}" + "};\n\n" 520 521 self.lineprefix = " " 522 self.data += f"{self.lineprefix}**Members**\n\n" 523 for parameter in args.parameterlist: 524 if not parameter or parameter.startswith("#"): 525 continue 526 527 parameter_name = parameter.split("[", maxsplit=1)[0] 528 529 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 530 continue 531 532 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 533 534 self.data += f"{self.lineprefix}``{parameter}``\n" 535 536 self.lineprefix = " " 537 self.output_highlight(args.parameterdescs[parameter_name]) 538 self.lineprefix = " " 539 540 self.data += "\n" 541 542 self.data += "\n" 543 544 self.lineprefix = oldprefix 545 self.out_section(args) 546 547 548class ManFormat(OutputFormat): 549 """Consts and functions used by man pages output""" 550 551 highlights = ( 552 (type_constant, r"\1"), 553 (type_constant2, r"\1"), 554 (type_func, r"\\fB\1\\fP"), 555 (type_enum, r"\\fI\1\\fP"), 556 (type_struct, r"\\fI\1\\fP"), 557 (type_typedef, r"\\fI\1\\fP"), 558 (type_union, r"\\fI\1\\fP"), 559 (type_param, r"\\fI\1\\fP"), 560 (type_param_ref, r"\\fI\1\2\\fP"), 561 (type_member, r"\\fI\1\2\3\\fP"), 562 (type_fallback, r"\\fI\1\\fP") 563 ) 564 blankline = "" 565 566 date_formats = [ 567 "%a %b %d %H:%M:%S %Z %Y", 568 "%a %b %d %H:%M:%S %Y", 569 "%Y-%m-%d", 570 "%b %d %Y", 571 "%B %d %Y", 572 "%m %d %Y", 573 ] 574 575 def __init__(self, modulename): 576 """ 577 Creates class variables. 578 579 Not really mandatory, but it is a good coding style and makes 580 pylint happy. 581 """ 582 583 super().__init__() 584 self.modulename = modulename 585 586 dt = None 587 tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") 588 if tstamp: 589 for fmt in self.date_formats: 590 try: 591 dt = datetime.strptime(tstamp, fmt) 592 break 593 except ValueError: 594 pass 595 596 if not dt: 597 dt = datetime.now() 598 599 self.man_date = dt.strftime("%B %Y") 600 601 def output_highlight(self, block): 602 """ 603 Outputs a C symbol that may require being highlighted with 604 self.highlights variable using troff syntax 605 """ 606 607 contents = self.highlight_block(block) 608 609 if isinstance(contents, list): 610 contents = "\n".join(contents) 611 612 for line in contents.strip("\n").split("\n"): 613 line = KernRe(r"^\s*").sub("", line) 614 if not line: 615 continue 616 617 if line[0] == ".": 618 self.data += "\\&" + line + "\n" 619 else: 620 self.data += line + "\n" 621 622 def out_doc(self, fname, name, args): 623 if not self.check_doc(name, args): 624 return 625 626 self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n" 627 628 for section, text in args.sections.items(): 629 self.data += f'.SH "{section}"' + "\n" 630 self.output_highlight(text) 631 632 def out_function(self, fname, name, args): 633 """output function in man""" 634 635 self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" 636 637 self.data += ".SH NAME\n" 638 self.data += f"{args['function']} \\- {args['purpose']}\n" 639 640 self.data += ".SH SYNOPSIS\n" 641 if args.get('functiontype', ''): 642 self.data += f'.B "{args["functiontype"]}" {args["function"]}' + "\n" 643 else: 644 self.data += f'.B "{args["function"]}' + "\n" 645 646 count = 0 647 parenth = "(" 648 post = "," 649 650 for parameter in args.parameterlist: 651 if count == len(args.parameterlist) - 1: 652 post = ");" 653 654 dtype = args.parametertypes.get(parameter, "") 655 if function_pointer.match(dtype): 656 # Pointer-to-function 657 self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" 658 else: 659 dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype) 660 661 self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" 662 count += 1 663 parenth = "" 664 665 if args.parameterlist: 666 self.data += ".SH ARGUMENTS\n" 667 668 for parameter in args.parameterlist: 669 parameter_name = re.sub(r'\[.*', '', parameter) 670 671 self.data += f'.IP "{parameter}" 12' + "\n" 672 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 673 674 for section, text in args.sections.items(): 675 self.data += f'.SH "{section.upper()}"' + "\n" 676 self.output_highlight(text) 677 678 def out_enum(self, fname, name, args): 679 680 name = args.get('enum', '') 681 682 self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" 683 684 self.data += ".SH NAME\n" 685 self.data += f"enum {args['enum']} \\- {args['purpose']}\n" 686 687 self.data += ".SH SYNOPSIS\n" 688 self.data += f"enum {args['enum']}" + " {\n" 689 690 count = 0 691 for parameter in args.parameterlist: 692 self.data += f'.br\n.BI " {parameter}"' + "\n" 693 if count == len(args.parameterlist) - 1: 694 self.data += "\n};\n" 695 else: 696 self.data += ", \n.br\n" 697 698 count += 1 699 700 self.data += ".SH Constants\n" 701 702 for parameter in args.parameterlist: 703 parameter_name = KernRe(r'\[.*').sub('', parameter) 704 self.data += f'.IP "{parameter}" 12' + "\n" 705 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 706 707 for section, text in args.sections.items(): 708 self.data += f'.SH "{section}"' + "\n" 709 self.output_highlight(text) 710 711 def out_typedef(self, fname, name, args): 712 module = self.modulename 713 typedef = args.get('typedef') 714 purpose = args.get('purpose') 715 716 self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n" 717 718 self.data += ".SH NAME\n" 719 self.data += f"typedef {typedef} \\- {purpose}\n" 720 721 for section, text in args.sections.items(): 722 self.data += f'.SH "{section}"' + "\n" 723 self.output_highlight(text) 724 725 def out_struct(self, fname, name, args): 726 module = self.modulename 727 struct_type = args.get('type') 728 struct_name = args.get('struct') 729 purpose = args.get('purpose') 730 definition = args.get('definition') 731 732 self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n" 733 734 self.data += ".SH NAME\n" 735 self.data += f"{struct_type} {struct_name} \\- {purpose}\n" 736 737 # Replace tabs with two spaces and handle newlines 738 declaration = definition.replace("\t", " ") 739 declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) 740 741 self.data += ".SH SYNOPSIS\n" 742 self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n" 743 self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" 744 745 self.data += ".SH Members\n" 746 for parameter in args.parameterlist: 747 if parameter.startswith("#"): 748 continue 749 750 parameter_name = re.sub(r"\[.*", "", parameter) 751 752 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 753 continue 754 755 self.data += f'.IP "{parameter}" 12' + "\n" 756 self.output_highlight(args.parameterdescs.get(parameter_name)) 757 758 for section, text in args.sections.items(): 759 self.data += f'.SH "{section}"' + "\n" 760 self.output_highlight(text) 761