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