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 342 sections = args.get('sections', {}) 343 section_start_lines = args.get('section_start_lines', {}) 344 345 for section in sections: 346 # Skip sections that are in the nosymbol_table 347 if section in self.nosymbol: 348 continue 349 350 if out_docblock: 351 if not self.out_mode == self.OUTPUT_INCLUDE: 352 self.data += f".. _{section}:\n\n" 353 self.data += f'{self.lineprefix}**{section}**\n\n' 354 else: 355 self.data += f'{self.lineprefix}**{section}**\n\n' 356 357 self.print_lineno(section_start_lines.get(section, 0)) 358 self.output_highlight(sections[section]) 359 self.data += "\n" 360 self.data += "\n" 361 362 def out_doc(self, fname, name, args): 363 if not self.check_doc(name, args): 364 return 365 self.out_section(args, out_docblock=True) 366 367 def out_function(self, fname, name, args): 368 369 oldprefix = self.lineprefix 370 signature = "" 371 372 func_macro = args.get('func_macro', False) 373 if func_macro: 374 signature = args['function'] 375 else: 376 if args.get('functiontype'): 377 signature = args['functiontype'] + " " 378 signature += args['function'] + " (" 379 380 parameterlist = args.get('parameterlist', []) 381 parameterdescs = args.get('parameterdescs', {}) 382 parameterdesc_start_lines = args.get('parameterdesc_start_lines', {}) 383 384 ln = args.get('declaration_start_line', 0) 385 386 count = 0 387 for parameter in parameterlist: 388 if count != 0: 389 signature += ", " 390 count += 1 391 dtype = args['parametertypes'].get(parameter, "") 392 393 if function_pointer.search(dtype): 394 signature += function_pointer.group(1) + parameter + function_pointer.group(3) 395 else: 396 signature += dtype 397 398 if not func_macro: 399 signature += ")" 400 401 self.print_lineno(ln) 402 if args.get('typedef') or not args.get('functiontype'): 403 self.data += f".. c:macro:: {args['function']}\n\n" 404 405 if args.get('typedef'): 406 self.data += " **Typedef**: " 407 self.lineprefix = "" 408 self.output_highlight(args.get('purpose', "")) 409 self.data += "\n\n**Syntax**\n\n" 410 self.data += f" ``{signature}``\n\n" 411 else: 412 self.data += f"``{signature}``\n\n" 413 else: 414 self.data += f".. c:function:: {signature}\n\n" 415 416 if not args.get('typedef'): 417 self.print_lineno(ln) 418 self.lineprefix = " " 419 self.output_highlight(args.get('purpose', "")) 420 self.data += "\n" 421 422 # Put descriptive text into a container (HTML <div>) to help set 423 # function prototypes apart 424 self.lineprefix = " " 425 426 if parameterlist: 427 self.data += ".. container:: kernelindent\n\n" 428 self.data += f"{self.lineprefix}**Parameters**\n\n" 429 430 for parameter in parameterlist: 431 parameter_name = KernRe(r'\[.*').sub('', parameter) 432 dtype = args['parametertypes'].get(parameter, "") 433 434 if dtype: 435 self.data += f"{self.lineprefix}``{dtype}``\n" 436 else: 437 self.data += f"{self.lineprefix}``{parameter}``\n" 438 439 self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) 440 441 self.lineprefix = " " 442 if parameter_name in parameterdescs and \ 443 parameterdescs[parameter_name] != KernelDoc.undescribed: 444 445 self.output_highlight(parameterdescs[parameter_name]) 446 self.data += "\n" 447 else: 448 self.data += f"{self.lineprefix}*undescribed*\n\n" 449 self.lineprefix = " " 450 451 self.out_section(args) 452 self.lineprefix = oldprefix 453 454 def out_enum(self, fname, name, args): 455 456 oldprefix = self.lineprefix 457 name = args.get('enum', '') 458 parameterlist = args.get('parameterlist', []) 459 parameterdescs = args.get('parameterdescs', {}) 460 ln = args.get('declaration_start_line', 0) 461 462 self.data += f"\n\n.. c:enum:: {name}\n\n" 463 464 self.print_lineno(ln) 465 self.lineprefix = " " 466 self.output_highlight(args.get('purpose', '')) 467 self.data += "\n" 468 469 self.data += ".. container:: kernelindent\n\n" 470 outer = self.lineprefix + " " 471 self.lineprefix = outer + " " 472 self.data += f"{outer}**Constants**\n\n" 473 474 for parameter in parameterlist: 475 self.data += f"{outer}``{parameter}``\n" 476 477 if parameterdescs.get(parameter, '') != KernelDoc.undescribed: 478 self.output_highlight(parameterdescs[parameter]) 479 else: 480 self.data += f"{self.lineprefix}*undescribed*\n\n" 481 self.data += "\n" 482 483 self.lineprefix = oldprefix 484 self.out_section(args) 485 486 def out_typedef(self, fname, name, args): 487 488 oldprefix = self.lineprefix 489 name = args.get('typedef', '') 490 ln = args.get('declaration_start_line', 0) 491 492 self.data += f"\n\n.. c:type:: {name}\n\n" 493 494 self.print_lineno(ln) 495 self.lineprefix = " " 496 497 self.output_highlight(args.get('purpose', '')) 498 499 self.data += "\n" 500 501 self.lineprefix = oldprefix 502 self.out_section(args) 503 504 def out_struct(self, fname, name, args): 505 506 name = args.get('struct', "") 507 purpose = args.get('purpose', "") 508 declaration = args.get('definition', "") 509 dtype = args.get('type', "struct") 510 ln = args.get('declaration_start_line', 0) 511 512 parameterlist = args.get('parameterlist', []) 513 parameterdescs = args.get('parameterdescs', {}) 514 parameterdesc_start_lines = args.get('parameterdesc_start_lines', {}) 515 516 self.data += f"\n\n.. c:{dtype}:: {name}\n\n" 517 518 self.print_lineno(ln) 519 520 oldprefix = self.lineprefix 521 self.lineprefix += " " 522 523 self.output_highlight(purpose) 524 self.data += "\n" 525 526 self.data += ".. container:: kernelindent\n\n" 527 self.data += f"{self.lineprefix}**Definition**::\n\n" 528 529 self.lineprefix = self.lineprefix + " " 530 531 declaration = declaration.replace("\t", self.lineprefix) 532 533 self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" 534 self.data += f"{declaration}{self.lineprefix}" + "};\n\n" 535 536 self.lineprefix = " " 537 self.data += f"{self.lineprefix}**Members**\n\n" 538 for parameter in parameterlist: 539 if not parameter or parameter.startswith("#"): 540 continue 541 542 parameter_name = parameter.split("[", maxsplit=1)[0] 543 544 if parameterdescs.get(parameter_name) == KernelDoc.undescribed: 545 continue 546 547 self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) 548 549 self.data += f"{self.lineprefix}``{parameter}``\n" 550 551 self.lineprefix = " " 552 self.output_highlight(parameterdescs[parameter_name]) 553 self.lineprefix = " " 554 555 self.data += "\n" 556 557 self.data += "\n" 558 559 self.lineprefix = oldprefix 560 self.out_section(args) 561 562 563class ManFormat(OutputFormat): 564 """Consts and functions used by man pages output""" 565 566 highlights = ( 567 (type_constant, r"\1"), 568 (type_constant2, r"\1"), 569 (type_func, r"\\fB\1\\fP"), 570 (type_enum, r"\\fI\1\\fP"), 571 (type_struct, r"\\fI\1\\fP"), 572 (type_typedef, r"\\fI\1\\fP"), 573 (type_union, r"\\fI\1\\fP"), 574 (type_param, r"\\fI\1\\fP"), 575 (type_param_ref, r"\\fI\1\2\\fP"), 576 (type_member, r"\\fI\1\2\3\\fP"), 577 (type_fallback, r"\\fI\1\\fP") 578 ) 579 blankline = "" 580 581 date_formats = [ 582 "%a %b %d %H:%M:%S %Z %Y", 583 "%a %b %d %H:%M:%S %Y", 584 "%Y-%m-%d", 585 "%b %d %Y", 586 "%B %d %Y", 587 "%m %d %Y", 588 ] 589 590 def __init__(self, modulename): 591 """ 592 Creates class variables. 593 594 Not really mandatory, but it is a good coding style and makes 595 pylint happy. 596 """ 597 598 super().__init__() 599 self.modulename = modulename 600 601 dt = None 602 tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") 603 if tstamp: 604 for fmt in self.date_formats: 605 try: 606 dt = datetime.strptime(tstamp, fmt) 607 break 608 except ValueError: 609 pass 610 611 if not dt: 612 dt = datetime.now() 613 614 self.man_date = dt.strftime("%B %Y") 615 616 def output_highlight(self, block): 617 """ 618 Outputs a C symbol that may require being highlighted with 619 self.highlights variable using troff syntax 620 """ 621 622 contents = self.highlight_block(block) 623 624 if isinstance(contents, list): 625 contents = "\n".join(contents) 626 627 for line in contents.strip("\n").split("\n"): 628 line = KernRe(r"^\s*").sub("", line) 629 if not line: 630 continue 631 632 if line[0] == ".": 633 self.data += "\\&" + line + "\n" 634 else: 635 self.data += line + "\n" 636 637 def out_doc(self, fname, name, args): 638 sections = args.get('sections', {}) 639 640 if not self.check_doc(name, args): 641 return 642 643 self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n" 644 645 for section in sections: 646 self.data += f'.SH "{section}"' + "\n" 647 self.output_highlight(sections.get(section)) 648 649 def out_function(self, fname, name, args): 650 """output function in man""" 651 652 parameterlist = args.get('parameterlist', []) 653 parameterdescs = args.get('parameterdescs', {}) 654 sections = args.get('sections', {}) 655 656 self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" 657 658 self.data += ".SH NAME\n" 659 self.data += f"{args['function']} \\- {args['purpose']}\n" 660 661 self.data += ".SH SYNOPSIS\n" 662 if args.get('functiontype', ''): 663 self.data += f'.B "{args["functiontype"]}" {args["function"]}' + "\n" 664 else: 665 self.data += f'.B "{args["function"]}' + "\n" 666 667 count = 0 668 parenth = "(" 669 post = "," 670 671 for parameter in parameterlist: 672 if count == len(parameterlist) - 1: 673 post = ");" 674 675 dtype = args['parametertypes'].get(parameter, "") 676 if function_pointer.match(dtype): 677 # Pointer-to-function 678 self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" 679 else: 680 dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype) 681 682 self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" 683 count += 1 684 parenth = "" 685 686 if parameterlist: 687 self.data += ".SH ARGUMENTS\n" 688 689 for parameter in parameterlist: 690 parameter_name = re.sub(r'\[.*', '', parameter) 691 692 self.data += f'.IP "{parameter}" 12' + "\n" 693 self.output_highlight(parameterdescs.get(parameter_name, "")) 694 695 for section in sections: 696 self.data += f'.SH "{section.upper()}"' + "\n" 697 self.output_highlight(sections[section]) 698 699 def out_enum(self, fname, name, args): 700 701 name = args.get('enum', '') 702 parameterlist = args.get('parameterlist', []) 703 sections = args.get('sections', {}) 704 705 self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" 706 707 self.data += ".SH NAME\n" 708 self.data += f"enum {args['enum']} \\- {args['purpose']}\n" 709 710 self.data += ".SH SYNOPSIS\n" 711 self.data += f"enum {args['enum']}" + " {\n" 712 713 count = 0 714 for parameter in parameterlist: 715 self.data += f'.br\n.BI " {parameter}"' + "\n" 716 if count == len(parameterlist) - 1: 717 self.data += "\n};\n" 718 else: 719 self.data += ", \n.br\n" 720 721 count += 1 722 723 self.data += ".SH Constants\n" 724 725 for parameter in parameterlist: 726 parameter_name = KernRe(r'\[.*').sub('', parameter) 727 self.data += f'.IP "{parameter}" 12' + "\n" 728 self.output_highlight(args['parameterdescs'].get(parameter_name, "")) 729 730 for section in sections: 731 self.data += f'.SH "{section}"' + "\n" 732 self.output_highlight(sections[section]) 733 734 def out_typedef(self, fname, name, args): 735 module = self.modulename 736 typedef = args.get('typedef') 737 purpose = args.get('purpose') 738 sections = args.get('sections', {}) 739 740 self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n" 741 742 self.data += ".SH NAME\n" 743 self.data += f"typedef {typedef} \\- {purpose}\n" 744 745 for section in sections: 746 self.data += f'.SH "{section}"' + "\n" 747 self.output_highlight(sections.get(section)) 748 749 def out_struct(self, fname, name, args): 750 module = self.modulename 751 struct_type = args.get('type') 752 struct_name = args.get('struct') 753 purpose = args.get('purpose') 754 definition = args.get('definition') 755 parameterlist = args.get('parameterlist', []) 756 sections = args.get('sections', {}) 757 parameterdescs = args.get('parameterdescs', {}) 758 759 self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n" 760 761 self.data += ".SH NAME\n" 762 self.data += f"{struct_type} {struct_name} \\- {purpose}\n" 763 764 # Replace tabs with two spaces and handle newlines 765 declaration = definition.replace("\t", " ") 766 declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) 767 768 self.data += ".SH SYNOPSIS\n" 769 self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n" 770 self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" 771 772 self.data += ".SH Members\n" 773 for parameter in parameterlist: 774 if parameter.startswith("#"): 775 continue 776 777 parameter_name = re.sub(r"\[.*", "", parameter) 778 779 if parameterdescs.get(parameter_name) == KernelDoc.undescribed: 780 continue 781 782 self.data += f'.IP "{parameter}" 12' + "\n" 783 self.output_highlight(parameterdescs.get(parameter_name)) 784 785 for section in sections: 786 self.data += f'.SH "{section}"' + "\n" 787 self.output_highlight(sections.get(section)) 788