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 = name 371 else: 372 if args.get('functiontype'): 373 signature = args['functiontype'] + " " 374 signature += name + " (" 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:: {name}\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 ln = args.get('declaration_start_line', 0) 449 450 self.data += f"\n\n.. c:enum:: {name}\n\n" 451 452 self.print_lineno(ln) 453 self.lineprefix = " " 454 self.output_highlight(args.get('purpose', '')) 455 self.data += "\n" 456 457 self.data += ".. container:: kernelindent\n\n" 458 outer = self.lineprefix + " " 459 self.lineprefix = outer + " " 460 self.data += f"{outer}**Constants**\n\n" 461 462 for parameter in args.parameterlist: 463 self.data += f"{outer}``{parameter}``\n" 464 465 if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed: 466 self.output_highlight(args.parameterdescs[parameter]) 467 else: 468 self.data += f"{self.lineprefix}*undescribed*\n\n" 469 self.data += "\n" 470 471 self.lineprefix = oldprefix 472 self.out_section(args) 473 474 def out_typedef(self, fname, name, args): 475 476 oldprefix = self.lineprefix 477 ln = args.get('declaration_start_line', 0) 478 479 self.data += f"\n\n.. c:type:: {name}\n\n" 480 481 self.print_lineno(ln) 482 self.lineprefix = " " 483 484 self.output_highlight(args.get('purpose', '')) 485 486 self.data += "\n" 487 488 self.lineprefix = oldprefix 489 self.out_section(args) 490 491 def out_struct(self, fname, name, args): 492 493 purpose = args.get('purpose', "") 494 declaration = args.get('definition', "") 495 dtype = args.get('type', "struct") 496 ln = args.get('declaration_start_line', 0) 497 498 self.data += f"\n\n.. c:{dtype}:: {name}\n\n" 499 500 self.print_lineno(ln) 501 502 oldprefix = self.lineprefix 503 self.lineprefix += " " 504 505 self.output_highlight(purpose) 506 self.data += "\n" 507 508 self.data += ".. container:: kernelindent\n\n" 509 self.data += f"{self.lineprefix}**Definition**::\n\n" 510 511 self.lineprefix = self.lineprefix + " " 512 513 declaration = declaration.replace("\t", self.lineprefix) 514 515 self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" 516 self.data += f"{declaration}{self.lineprefix}" + "};\n\n" 517 518 self.lineprefix = " " 519 self.data += f"{self.lineprefix}**Members**\n\n" 520 for parameter in args.parameterlist: 521 if not parameter or parameter.startswith("#"): 522 continue 523 524 parameter_name = parameter.split("[", maxsplit=1)[0] 525 526 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 527 continue 528 529 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 530 531 self.data += f"{self.lineprefix}``{parameter}``\n" 532 533 self.lineprefix = " " 534 self.output_highlight(args.parameterdescs[parameter_name]) 535 self.lineprefix = " " 536 537 self.data += "\n" 538 539 self.data += "\n" 540 541 self.lineprefix = oldprefix 542 self.out_section(args) 543 544 545class ManFormat(OutputFormat): 546 """Consts and functions used by man pages output""" 547 548 highlights = ( 549 (type_constant, r"\1"), 550 (type_constant2, r"\1"), 551 (type_func, r"\\fB\1\\fP"), 552 (type_enum, r"\\fI\1\\fP"), 553 (type_struct, r"\\fI\1\\fP"), 554 (type_typedef, r"\\fI\1\\fP"), 555 (type_union, r"\\fI\1\\fP"), 556 (type_param, r"\\fI\1\\fP"), 557 (type_param_ref, r"\\fI\1\2\\fP"), 558 (type_member, r"\\fI\1\2\3\\fP"), 559 (type_fallback, r"\\fI\1\\fP") 560 ) 561 blankline = "" 562 563 date_formats = [ 564 "%a %b %d %H:%M:%S %Z %Y", 565 "%a %b %d %H:%M:%S %Y", 566 "%Y-%m-%d", 567 "%b %d %Y", 568 "%B %d %Y", 569 "%m %d %Y", 570 ] 571 572 def __init__(self, modulename): 573 """ 574 Creates class variables. 575 576 Not really mandatory, but it is a good coding style and makes 577 pylint happy. 578 """ 579 580 super().__init__() 581 self.modulename = modulename 582 583 dt = None 584 tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") 585 if tstamp: 586 for fmt in self.date_formats: 587 try: 588 dt = datetime.strptime(tstamp, fmt) 589 break 590 except ValueError: 591 pass 592 593 if not dt: 594 dt = datetime.now() 595 596 self.man_date = dt.strftime("%B %Y") 597 598 def output_highlight(self, block): 599 """ 600 Outputs a C symbol that may require being highlighted with 601 self.highlights variable using troff syntax 602 """ 603 604 contents = self.highlight_block(block) 605 606 if isinstance(contents, list): 607 contents = "\n".join(contents) 608 609 for line in contents.strip("\n").split("\n"): 610 line = KernRe(r"^\s*").sub("", line) 611 if not line: 612 continue 613 614 if line[0] == ".": 615 self.data += "\\&" + line + "\n" 616 else: 617 self.data += line + "\n" 618 619 def out_doc(self, fname, name, args): 620 if not self.check_doc(name, args): 621 return 622 623 self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n" 624 625 for section, text in args.sections.items(): 626 self.data += f'.SH "{section}"' + "\n" 627 self.output_highlight(text) 628 629 def out_function(self, fname, name, args): 630 """output function in man""" 631 632 self.data += f'.TH "{name}" 9 "{name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" 633 634 self.data += ".SH NAME\n" 635 self.data += f"{name} \\- {args['purpose']}\n" 636 637 self.data += ".SH SYNOPSIS\n" 638 if args.get('functiontype', ''): 639 self.data += f'.B "{args["functiontype"]}" {name}' + "\n" 640 else: 641 self.data += f'.B "{name}' + "\n" 642 643 count = 0 644 parenth = "(" 645 post = "," 646 647 for parameter in args.parameterlist: 648 if count == len(args.parameterlist) - 1: 649 post = ");" 650 651 dtype = args.parametertypes.get(parameter, "") 652 if function_pointer.match(dtype): 653 # Pointer-to-function 654 self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" 655 else: 656 dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype) 657 658 self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" 659 count += 1 660 parenth = "" 661 662 if args.parameterlist: 663 self.data += ".SH ARGUMENTS\n" 664 665 for parameter in args.parameterlist: 666 parameter_name = re.sub(r'\[.*', '', parameter) 667 668 self.data += f'.IP "{parameter}" 12' + "\n" 669 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 670 671 for section, text in args.sections.items(): 672 self.data += f'.SH "{section.upper()}"' + "\n" 673 self.output_highlight(text) 674 675 def out_enum(self, fname, name, args): 676 self.data += f'.TH "{self.modulename}" 9 "enum {name}" "{self.man_date}" "API Manual" LINUX' + "\n" 677 678 self.data += ".SH NAME\n" 679 self.data += f"enum {name} \\- {args['purpose']}\n" 680 681 self.data += ".SH SYNOPSIS\n" 682 self.data += f"enum {name}" + " {\n" 683 684 count = 0 685 for parameter in args.parameterlist: 686 self.data += f'.br\n.BI " {parameter}"' + "\n" 687 if count == len(args.parameterlist) - 1: 688 self.data += "\n};\n" 689 else: 690 self.data += ", \n.br\n" 691 692 count += 1 693 694 self.data += ".SH Constants\n" 695 696 for parameter in args.parameterlist: 697 parameter_name = KernRe(r'\[.*').sub('', parameter) 698 self.data += f'.IP "{parameter}" 12' + "\n" 699 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 700 701 for section, text in args.sections.items(): 702 self.data += f'.SH "{section}"' + "\n" 703 self.output_highlight(text) 704 705 def out_typedef(self, fname, name, args): 706 module = self.modulename 707 purpose = args.get('purpose') 708 709 self.data += f'.TH "{module}" 9 "{name}" "{self.man_date}" "API Manual" LINUX' + "\n" 710 711 self.data += ".SH NAME\n" 712 self.data += f"typedef {name} \\- {purpose}\n" 713 714 for section, text in args.sections.items(): 715 self.data += f'.SH "{section}"' + "\n" 716 self.output_highlight(text) 717 718 def out_struct(self, fname, name, args): 719 module = self.modulename 720 purpose = args.get('purpose') 721 definition = args.get('definition') 722 723 self.data += f'.TH "{module}" 9 "{args.type} {name}" "{self.man_date}" "API Manual" LINUX' + "\n" 724 725 self.data += ".SH NAME\n" 726 self.data += f"{args.type} {name} \\- {purpose}\n" 727 728 # Replace tabs with two spaces and handle newlines 729 declaration = definition.replace("\t", " ") 730 declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) 731 732 self.data += ".SH SYNOPSIS\n" 733 self.data += f"{args.type} {name} " + "{" + "\n.br\n" 734 self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" 735 736 self.data += ".SH Members\n" 737 for parameter in args.parameterlist: 738 if parameter.startswith("#"): 739 continue 740 741 parameter_name = re.sub(r"\[.*", "", parameter) 742 743 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 744 continue 745 746 self.data += f'.IP "{parameter}" 12' + "\n" 747 self.output_highlight(args.parameterdescs.get(parameter_name)) 748 749 for section, text in args.sections.items(): 750 self.data += f'.SH "{section}"' + "\n" 751 self.output_highlight(text) 752