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