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 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.kdoc_parser import KernelDoc, type_param 23from kdoc.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 overridden 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 to 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 for log_msg in args.warnings: 128 self.config.warning(log_msg) 129 130 def check_doc(self, name, args): 131 """Check if DOC should be output""" 132 133 if self.no_doc_sections: 134 return False 135 136 if name in self.nosymbol: 137 return False 138 139 if self.out_mode == self.OUTPUT_ALL: 140 self.out_warnings(args) 141 return True 142 143 if self.out_mode == self.OUTPUT_INCLUDE: 144 if name in self.function_table: 145 self.out_warnings(args) 146 return True 147 148 return False 149 150 def check_declaration(self, dtype, name, args): 151 """ 152 Checks if a declaration should be output or not based on the 153 filtering criteria. 154 """ 155 156 if name in self.nosymbol: 157 return False 158 159 if self.out_mode == self.OUTPUT_ALL: 160 self.out_warnings(args) 161 return True 162 163 if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]: 164 if name in self.function_table: 165 return True 166 167 if self.out_mode == self.OUTPUT_INTERNAL: 168 if dtype != "function": 169 self.out_warnings(args) 170 return True 171 172 if name not in self.function_table: 173 self.out_warnings(args) 174 return True 175 176 return False 177 178 def msg(self, fname, name, args): 179 """ 180 Handles a single entry from kernel-doc parser 181 """ 182 183 self.data = "" 184 185 dtype = args.type 186 187 if dtype == "doc": 188 self.out_doc(fname, name, args) 189 return self.data 190 191 if not self.check_declaration(dtype, name, args): 192 return self.data 193 194 if dtype == "function": 195 self.out_function(fname, name, args) 196 return self.data 197 198 if dtype == "enum": 199 self.out_enum(fname, name, args) 200 return self.data 201 202 if dtype == "typedef": 203 self.out_typedef(fname, name, args) 204 return self.data 205 206 if dtype in ["struct", "union"]: 207 self.out_struct(fname, name, args) 208 return self.data 209 210 # Warn if some type requires an output logic 211 self.config.log.warning("doesn't know how to output '%s' block", 212 dtype) 213 214 return None 215 216 # Virtual methods to be overridden by inherited classes 217 # At the base class, those do nothing. 218 def set_symbols(self, symbols): 219 """Get a list of all symbols from kernel_doc""" 220 221 def out_doc(self, fname, name, args): 222 """Outputs a DOC block""" 223 224 def out_function(self, fname, name, args): 225 """Outputs a function""" 226 227 def out_enum(self, fname, name, args): 228 """Outputs an enum""" 229 230 def out_typedef(self, fname, name, args): 231 """Outputs a typedef""" 232 233 def out_struct(self, fname, name, args): 234 """Outputs a struct""" 235 236 237class RestFormat(OutputFormat): 238 """Consts and functions used by ReST output""" 239 240 highlights = [ 241 (type_constant, r"``\1``"), 242 (type_constant2, r"``\1``"), 243 244 # Note: need to escape () to avoid func matching later 245 (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"), 246 (type_member, r":c:type:`\1\2\3 <\1>`"), 247 (type_fp_param, r"**\1\\(\\)**"), 248 (type_fp_param2, r"**\1\\(\\)**"), 249 (type_func, r"\1()"), 250 (type_enum, r":c:type:`\1 <\2>`"), 251 (type_struct, r":c:type:`\1 <\2>`"), 252 (type_typedef, r":c:type:`\1 <\2>`"), 253 (type_union, r":c:type:`\1 <\2>`"), 254 255 # in rst this can refer to any type 256 (type_fallback, r":c:type:`\1`"), 257 (type_param_ref, r"**\1\2**") 258 ] 259 blankline = "\n" 260 261 sphinx_literal = KernRe(r'^[^.].*::$', cache=False) 262 sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False) 263 264 def __init__(self): 265 """ 266 Creates class variables. 267 268 Not really mandatory, but it is a good coding style and makes 269 pylint happy. 270 """ 271 272 super().__init__() 273 self.lineprefix = "" 274 275 def print_lineno(self, ln): 276 """Outputs a line number""" 277 278 if self.enable_lineno and ln is not None: 279 ln += 1 280 self.data += f".. LINENO {ln}\n" 281 282 def output_highlight(self, args): 283 """ 284 Outputs a C symbol that may require being converted to ReST using 285 the self.highlights variable 286 """ 287 288 input_text = args 289 output = "" 290 in_literal = False 291 litprefix = "" 292 block = "" 293 294 for line in input_text.strip("\n").split("\n"): 295 296 # If we're in a literal block, see if we should drop out of it. 297 # Otherwise, pass the line straight through unmunged. 298 if in_literal: 299 if line.strip(): # If the line is not blank 300 # If this is the first non-blank line in a literal block, 301 # figure out the proper indent. 302 if not litprefix: 303 r = KernRe(r'^(\s*)') 304 if r.match(line): 305 litprefix = '^' + r.group(1) 306 else: 307 litprefix = "" 308 309 output += line + "\n" 310 elif not KernRe(litprefix).match(line): 311 in_literal = False 312 else: 313 output += line + "\n" 314 else: 315 output += line + "\n" 316 317 # Not in a literal block (or just dropped out) 318 if not in_literal: 319 block += line + "\n" 320 if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line): 321 in_literal = True 322 litprefix = "" 323 output += self.highlight_block(block) 324 block = "" 325 326 # Handle any remaining block 327 if block: 328 output += self.highlight_block(block) 329 330 # Print the output with the line prefix 331 for line in output.strip("\n").split("\n"): 332 self.data += self.lineprefix + line + "\n" 333 334 def out_section(self, args, out_docblock=False): 335 """ 336 Outputs a block section. 337 338 This could use some work; it's used to output the DOC: sections, and 339 starts by putting out the name of the doc section itself, but that 340 tends to duplicate a header already in the template file. 341 """ 342 for section, text in args.sections.items(): 343 # Skip sections that are in the nosymbol_table 344 if section in self.nosymbol: 345 continue 346 347 if out_docblock: 348 if not self.out_mode == self.OUTPUT_INCLUDE: 349 self.data += f".. _{section}:\n\n" 350 self.data += f'{self.lineprefix}**{section}**\n\n' 351 else: 352 self.data += f'{self.lineprefix}**{section}**\n\n' 353 354 self.print_lineno(args.section_start_lines.get(section, 0)) 355 self.output_highlight(text) 356 self.data += "\n" 357 self.data += "\n" 358 359 def out_doc(self, fname, name, args): 360 if not self.check_doc(name, args): 361 return 362 self.out_section(args, out_docblock=True) 363 364 def out_function(self, fname, name, args): 365 366 oldprefix = self.lineprefix 367 signature = "" 368 369 func_macro = args.get('func_macro', False) 370 if func_macro: 371 signature = name 372 else: 373 if args.get('functiontype'): 374 signature = args['functiontype'] + " " 375 signature += name + " (" 376 377 ln = args.declaration_start_line 378 count = 0 379 for parameter in args.parameterlist: 380 if count != 0: 381 signature += ", " 382 count += 1 383 dtype = args.parametertypes.get(parameter, "") 384 385 if function_pointer.search(dtype): 386 signature += function_pointer.group(1) + parameter + function_pointer.group(3) 387 else: 388 signature += dtype 389 390 if not func_macro: 391 signature += ")" 392 393 self.print_lineno(ln) 394 if args.get('typedef') or not args.get('functiontype'): 395 self.data += f".. c:macro:: {name}\n\n" 396 397 if args.get('typedef'): 398 self.data += " **Typedef**: " 399 self.lineprefix = "" 400 self.output_highlight(args.get('purpose', "")) 401 self.data += "\n\n**Syntax**\n\n" 402 self.data += f" ``{signature}``\n\n" 403 else: 404 self.data += f"``{signature}``\n\n" 405 else: 406 self.data += f".. c:function:: {signature}\n\n" 407 408 if not args.get('typedef'): 409 self.print_lineno(ln) 410 self.lineprefix = " " 411 self.output_highlight(args.get('purpose', "")) 412 self.data += "\n" 413 414 # Put descriptive text into a container (HTML <div>) to help set 415 # function prototypes apart 416 self.lineprefix = " " 417 418 if args.parameterlist: 419 self.data += ".. container:: kernelindent\n\n" 420 self.data += f"{self.lineprefix}**Parameters**\n\n" 421 422 for parameter in args.parameterlist: 423 parameter_name = KernRe(r'\[.*').sub('', parameter) 424 dtype = args.parametertypes.get(parameter, "") 425 426 if dtype: 427 self.data += f"{self.lineprefix}``{dtype}``\n" 428 else: 429 self.data += f"{self.lineprefix}``{parameter}``\n" 430 431 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 432 433 self.lineprefix = " " 434 if parameter_name in args.parameterdescs and \ 435 args.parameterdescs[parameter_name] != KernelDoc.undescribed: 436 437 self.output_highlight(args.parameterdescs[parameter_name]) 438 self.data += "\n" 439 else: 440 self.data += f"{self.lineprefix}*undescribed*\n\n" 441 self.lineprefix = " " 442 443 self.out_section(args) 444 self.lineprefix = oldprefix 445 446 def out_enum(self, fname, name, args): 447 448 oldprefix = self.lineprefix 449 ln = args.declaration_start_line 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 ln = args.declaration_start_line 479 480 self.data += f"\n\n.. c:type:: {name}\n\n" 481 482 self.print_lineno(ln) 483 self.lineprefix = " " 484 485 self.output_highlight(args.get('purpose', '')) 486 487 self.data += "\n" 488 489 self.lineprefix = oldprefix 490 self.out_section(args) 491 492 def out_struct(self, fname, name, args): 493 494 purpose = args.get('purpose', "") 495 declaration = args.get('definition', "") 496 dtype = args.type 497 ln = args.declaration_start_line 498 499 self.data += f"\n\n.. c:{dtype}:: {name}\n\n" 500 501 self.print_lineno(ln) 502 503 oldprefix = self.lineprefix 504 self.lineprefix += " " 505 506 self.output_highlight(purpose) 507 self.data += "\n" 508 509 self.data += ".. container:: kernelindent\n\n" 510 self.data += f"{self.lineprefix}**Definition**::\n\n" 511 512 self.lineprefix = self.lineprefix + " " 513 514 declaration = declaration.replace("\t", self.lineprefix) 515 516 self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" 517 self.data += f"{declaration}{self.lineprefix}" + "};\n\n" 518 519 self.lineprefix = " " 520 self.data += f"{self.lineprefix}**Members**\n\n" 521 for parameter in args.parameterlist: 522 if not parameter or parameter.startswith("#"): 523 continue 524 525 parameter_name = parameter.split("[", maxsplit=1)[0] 526 527 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 528 continue 529 530 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 531 532 self.data += f"{self.lineprefix}``{parameter}``\n" 533 534 self.lineprefix = " " 535 self.output_highlight(args.parameterdescs[parameter_name]) 536 self.lineprefix = " " 537 538 self.data += "\n" 539 540 self.data += "\n" 541 542 self.lineprefix = oldprefix 543 self.out_section(args) 544 545 546class ManFormat(OutputFormat): 547 """Consts and functions used by man pages output""" 548 549 highlights = ( 550 (type_constant, r"\1"), 551 (type_constant2, r"\1"), 552 (type_func, r"\\fB\1\\fP"), 553 (type_enum, r"\\fI\1\\fP"), 554 (type_struct, r"\\fI\1\\fP"), 555 (type_typedef, r"\\fI\1\\fP"), 556 (type_union, r"\\fI\1\\fP"), 557 (type_param, r"\\fI\1\\fP"), 558 (type_param_ref, r"\\fI\1\2\\fP"), 559 (type_member, r"\\fI\1\2\3\\fP"), 560 (type_fallback, r"\\fI\1\\fP") 561 ) 562 blankline = "" 563 564 date_formats = [ 565 "%a %b %d %H:%M:%S %Z %Y", 566 "%a %b %d %H:%M:%S %Y", 567 "%Y-%m-%d", 568 "%b %d %Y", 569 "%B %d %Y", 570 "%m %d %Y", 571 ] 572 573 def __init__(self, modulename): 574 """ 575 Creates class variables. 576 577 Not really mandatory, but it is a good coding style and makes 578 pylint happy. 579 """ 580 581 super().__init__() 582 self.modulename = modulename 583 self.symbols = [] 584 585 dt = None 586 tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") 587 if tstamp: 588 for fmt in self.date_formats: 589 try: 590 dt = datetime.strptime(tstamp, fmt) 591 break 592 except ValueError: 593 pass 594 595 if not dt: 596 dt = datetime.now() 597 598 self.man_date = dt.strftime("%B %Y") 599 600 def arg_name(self, args, name): 601 """ 602 Return the name that will be used for the man page. 603 604 As we may have the same name on different namespaces, 605 prepend the data type for all types except functions and typedefs. 606 607 The doc section is special: it uses the modulename. 608 """ 609 610 dtype = args.type 611 612 if dtype == "doc": 613 return self.modulename 614 615 if dtype in ["function", "typedef"]: 616 return name 617 618 return f"{dtype} {name}" 619 620 def set_symbols(self, symbols): 621 """ 622 Get a list of all symbols from kernel_doc. 623 624 Man pages will uses it to add a SEE ALSO section with other 625 symbols at the same file. 626 """ 627 self.symbols = symbols 628 629 def out_tail(self, fname, name, args): 630 """Adds a tail for all man pages""" 631 632 # SEE ALSO section 633 self.data += f'.SH "SEE ALSO"' + "\n.PP\n" 634 self.data += (f"Kernel file \\fB{args.fname}\\fR\n") 635 if len(self.symbols) >= 2: 636 cur_name = self.arg_name(args, name) 637 638 related = [] 639 for arg in self.symbols: 640 out_name = self.arg_name(arg, arg.name) 641 642 if cur_name == out_name: 643 continue 644 645 related.append(f"\\fB{out_name}\\fR(9)") 646 647 self.data += ",\n".join(related) + "\n" 648 649 # TODO: does it make sense to add other sections? Maybe 650 # REPORTING ISSUES? LICENSE? 651 652 def msg(self, fname, name, args): 653 """ 654 Handles a single entry from kernel-doc parser. 655 656 Add a tail at the end of man pages output. 657 """ 658 super().msg(fname, name, args) 659 self.out_tail(fname, name, args) 660 661 return self.data 662 663 def output_highlight(self, block): 664 """ 665 Outputs a C symbol that may require being highlighted with 666 self.highlights variable using troff syntax 667 """ 668 669 contents = self.highlight_block(block) 670 671 if isinstance(contents, list): 672 contents = "\n".join(contents) 673 674 for line in contents.strip("\n").split("\n"): 675 line = KernRe(r"^\s*").sub("", line) 676 if not line: 677 continue 678 679 if line[0] == ".": 680 self.data += "\\&" + line + "\n" 681 else: 682 self.data += line + "\n" 683 684 def out_doc(self, fname, name, args): 685 if not self.check_doc(name, args): 686 return 687 688 out_name = self.arg_name(args, name) 689 690 self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n" 691 692 for section, text in args.sections.items(): 693 self.data += f'.SH "{section}"' + "\n" 694 self.output_highlight(text) 695 696 def out_function(self, fname, name, args): 697 """output function in man""" 698 699 out_name = self.arg_name(args, name) 700 701 self.data += f'.TH "{name}" 9 "{out_name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" 702 703 self.data += ".SH NAME\n" 704 self.data += f"{name} \\- {args['purpose']}\n" 705 706 self.data += ".SH SYNOPSIS\n" 707 if args.get('functiontype', ''): 708 self.data += f'.B "{args["functiontype"]}" {name}' + "\n" 709 else: 710 self.data += f'.B "{name}' + "\n" 711 712 count = 0 713 parenth = "(" 714 post = "," 715 716 for parameter in args.parameterlist: 717 if count == len(args.parameterlist) - 1: 718 post = ");" 719 720 dtype = args.parametertypes.get(parameter, "") 721 if function_pointer.match(dtype): 722 # Pointer-to-function 723 self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" 724 else: 725 dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype) 726 727 self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" 728 count += 1 729 parenth = "" 730 731 if args.parameterlist: 732 self.data += ".SH ARGUMENTS\n" 733 734 for parameter in args.parameterlist: 735 parameter_name = re.sub(r'\[.*', '', parameter) 736 737 self.data += f'.IP "{parameter}" 12' + "\n" 738 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 739 740 for section, text in args.sections.items(): 741 self.data += f'.SH "{section.upper()}"' + "\n" 742 self.output_highlight(text) 743 744 def out_enum(self, fname, name, args): 745 out_name = self.arg_name(args, name) 746 747 self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n" 748 749 self.data += ".SH NAME\n" 750 self.data += f"enum {name} \\- {args['purpose']}\n" 751 752 self.data += ".SH SYNOPSIS\n" 753 self.data += f"enum {name}" + " {\n" 754 755 count = 0 756 for parameter in args.parameterlist: 757 self.data += f'.br\n.BI " {parameter}"' + "\n" 758 if count == len(args.parameterlist) - 1: 759 self.data += "\n};\n" 760 else: 761 self.data += ", \n.br\n" 762 763 count += 1 764 765 self.data += ".SH Constants\n" 766 767 for parameter in args.parameterlist: 768 parameter_name = KernRe(r'\[.*').sub('', parameter) 769 self.data += f'.IP "{parameter}" 12' + "\n" 770 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 771 772 for section, text in args.sections.items(): 773 self.data += f'.SH "{section}"' + "\n" 774 self.output_highlight(text) 775 776 def out_typedef(self, fname, name, args): 777 module = self.modulename 778 purpose = args.get('purpose') 779 out_name = self.arg_name(args, name) 780 781 self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n" 782 783 self.data += ".SH NAME\n" 784 self.data += f"typedef {name} \\- {purpose}\n" 785 786 for section, text in args.sections.items(): 787 self.data += f'.SH "{section}"' + "\n" 788 self.output_highlight(text) 789 790 def out_struct(self, fname, name, args): 791 module = self.modulename 792 purpose = args.get('purpose') 793 definition = args.get('definition') 794 out_name = self.arg_name(args, name) 795 796 self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n" 797 798 self.data += ".SH NAME\n" 799 self.data += f"{args.type} {name} \\- {purpose}\n" 800 801 # Replace tabs with two spaces and handle newlines 802 declaration = definition.replace("\t", " ") 803 declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) 804 805 self.data += ".SH SYNOPSIS\n" 806 self.data += f"{args.type} {name} " + "{" + "\n.br\n" 807 self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" 808 809 self.data += ".SH Members\n" 810 for parameter in args.parameterlist: 811 if parameter.startswith("#"): 812 continue 813 814 parameter_name = re.sub(r"\[.*", "", parameter) 815 816 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 817 continue 818 819 self.data += f'.IP "{parameter}" 12' + "\n" 820 self.output_highlight(args.parameterdescs.get(parameter_name)) 821 822 for section, text in args.sections.items(): 823 self.data += f'.SH "{section}"' + "\n" 824 self.output_highlight(text) 825