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""" 8Classes to implement output filters to print kernel-doc documentation. 9 10The implementation uses a virtual base class ``OutputFormat``. It 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, e.g. ``RestFormat`` and ``ManFormat`` classes. 16 17Currently, there are output classes for ReST and man/troff. 18""" 19 20import os 21import re 22from datetime import datetime 23 24from kdoc.kdoc_parser import KernelDoc, type_param 25from kdoc.kdoc_re import KernRe 26 27 28function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False) 29 30# match expressions used to find embedded type information 31type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False) 32type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False) 33type_func = KernRe(r"(\w+)\(\)", cache=False) 34type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) 35 36# Special RST handling for func ptr params 37type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False) 38 39# Special RST handling for structs with func ptr params 40type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False) 41 42type_env = KernRe(r"(\$\w+)", cache=False) 43type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False) 44type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False) 45type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False) 46type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False) 47type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False) 48type_fallback = KernRe(r"\&([_\w]+)", cache=False) 49type_member_func = type_member + KernRe(r"\(\)", cache=False) 50 51 52class OutputFormat: 53 """ 54 Base class for OutputFormat. If used as-is, it means that only 55 warnings will be displayed. 56 """ 57 58 # output mode. 59 OUTPUT_ALL = 0 #: Output all symbols and doc sections. 60 OUTPUT_INCLUDE = 1 #: Output only specified symbols. 61 OUTPUT_EXPORTED = 2 #: Output exported symbols. 62 OUTPUT_INTERNAL = 3 #: Output non-exported symbols. 63 64 #: Highlights to be used in ReST format. 65 highlights = [] 66 67 #: Blank line character. 68 blankline = "" 69 70 def __init__(self): 71 """Declare internal vars and set mode to ``OUTPUT_ALL``.""" 72 73 self.out_mode = self.OUTPUT_ALL 74 self.enable_lineno = None 75 self.nosymbol = {} 76 self.symbol = None 77 self.function_table = None 78 self.config = None 79 self.no_doc_sections = False 80 81 self.data = "" 82 83 def set_config(self, config): 84 """ 85 Setup global config variables used by both parser and output. 86 """ 87 88 self.config = config 89 90 def set_filter(self, export, internal, symbol, nosymbol, function_table, 91 enable_lineno, no_doc_sections): 92 """ 93 Initialize filter variables according to the requested mode. 94 95 Only one choice is valid between export, internal and symbol. 96 97 The nosymbol filter can be used on all modes. 98 """ 99 100 self.enable_lineno = enable_lineno 101 self.no_doc_sections = no_doc_sections 102 self.function_table = function_table 103 104 if symbol: 105 self.out_mode = self.OUTPUT_INCLUDE 106 elif export: 107 self.out_mode = self.OUTPUT_EXPORTED 108 elif internal: 109 self.out_mode = self.OUTPUT_INTERNAL 110 else: 111 self.out_mode = self.OUTPUT_ALL 112 113 if nosymbol: 114 self.nosymbol = set(nosymbol) 115 116 117 def highlight_block(self, block): 118 """ 119 Apply the RST highlights to a sub-block of text. 120 """ 121 122 for r, sub in self.highlights: 123 block = r.sub(sub, block) 124 125 return block 126 127 def out_warnings(self, args): 128 """ 129 Output warnings for identifiers that will be displayed. 130 """ 131 132 for log_msg in args.warnings: 133 self.config.warning(log_msg) 134 135 def check_doc(self, name, args): 136 """Check if DOC should be output.""" 137 138 if self.no_doc_sections: 139 return False 140 141 if name in self.nosymbol: 142 return False 143 144 if self.out_mode == self.OUTPUT_ALL: 145 self.out_warnings(args) 146 return True 147 148 if self.out_mode == self.OUTPUT_INCLUDE: 149 if name in self.function_table: 150 self.out_warnings(args) 151 return True 152 153 return False 154 155 def check_declaration(self, dtype, name, args): 156 """ 157 Checks if a declaration should be output or not based on the 158 filtering criteria. 159 """ 160 161 if name in self.nosymbol: 162 return False 163 164 if self.out_mode == self.OUTPUT_ALL: 165 self.out_warnings(args) 166 return True 167 168 if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]: 169 if name in self.function_table: 170 return True 171 172 if self.out_mode == self.OUTPUT_INTERNAL: 173 if dtype != "function": 174 self.out_warnings(args) 175 return True 176 177 if name not in self.function_table: 178 self.out_warnings(args) 179 return True 180 181 return False 182 183 def msg(self, fname, name, args): 184 """ 185 Handles a single entry from kernel-doc parser. 186 """ 187 188 self.data = "" 189 190 dtype = args.type 191 192 if dtype == "doc": 193 self.out_doc(fname, name, args) 194 return self.data 195 196 if not self.check_declaration(dtype, name, args): 197 return self.data 198 199 if dtype == "function": 200 self.out_function(fname, name, args) 201 return self.data 202 203 if dtype == "enum": 204 self.out_enum(fname, name, args) 205 return self.data 206 207 if dtype == "var": 208 self.out_var(fname, name, args) 209 return self.data 210 211 if dtype == "typedef": 212 self.out_typedef(fname, name, args) 213 return self.data 214 215 if dtype in ["struct", "union"]: 216 self.out_struct(fname, name, args) 217 return self.data 218 219 # Warn if some type requires an output logic 220 self.config.log.warning("doesn't know how to output '%s' block", 221 dtype) 222 223 return None 224 225 # Virtual methods to be overridden by inherited classes 226 # At the base class, those do nothing. 227 def set_symbols(self, symbols): 228 """Get a list of all symbols from kernel_doc.""" 229 230 def out_doc(self, fname, name, args): 231 """Outputs a DOC block.""" 232 233 def out_function(self, fname, name, args): 234 """Outputs a function.""" 235 236 def out_enum(self, fname, name, args): 237 """Outputs an enum.""" 238 239 def out_var(self, fname, name, args): 240 """Outputs a variable.""" 241 242 def out_typedef(self, fname, name, args): 243 """Outputs a typedef.""" 244 245 def out_struct(self, fname, name, args): 246 """Outputs a struct.""" 247 248 249class RestFormat(OutputFormat): 250 """Consts and functions used by ReST output.""" 251 252 #: Highlights to be used in ReST format 253 highlights = [ 254 (type_constant, r"``\1``"), 255 (type_constant2, r"``\1``"), 256 257 # Note: need to escape () to avoid func matching later 258 (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"), 259 (type_member, r":c:type:`\1\2\3 <\1>`"), 260 (type_fp_param, r"**\1\\(\\)**"), 261 (type_fp_param2, r"**\1\\(\\)**"), 262 (type_func, r"\1()"), 263 (type_enum, r":c:type:`\1 <\2>`"), 264 (type_struct, r":c:type:`\1 <\2>`"), 265 (type_typedef, r":c:type:`\1 <\2>`"), 266 (type_union, r":c:type:`\1 <\2>`"), 267 268 # in rst this can refer to any type 269 (type_fallback, r":c:type:`\1`"), 270 (type_param_ref, r"**\1\2**") 271 ] 272 273 blankline = "\n" 274 275 #: Sphinx literal block regex. 276 sphinx_literal = KernRe(r'^[^.].*::$', cache=False) 277 278 #: Sphinx code block regex. 279 sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False) 280 281 def __init__(self): 282 """ 283 Creates class variables. 284 285 Not really mandatory, but it is a good coding style and makes 286 pylint happy. 287 """ 288 289 super().__init__() 290 self.lineprefix = "" 291 292 def print_lineno(self, ln): 293 """Outputs a line number.""" 294 295 if self.enable_lineno and ln is not None: 296 ln += 1 297 self.data += f".. LINENO {ln}\n" 298 299 def output_highlight(self, args): 300 """ 301 Outputs a C symbol that may require being converted to ReST using 302 the self.highlights variable. 303 """ 304 305 input_text = args 306 output = "" 307 in_literal = False 308 litprefix = "" 309 block = "" 310 311 for line in input_text.strip("\n").split("\n"): 312 313 # If we're in a literal block, see if we should drop out of it. 314 # Otherwise, pass the line straight through unmunged. 315 if in_literal: 316 if line.strip(): # If the line is not blank 317 # If this is the first non-blank line in a literal block, 318 # figure out the proper indent. 319 if not litprefix: 320 r = KernRe(r'^(\s*)') 321 if r.match(line): 322 litprefix = '^' + r.group(1) 323 else: 324 litprefix = "" 325 326 output += line + "\n" 327 elif not KernRe(litprefix).match(line): 328 in_literal = False 329 else: 330 output += line + "\n" 331 else: 332 output += line + "\n" 333 334 # Not in a literal block (or just dropped out) 335 if not in_literal: 336 block += line + "\n" 337 if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line): 338 in_literal = True 339 litprefix = "" 340 output += self.highlight_block(block) 341 block = "" 342 343 # Handle any remaining block 344 if block: 345 output += self.highlight_block(block) 346 347 # Print the output with the line prefix 348 for line in output.strip("\n").split("\n"): 349 self.data += self.lineprefix + line + "\n" 350 351 def out_section(self, args, out_docblock=False): 352 """ 353 Outputs a block section. 354 355 This could use some work; it's used to output the DOC: sections, and 356 starts by putting out the name of the doc section itself, but that 357 tends to duplicate a header already in the template file. 358 """ 359 for section, text in args.sections.items(): 360 # Skip sections that are in the nosymbol_table 361 if section in self.nosymbol: 362 continue 363 364 if out_docblock: 365 if not self.out_mode == self.OUTPUT_INCLUDE: 366 self.data += f".. _{section}:\n\n" 367 self.data += f'{self.lineprefix}**{section}**\n\n' 368 else: 369 self.data += f'{self.lineprefix}**{section}**\n\n' 370 371 self.print_lineno(args.section_start_lines.get(section, 0)) 372 self.output_highlight(text) 373 self.data += "\n" 374 self.data += "\n" 375 376 def out_doc(self, fname, name, args): 377 if not self.check_doc(name, args): 378 return 379 self.out_section(args, out_docblock=True) 380 381 def out_function(self, fname, name, args): 382 383 oldprefix = self.lineprefix 384 signature = "" 385 386 func_macro = args.get('func_macro', False) 387 if func_macro: 388 signature = name 389 else: 390 if args.get('functiontype'): 391 signature = args['functiontype'] + " " 392 signature += name + " (" 393 394 ln = args.declaration_start_line 395 count = 0 396 for parameter in args.parameterlist: 397 if count != 0: 398 signature += ", " 399 count += 1 400 dtype = args.parametertypes.get(parameter, "") 401 402 if function_pointer.search(dtype): 403 signature += function_pointer.group(1) + parameter + function_pointer.group(3) 404 else: 405 signature += dtype 406 407 if not func_macro: 408 signature += ")" 409 410 self.print_lineno(ln) 411 if args.get('typedef') or not args.get('functiontype'): 412 self.data += f".. c:macro:: {name}\n\n" 413 414 if args.get('typedef'): 415 self.data += " **Typedef**: " 416 self.lineprefix = "" 417 self.output_highlight(args.get('purpose', "")) 418 self.data += "\n\n**Syntax**\n\n" 419 self.data += f" ``{signature}``\n\n" 420 else: 421 self.data += f"``{signature}``\n\n" 422 else: 423 self.data += f".. c:function:: {signature}\n\n" 424 425 if not args.get('typedef'): 426 self.print_lineno(ln) 427 self.lineprefix = " " 428 self.output_highlight(args.get('purpose', "")) 429 self.data += "\n" 430 431 # Put descriptive text into a container (HTML <div>) to help set 432 # function prototypes apart 433 self.lineprefix = " " 434 435 if args.parameterlist: 436 self.data += ".. container:: kernelindent\n\n" 437 self.data += f"{self.lineprefix}**Parameters**\n\n" 438 439 for parameter in args.parameterlist: 440 parameter_name = KernRe(r'\[.*').sub('', parameter) 441 dtype = args.parametertypes.get(parameter, "") 442 443 if dtype: 444 self.data += f"{self.lineprefix}``{dtype}``\n" 445 else: 446 self.data += f"{self.lineprefix}``{parameter}``\n" 447 448 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 449 450 self.lineprefix = " " 451 if parameter_name in args.parameterdescs and \ 452 args.parameterdescs[parameter_name] != KernelDoc.undescribed: 453 454 self.output_highlight(args.parameterdescs[parameter_name]) 455 self.data += "\n" 456 else: 457 self.data += f"{self.lineprefix}*undescribed*\n\n" 458 self.lineprefix = " " 459 460 self.out_section(args) 461 self.lineprefix = oldprefix 462 463 def out_enum(self, fname, name, args): 464 465 oldprefix = self.lineprefix 466 ln = args.declaration_start_line 467 468 self.data += f"\n\n.. c:enum:: {name}\n\n" 469 470 self.print_lineno(ln) 471 self.lineprefix = " " 472 self.output_highlight(args.get('purpose', '')) 473 self.data += "\n" 474 475 self.data += ".. container:: kernelindent\n\n" 476 outer = self.lineprefix + " " 477 self.lineprefix = outer + " " 478 self.data += f"{outer}**Constants**\n\n" 479 480 for parameter in args.parameterlist: 481 self.data += f"{outer}``{parameter}``\n" 482 483 if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed: 484 self.output_highlight(args.parameterdescs[parameter]) 485 else: 486 self.data += f"{self.lineprefix}*undescribed*\n\n" 487 self.data += "\n" 488 489 self.lineprefix = oldprefix 490 self.out_section(args) 491 492 def out_var(self, fname, name, args): 493 oldprefix = self.lineprefix 494 ln = args.declaration_start_line 495 full_proto = args.other_stuff["full_proto"] 496 497 self.lineprefix = " " 498 499 self.data += f"\n\n.. c:macro:: {name}\n\n{self.lineprefix}``{full_proto}``\n\n" 500 501 self.print_lineno(ln) 502 self.output_highlight(args.get('purpose', '')) 503 self.data += "\n" 504 505 if args.other_stuff["default_val"]: 506 self.data += f'{self.lineprefix}**Initialization**\n\n' 507 self.output_highlight(f'default: ``{args.other_stuff["default_val"]}``') 508 509 self.out_section(args) 510 511 def out_typedef(self, fname, name, args): 512 513 oldprefix = self.lineprefix 514 ln = args.declaration_start_line 515 516 self.data += f"\n\n.. c:type:: {name}\n\n" 517 518 self.print_lineno(ln) 519 self.lineprefix = " " 520 521 self.output_highlight(args.get('purpose', '')) 522 523 self.data += "\n" 524 525 self.lineprefix = oldprefix 526 self.out_section(args) 527 528 def out_struct(self, fname, name, args): 529 530 purpose = args.get('purpose', "") 531 declaration = args.get('definition', "") 532 dtype = args.type 533 ln = args.declaration_start_line 534 535 self.data += f"\n\n.. c:{dtype}:: {name}\n\n" 536 537 self.print_lineno(ln) 538 539 oldprefix = self.lineprefix 540 self.lineprefix += " " 541 542 self.output_highlight(purpose) 543 self.data += "\n" 544 545 self.data += ".. container:: kernelindent\n\n" 546 self.data += f"{self.lineprefix}**Definition**::\n\n" 547 548 self.lineprefix = self.lineprefix + " " 549 550 declaration = declaration.replace("\t", self.lineprefix) 551 552 self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" 553 self.data += f"{declaration}{self.lineprefix}" + "};\n\n" 554 555 self.lineprefix = " " 556 self.data += f"{self.lineprefix}**Members**\n\n" 557 for parameter in args.parameterlist: 558 if not parameter or parameter.startswith("#"): 559 continue 560 561 parameter_name = parameter.split("[", maxsplit=1)[0] 562 563 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 564 continue 565 566 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 567 568 self.data += f"{self.lineprefix}``{parameter}``\n" 569 570 self.lineprefix = " " 571 self.output_highlight(args.parameterdescs[parameter_name]) 572 self.lineprefix = " " 573 574 self.data += "\n" 575 576 self.data += "\n" 577 578 self.lineprefix = oldprefix 579 self.out_section(args) 580 581 582class ManFormat(OutputFormat): 583 """ 584 Consts and functions used by man pages output. 585 586 This class has one mandatory parameter and some optional ones, which 587 are needed to define the title header contents: 588 589 ``modulename`` 590 Defines the module name to be used at the troff ``.TH`` output. 591 592 This argument is optional. If not specified, it will be filled 593 with the directory which contains the documented file. 594 595 ``section`` 596 Usually a numeric value from 0 to 9, but man pages also accept 597 some strings like "p". 598 599 Defauls to ``9`` 600 601 ``manual`` 602 Defaults to ``Kernel API Manual``. 603 604 The above controls the output of teh corresponding fields on troff 605 title headers, which will be filled like this:: 606 607 .TH "{name}" {section} "{date}" "{modulename}" "{manual}" 608 609 where ``name``` will match the API symbol name, and ``date`` will be 610 either the date where the Kernel was compiled or the current date 611 """ 612 613 highlights = ( 614 (type_constant, r"\1"), 615 (type_constant2, r"\1"), 616 (type_func, r"\\fB\1\\fP"), 617 (type_enum, r"\\fI\1\\fP"), 618 (type_struct, r"\\fI\1\\fP"), 619 (type_typedef, r"\\fI\1\\fP"), 620 (type_union, r"\\fI\1\\fP"), 621 (type_param, r"\\fI\1\\fP"), 622 (type_param_ref, r"\\fI\1\2\\fP"), 623 (type_member, r"\\fI\1\2\3\\fP"), 624 (type_fallback, r"\\fI\1\\fP") 625 ) 626 blankline = "" 627 628 #: Allowed timestamp formats. 629 date_formats = [ 630 "%a %b %d %H:%M:%S %Z %Y", 631 "%a %b %d %H:%M:%S %Y", 632 "%Y-%m-%d", 633 "%b %d %Y", 634 "%B %d %Y", 635 "%m %d %Y", 636 ] 637 638 def modulename(self, args): 639 if self._modulename: 640 return self._modulename 641 642 return os.path.dirname(args.fname) 643 644 def emit_th(self, name, args): 645 """Emit a title header line.""" 646 title = name.strip() 647 module = self.modulename(args) 648 649 self.data += f'.TH "{title}" {self.section} "{self.date}" ' 650 self.data += f'"{module}" "{self.manual}"\n' 651 652 def __init__(self, modulename=None, section="9", manual="Kernel API Manual"): 653 """ 654 Creates class variables. 655 656 Not really mandatory, but it is a good coding style and makes 657 pylint happy. 658 """ 659 660 super().__init__() 661 662 self._modulename = modulename 663 self.section = section 664 self.manual = manual 665 666 self.symbols = [] 667 668 dt = None 669 tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") 670 if tstamp: 671 for fmt in self.date_formats: 672 try: 673 dt = datetime.strptime(tstamp, fmt) 674 break 675 except ValueError: 676 pass 677 678 if not dt: 679 dt = datetime.now() 680 681 self.date = dt.strftime("%B %Y") 682 683 def arg_name(self, args, name): 684 """ 685 Return the name that will be used for the man page. 686 687 As we may have the same name on different namespaces, 688 prepend the data type for all types except functions and typedefs. 689 690 The doc section is special: it uses the modulename. 691 """ 692 693 dtype = args.type 694 695 if dtype == "doc": 696 return name 697# return os.path.basename(self.modulename(args)) 698 699 if dtype in ["function", "typedef"]: 700 return name 701 702 return f"{dtype} {name}" 703 704 def set_symbols(self, symbols): 705 """ 706 Get a list of all symbols from kernel_doc. 707 708 Man pages will uses it to add a SEE ALSO section with other 709 symbols at the same file. 710 """ 711 self.symbols = symbols 712 713 def out_tail(self, fname, name, args): 714 """Adds a tail for all man pages.""" 715 716 # SEE ALSO section 717 self.data += f'.SH "SEE ALSO"' + "\n.PP\n" 718 self.data += (f"Kernel file \\fB{args.fname}\\fR\n") 719 if len(self.symbols) >= 2: 720 cur_name = self.arg_name(args, name) 721 722 related = [] 723 for arg in self.symbols: 724 out_name = self.arg_name(arg, arg.name) 725 726 if cur_name == out_name: 727 continue 728 729 related.append(f"\\fB{out_name}\\fR(9)") 730 731 self.data += ",\n".join(related) + "\n" 732 733 # TODO: does it make sense to add other sections? Maybe 734 # REPORTING ISSUES? LICENSE? 735 736 def msg(self, fname, name, args): 737 """ 738 Handles a single entry from kernel-doc parser. 739 740 Add a tail at the end of man pages output. 741 """ 742 super().msg(fname, name, args) 743 self.out_tail(fname, name, args) 744 745 return self.data 746 747 def emit_table(self, colspec_row, rows): 748 749 if not rows: 750 return "" 751 752 out = "" 753 colspec = "\t".join(["l"] * len(rows[0])) 754 755 out += "\n.TS\n" 756 out += "box;\n" 757 out += f"{colspec}.\n" 758 759 if colspec_row: 760 out_row = [] 761 762 for text in colspec_row: 763 out_row.append(f"\\fB{text}\\fP") 764 765 out += "\t".join(out_row) + "\n_\n" 766 767 for r in rows: 768 out += "\t".join(r) + "\n" 769 770 out += ".TE\n" 771 772 return out 773 774 def grid_table(self, lines, start): 775 """ 776 Ancillary function to help handling a grid table inside the text. 777 """ 778 779 i = start + 1 780 rows = [] 781 colspec_row = None 782 783 while i < len(lines): 784 line = lines[i] 785 786 if KernRe(r"^\s*\|.*\|\s*$").match(line): 787 parts = [] 788 789 for p in line.strip('|').split('|'): 790 parts.append(p.strip()) 791 792 rows.append(parts) 793 794 elif KernRe(r'^\+\=[\+\=]+\+\s*$').match(line): 795 if rows and rows[0]: 796 if not colspec_row: 797 colspec_row = [""] * len(rows[0]) 798 799 for j in range(0, len(rows[0])): 800 content = [] 801 for row in rows: 802 content.append(row[j]) 803 804 colspec_row[j] = " ".join(content) 805 806 rows = [] 807 808 elif KernRe(r"^\s*\+[-+]+\+.*$").match(line): 809 pass 810 811 else: 812 break 813 814 i += 1 815 816 return i, self.emit_table(colspec_row, rows) 817 818 def simple_table(self, lines, start): 819 """ 820 Ancillary function to help handling a simple table inside the text. 821 """ 822 823 i = start 824 rows = [] 825 colspec_row = None 826 827 pos = [] 828 for m in KernRe(r'\-+').finditer(lines[i]): 829 pos.append((m.start(), m.end() - 1)) 830 831 i += 1 832 while i < len(lines): 833 line = lines[i] 834 835 if KernRe(r"^\s*[\-]+[ \t\-]+$").match(line): 836 i += 1 837 break 838 839 elif KernRe(r'^[\s=]+$').match(line): 840 if rows and rows[0]: 841 if not colspec_row: 842 colspec_row = [""] * len(rows[0]) 843 844 for j in range(0, len(rows[0])): 845 content = [] 846 for row in rows: 847 content.append(row[j]) 848 849 colspec_row[j] = " ".join(content) 850 851 rows = [] 852 853 else: 854 row = [""] * len(pos) 855 856 for j in range(0, len(pos)): 857 start, end = pos[j] 858 859 row[j] = line[start:end].strip() 860 861 rows.append(row) 862 863 i += 1 864 865 return i, self.emit_table(colspec_row, rows) 866 867 def code_block(self, lines, start): 868 """ 869 Ensure that code blocks won't be messed up at the output. 870 871 By default, troff join lines at the same paragraph. Disable it, 872 on code blocks. 873 """ 874 875 line = lines[start] 876 877 if "code-block" in line: 878 out = "\n.nf\n" 879 elif line.startswith("..") and line.endswith("::"): 880 # 881 # Handle note, warning, error, ... markups 882 # 883 line = line[2:-1].strip().upper() 884 out = f"\n.nf\n\\fB{line}\\fP\n" 885 elif line.endswith("::"): 886 out = line[:-1] 887 out += "\n.nf\n" 888 else: 889 # Just in case. Should never happen in practice 890 out = "\n.nf\n" 891 892 i = start + 1 893 ident = None 894 895 while i < len(lines): 896 line = lines[i] 897 898 m = KernRe(r"\S").match(line) 899 if not m: 900 out += line + "\n" 901 i += 1 902 continue 903 904 pos = m.start() 905 if not ident: 906 if pos > 0: 907 ident = pos 908 else: 909 out += "\n.fi\n" 910 if i > start + 1: 911 return i - 1, out 912 else: 913 # Just in case. Should never happen in practice 914 return i, out 915 916 if pos >= ident: 917 out += line + "\n" 918 i += 1 919 continue 920 921 break 922 923 out += "\n.fi\n" 924 return i, out 925 926 def output_highlight(self, block): 927 """ 928 Outputs a C symbol that may require being highlighted with 929 self.highlights variable using troff syntax. 930 """ 931 932 contents = self.highlight_block(block) 933 934 if isinstance(contents, list): 935 contents = "\n".join(contents) 936 937 lines = contents.strip("\n").split("\n") 938 i = 0 939 940 while i < len(lines): 941 org_line = lines[i] 942 943 line = KernRe(r"^\s*").sub("", org_line) 944 945 if line: 946 if KernRe(r"^\+\-[-+]+\+.*$").match(line): 947 i, text = self.grid_table(lines, i) 948 self.data += text 949 continue 950 951 if KernRe(r"^\-+[ \t]\-[ \t\-]+$").match(line): 952 i, text = self.simple_table(lines, i) 953 self.data += text 954 continue 955 956 if line.endswith("::") or KernRe(r"\.\.\s+code-block.*::").match(line): 957 i, text = self.code_block(lines, i) 958 self.data += text 959 continue 960 961 if line[0] == ".": 962 self.data += "\\&" + line + "\n" 963 i += 1 964 continue 965 966 # 967 # Handle lists 968 # 969 line = KernRe(r'^[-*]\s+').sub(r'.IP \[bu]\n', line) 970 line = KernRe(r'^(\d+|a-z)[\.\)]\s+').sub(r'.IP \1\n', line) 971 else: 972 line = ".PP\n" 973 974 i += 1 975 976 self.data += line + "\n" 977 978 def out_doc(self, fname, name, args): 979 if not self.check_doc(name, args): 980 return 981 982 out_name = self.arg_name(args, name) 983 984 self.emit_th(out_name, args) 985 986 for section, text in args.sections.items(): 987 self.data += f'.SH "{section}"' + "\n" 988 self.output_highlight(text) 989 990 def out_function(self, fname, name, args): 991 992 out_name = self.arg_name(args, name) 993 994 self.emit_th(out_name, args) 995 996 self.data += ".SH NAME\n" 997 self.data += f"{name} \\- {args['purpose']}\n" 998 999 self.data += ".SH SYNOPSIS\n" 1000 if args.get('functiontype', ''): 1001 self.data += f'.B "{args["functiontype"]}" {name}' + "\n" 1002 else: 1003 self.data += f'.B "{name}' + "\n" 1004 1005 count = 0 1006 parenth = "(" 1007 post = "," 1008 1009 for parameter in args.parameterlist: 1010 if count == len(args.parameterlist) - 1: 1011 post = ");" 1012 1013 dtype = args.parametertypes.get(parameter, "") 1014 if function_pointer.match(dtype): 1015 # Pointer-to-function 1016 self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" 1017 else: 1018 dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype) 1019 1020 self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" 1021 count += 1 1022 parenth = "" 1023 1024 if args.parameterlist: 1025 self.data += ".SH ARGUMENTS\n" 1026 1027 for parameter in args.parameterlist: 1028 parameter_name = re.sub(r'\[.*', '', parameter) 1029 1030 self.data += f'.IP "{parameter}" 12' + "\n" 1031 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 1032 1033 for section, text in args.sections.items(): 1034 self.data += f'.SH "{section.upper()}"' + "\n" 1035 self.output_highlight(text) 1036 1037 def out_enum(self, fname, name, args): 1038 out_name = self.arg_name(args, name) 1039 1040 self.emit_th(out_name, args) 1041 1042 self.data += ".SH NAME\n" 1043 self.data += f"enum {name} \\- {args['purpose']}\n" 1044 1045 self.data += ".SH SYNOPSIS\n" 1046 self.data += f"enum {name}" + " {\n" 1047 1048 count = 0 1049 for parameter in args.parameterlist: 1050 self.data += f'.br\n.BI " {parameter}"' + "\n" 1051 if count == len(args.parameterlist) - 1: 1052 self.data += "\n};\n" 1053 else: 1054 self.data += ", \n.br\n" 1055 1056 count += 1 1057 1058 self.data += ".SH Constants\n" 1059 1060 for parameter in args.parameterlist: 1061 parameter_name = KernRe(r'\[.*').sub('', parameter) 1062 self.data += f'.IP "{parameter}" 12' + "\n" 1063 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 1064 1065 for section, text in args.sections.items(): 1066 self.data += f'.SH "{section}"' + "\n" 1067 self.output_highlight(text) 1068 1069 def out_var(self, fname, name, args): 1070 out_name = self.arg_name(args, name) 1071 full_proto = args.other_stuff["full_proto"] 1072 1073 self.emit_th(out_name, args) 1074 1075 self.data += ".SH NAME\n" 1076 self.data += f"{name} \\- {args['purpose']}\n" 1077 1078 self.data += ".SH SYNOPSIS\n" 1079 self.data += f"{full_proto}\n" 1080 1081 if args.other_stuff["default_val"]: 1082 self.data += f'.SH "Initialization"' + "\n" 1083 self.output_highlight(f'default: {args.other_stuff["default_val"]}') 1084 1085 for section, text in args.sections.items(): 1086 self.data += f'.SH "{section}"' + "\n" 1087 self.output_highlight(text) 1088 1089 def out_typedef(self, fname, name, args): 1090 module = self.modulename(args) 1091 purpose = args.get('purpose') 1092 out_name = self.arg_name(args, name) 1093 1094 self.emit_th(out_name, args) 1095 1096 self.data += ".SH NAME\n" 1097 self.data += f"typedef {name} \\- {purpose}\n" 1098 1099 for section, text in args.sections.items(): 1100 self.data += f'.SH "{section}"' + "\n" 1101 self.output_highlight(text) 1102 1103 def out_struct(self, fname, name, args): 1104 module = self.modulename(args) 1105 purpose = args.get('purpose') 1106 definition = args.get('definition') 1107 out_name = self.arg_name(args, name) 1108 1109 self.emit_th(out_name, args) 1110 1111 self.data += ".SH NAME\n" 1112 self.data += f"{args.type} {name} \\- {purpose}\n" 1113 1114 # Replace tabs with two spaces and handle newlines 1115 declaration = definition.replace("\t", " ") 1116 declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) 1117 1118 self.data += ".SH SYNOPSIS\n" 1119 self.data += f"{args.type} {name} " + "{" + "\n.br\n" 1120 self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" 1121 1122 self.data += ".SH Members\n" 1123 for parameter in args.parameterlist: 1124 if parameter.startswith("#"): 1125 continue 1126 1127 parameter_name = re.sub(r"\[.*", "", parameter) 1128 1129 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 1130 continue 1131 1132 self.data += f'.IP "{parameter}" 12' + "\n" 1133 self.output_highlight(args.parameterdescs.get(parameter_name)) 1134 1135 for section, text in args.sections.items(): 1136 self.data += f'.SH "{section}"' + "\n" 1137 self.output_highlight(text) 1138