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["full_proto"] 517 518 self.lineprefix = " " 519 520 self.data += f"\n\n.. c:macro:: {name}\n\n{self.lineprefix}``{full_proto}``\n\n" 521 522 self.print_lineno(ln) 523 self.output_highlight(args.get('purpose', '')) 524 self.data += "\n" 525 526 if args.other_stuff["default_val"]: 527 self.data += f'{self.lineprefix}**Initialization**\n\n' 528 self.output_highlight(f'default: ``{args.other_stuff["default_val"]}``') 529 530 self.out_section(args) 531 532 def out_typedef(self, fname, name, args): 533 534 oldprefix = self.lineprefix 535 ln = args.declaration_start_line 536 537 self.data += f"\n\n.. c:type:: {name}\n\n" 538 539 self.print_lineno(ln) 540 self.lineprefix = " " 541 542 self.output_highlight(args.get('purpose', '')) 543 544 self.data += "\n" 545 546 self.lineprefix = oldprefix 547 self.out_section(args) 548 549 def out_struct(self, fname, name, args): 550 551 purpose = args.get('purpose', "") 552 declaration = args.get('definition', "") 553 dtype = args.type 554 ln = args.declaration_start_line 555 556 self.data += f"\n\n.. c:{dtype}:: {name}\n\n" 557 558 self.print_lineno(ln) 559 560 oldprefix = self.lineprefix 561 self.lineprefix += " " 562 563 self.output_highlight(purpose) 564 self.data += "\n" 565 566 self.data += ".. container:: kernelindent\n\n" 567 self.data += f"{self.lineprefix}**Definition**::\n\n" 568 569 self.lineprefix = self.lineprefix + " " 570 571 declaration = declaration.replace("\t", self.lineprefix) 572 573 self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" 574 self.data += f"{declaration}{self.lineprefix}" + "};\n\n" 575 576 self.lineprefix = " " 577 self.data += f"{self.lineprefix}**Members**\n\n" 578 for parameter in args.parameterlist: 579 if not parameter or parameter.startswith("#"): 580 continue 581 582 parameter_name = parameter.split("[", maxsplit=1)[0] 583 584 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 585 continue 586 587 self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0)) 588 589 self.data += f"{self.lineprefix}``{parameter}``\n" 590 591 self.lineprefix = " " 592 self.output_highlight(args.parameterdescs[parameter_name]) 593 self.lineprefix = " " 594 595 self.data += "\n" 596 597 self.data += "\n" 598 599 self.lineprefix = oldprefix 600 self.out_section(args) 601 602 603class ManFormat(OutputFormat): 604 """ 605 Consts and functions used by man pages output. 606 607 This class has one mandatory parameter and some optional ones, which 608 are needed to define the title header contents: 609 610 ``modulename`` 611 Defines the module name to be used at the troff ``.TH`` output. 612 613 This argument is optional. If not specified, it will be filled 614 with the directory which contains the documented file. 615 616 ``section`` 617 Usually a numeric value from 0 to 9, but man pages also accept 618 some strings like "p". 619 620 Defauls to ``9`` 621 622 ``manual`` 623 Defaults to ``Kernel API Manual``. 624 625 The above controls the output of teh corresponding fields on troff 626 title headers, which will be filled like this:: 627 628 .TH "{name}" {section} "{date}" "{modulename}" "{manual}" 629 630 where ``name``` will match the API symbol name, and ``date`` will be 631 either the date where the Kernel was compiled or the current date 632 """ 633 634 highlights = ( 635 (type_constant, r"\1"), 636 (type_constant2, r"\1"), 637 (type_func, r"\\fB\1\\fP"), 638 (type_enum, r"\\fI\1\\fP"), 639 (type_struct, r"\\fI\1\\fP"), 640 (type_typedef, r"\\fI\1\\fP"), 641 (type_union, r"\\fI\1\\fP"), 642 (type_param, r"\\fI\1\\fP"), 643 (type_param_ref, r"\\fI\1\2\\fP"), 644 (type_member, r"\\fI\1\2\3\\fP"), 645 (type_fallback, r"\\fI\1\\fP") 646 ) 647 blankline = "" 648 649 #: Allowed timestamp formats. 650 date_formats = [ 651 "%a %b %d %H:%M:%S %Z %Y", 652 "%a %b %d %H:%M:%S %Y", 653 "%Y-%m-%d", 654 "%b %d %Y", 655 "%B %d %Y", 656 "%m %d %Y", 657 ] 658 659 def modulename(self, args): 660 if self._modulename: 661 return self._modulename 662 663 return os.path.dirname(args.fname) 664 665 def emit_th(self, name, args): 666 """Emit a title header line.""" 667 title = name.strip() 668 module = self.modulename(args) 669 670 self.data += f'.TH "{title}" {self.section} "{self.date}" ' 671 self.data += f'"{module}" "{self.manual}"\n' 672 673 def __init__(self, modulename=None, section="9", manual="Kernel API Manual"): 674 """ 675 Creates class variables. 676 677 Not really mandatory, but it is a good coding style and makes 678 pylint happy. 679 """ 680 681 super().__init__() 682 683 self._modulename = modulename 684 self.section = section 685 self.manual = manual 686 687 self.symbols = [] 688 689 dt = None 690 tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") 691 if tstamp: 692 for fmt in self.date_formats: 693 try: 694 dt = datetime.strptime(tstamp, fmt) 695 break 696 except ValueError: 697 pass 698 699 if not dt: 700 dt = datetime.now() 701 702 self.date = dt.strftime("%B %Y") 703 704 def arg_name(self, args, name): 705 """ 706 Return the name that will be used for the man page. 707 708 As we may have the same name on different namespaces, 709 prepend the data type for all types except functions and typedefs. 710 711 The doc section is special: it uses the modulename. 712 """ 713 714 dtype = args.type 715 716 if dtype == "doc": 717 return name 718# return os.path.basename(self.modulename(args)) 719 720 if dtype in ["function", "typedef"]: 721 return name 722 723 return f"{dtype} {name}" 724 725 def set_symbols(self, symbols): 726 """ 727 Get a list of all symbols from kernel_doc. 728 729 Man pages will uses it to add a SEE ALSO section with other 730 symbols at the same file. 731 """ 732 self.symbols = symbols 733 734 def out_tail(self, fname, name, args): 735 """Adds a tail for all man pages.""" 736 737 # SEE ALSO section 738 self.data += f'.SH "SEE ALSO"' + "\n.PP\n" 739 self.data += (f"Kernel file \\fB{args.fname}\\fR\n") 740 if len(self.symbols) >= 2: 741 cur_name = self.arg_name(args, name) 742 743 related = [] 744 for arg in self.symbols: 745 out_name = self.arg_name(arg, arg.name) 746 747 if cur_name == out_name: 748 continue 749 750 related.append(f"\\fB{out_name}\\fR(9)") 751 752 self.data += ",\n".join(related) + "\n" 753 754 # TODO: does it make sense to add other sections? Maybe 755 # REPORTING ISSUES? LICENSE? 756 757 def msg(self, fname, name, args): 758 """ 759 Handles a single entry from kernel-doc parser. 760 761 Add a tail at the end of man pages output. 762 """ 763 super().msg(fname, name, args) 764 self.out_tail(fname, name, args) 765 766 return self.data 767 768 def emit_table(self, colspec_row, rows): 769 770 if not rows: 771 return "" 772 773 out = "" 774 colspec = "\t".join(["l"] * len(rows[0])) 775 776 out += "\n.TS\n" 777 out += "box;\n" 778 out += f"{colspec}.\n" 779 780 if colspec_row: 781 out_row = [] 782 783 for text in colspec_row: 784 out_row.append(f"\\fB{text}\\fP") 785 786 out += "\t".join(out_row) + "\n_\n" 787 788 for r in rows: 789 out += "\t".join(r) + "\n" 790 791 out += ".TE\n" 792 793 return out 794 795 def grid_table(self, lines, start): 796 """ 797 Ancillary function to help handling a grid table inside the text. 798 """ 799 800 i = start + 1 801 rows = [] 802 colspec_row = None 803 804 while i < len(lines): 805 line = lines[i] 806 807 if KernRe(r"^\s*\|.*\|\s*$").match(line): 808 parts = [] 809 810 for p in line.strip('|').split('|'): 811 parts.append(p.strip()) 812 813 rows.append(parts) 814 815 elif KernRe(r'^\+\=[\+\=]+\+\s*$').match(line): 816 if rows and rows[0]: 817 if not colspec_row: 818 colspec_row = [""] * len(rows[0]) 819 820 for j in range(0, len(rows[0])): 821 content = [] 822 for row in rows: 823 content.append(row[j]) 824 825 colspec_row[j] = " ".join(content) 826 827 rows = [] 828 829 elif KernRe(r"^\s*\+[-+]+\+.*$").match(line): 830 pass 831 832 else: 833 break 834 835 i += 1 836 837 return i, self.emit_table(colspec_row, rows) 838 839 def simple_table(self, lines, start): 840 """ 841 Ancillary function to help handling a simple table inside the text. 842 """ 843 844 i = start 845 rows = [] 846 colspec_row = None 847 848 pos = [] 849 for m in KernRe(r'\-+').finditer(lines[i]): 850 pos.append((m.start(), m.end() - 1)) 851 852 i += 1 853 while i < len(lines): 854 line = lines[i] 855 856 if KernRe(r"^\s*[\-]+[ \t\-]+$").match(line): 857 i += 1 858 break 859 860 elif KernRe(r'^[\s=]+$').match(line): 861 if rows and rows[0]: 862 if not colspec_row: 863 colspec_row = [""] * len(rows[0]) 864 865 for j in range(0, len(rows[0])): 866 content = [] 867 for row in rows: 868 content.append(row[j]) 869 870 colspec_row[j] = " ".join(content) 871 872 rows = [] 873 874 else: 875 row = [""] * len(pos) 876 877 for j in range(0, len(pos)): 878 start, end = pos[j] 879 880 row[j] = line[start:end].strip() 881 882 rows.append(row) 883 884 i += 1 885 886 return i, self.emit_table(colspec_row, rows) 887 888 def code_block(self, lines, start): 889 """ 890 Ensure that code blocks won't be messed up at the output. 891 892 By default, troff join lines at the same paragraph. Disable it, 893 on code blocks. 894 """ 895 896 line = lines[start] 897 898 if "code-block" in line: 899 out = "\n.nf\n" 900 elif line.startswith("..") and line.endswith("::"): 901 # 902 # Handle note, warning, error, ... markups 903 # 904 line = line[2:-1].strip().upper() 905 out = f"\n.nf\n\\fB{line}\\fP\n" 906 elif line.endswith("::"): 907 out = line[:-1] 908 out += "\n.nf\n" 909 else: 910 # Just in case. Should never happen in practice 911 out = "\n.nf\n" 912 913 i = start + 1 914 ident = None 915 916 while i < len(lines): 917 line = lines[i] 918 919 m = KernRe(r"\S").match(line) 920 if not m: 921 out += line + "\n" 922 i += 1 923 continue 924 925 pos = m.start() 926 if not ident: 927 if pos > 0: 928 ident = pos 929 else: 930 out += "\n.fi\n" 931 if i > start + 1: 932 return i - 1, out 933 else: 934 # Just in case. Should never happen in practice 935 return i, out 936 937 if pos >= ident: 938 out += line + "\n" 939 i += 1 940 continue 941 942 break 943 944 out += "\n.fi\n" 945 return i, out 946 947 def output_highlight(self, block): 948 """ 949 Outputs a C symbol that may require being highlighted with 950 self.highlights variable using troff syntax. 951 """ 952 953 contents = self.highlight_block(block) 954 955 if isinstance(contents, list): 956 contents = "\n".join(contents) 957 958 lines = contents.strip("\n").split("\n") 959 i = 0 960 961 while i < len(lines): 962 org_line = lines[i] 963 964 line = KernRe(r"^\s*").sub("", org_line) 965 966 if line: 967 if KernRe(r"^\+\-[-+]+\+.*$").match(line): 968 i, text = self.grid_table(lines, i) 969 self.data += text 970 continue 971 972 if KernRe(r"^\-+[ \t]\-[ \t\-]+$").match(line): 973 i, text = self.simple_table(lines, i) 974 self.data += text 975 continue 976 977 if line.endswith("::") or KernRe(r"\.\.\s+code-block.*::").match(line): 978 i, text = self.code_block(lines, i) 979 self.data += text 980 continue 981 982 if line[0] == ".": 983 self.data += "\\&" + line + "\n" 984 i += 1 985 continue 986 987 # 988 # Handle lists 989 # 990 line = KernRe(r'^[-*]\s+').sub(r'.IP \[bu]\n', line) 991 line = KernRe(r'^(\d+|a-z)[\.\)]\s+').sub(r'.IP \1\n', line) 992 else: 993 line = ".PP\n" 994 995 i += 1 996 997 self.data += line + "\n" 998 999 def out_doc(self, fname, name, args): 1000 if not self.check_doc(name, args): 1001 return 1002 1003 out_name = self.arg_name(args, name) 1004 1005 self.emit_th(out_name, args) 1006 1007 for section, text in args.sections.items(): 1008 self.data += f'.SH "{section}"' + "\n" 1009 self.output_highlight(text) 1010 1011 def out_function(self, fname, name, args): 1012 1013 out_name = self.arg_name(args, name) 1014 1015 self.emit_th(out_name, args) 1016 1017 self.data += ".SH NAME\n" 1018 self.data += f"{name} \\- {args['purpose']}\n" 1019 1020 self.data += ".SH SYNOPSIS\n" 1021 if args.get('functiontype', ''): 1022 self.data += f'.B "{args["functiontype"]}" {name}' + "\n" 1023 else: 1024 self.data += f'.B "{name}' + "\n" 1025 1026 count = 0 1027 parenth = "(" 1028 post = "," 1029 1030 for parameter in args.parameterlist: 1031 if count == len(args.parameterlist) - 1: 1032 post = ");" 1033 1034 dtype = args.parametertypes.get(parameter, "") 1035 if function_pointer.match(dtype): 1036 # Pointer-to-function 1037 self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" 1038 else: 1039 dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype) 1040 1041 self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" 1042 count += 1 1043 parenth = "" 1044 1045 if args.parameterlist: 1046 self.data += ".SH ARGUMENTS\n" 1047 1048 for parameter in args.parameterlist: 1049 parameter_name = re.sub(r'\[.*', '', parameter) 1050 1051 self.data += f'.IP "{parameter}" 12' + "\n" 1052 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 1053 1054 for section, text in args.sections.items(): 1055 self.data += f'.SH "{section.upper()}"' + "\n" 1056 self.output_highlight(text) 1057 1058 def out_enum(self, fname, name, args): 1059 out_name = self.arg_name(args, name) 1060 1061 self.emit_th(out_name, args) 1062 1063 self.data += ".SH NAME\n" 1064 self.data += f"enum {name} \\- {args['purpose']}\n" 1065 1066 self.data += ".SH SYNOPSIS\n" 1067 self.data += f"enum {name}" + " {\n" 1068 1069 count = 0 1070 for parameter in args.parameterlist: 1071 self.data += f'.br\n.BI " {parameter}"' + "\n" 1072 if count == len(args.parameterlist) - 1: 1073 self.data += "\n};\n" 1074 else: 1075 self.data += ", \n.br\n" 1076 1077 count += 1 1078 1079 self.data += ".SH Constants\n" 1080 1081 for parameter in args.parameterlist: 1082 parameter_name = KernRe(r'\[.*').sub('', parameter) 1083 self.data += f'.IP "{parameter}" 12' + "\n" 1084 self.output_highlight(args.parameterdescs.get(parameter_name, "")) 1085 1086 for section, text in args.sections.items(): 1087 self.data += f'.SH "{section}"' + "\n" 1088 self.output_highlight(text) 1089 1090 def out_var(self, fname, name, args): 1091 out_name = self.arg_name(args, name) 1092 full_proto = args.other_stuff["full_proto"] 1093 1094 self.emit_th(out_name, args) 1095 1096 self.data += ".SH NAME\n" 1097 self.data += f"{name} \\- {args['purpose']}\n" 1098 1099 self.data += ".SH SYNOPSIS\n" 1100 self.data += f"{full_proto}\n" 1101 1102 if args.other_stuff["default_val"]: 1103 self.data += f'.SH "Initialization"' + "\n" 1104 self.output_highlight(f'default: {args.other_stuff["default_val"]}') 1105 1106 for section, text in args.sections.items(): 1107 self.data += f'.SH "{section}"' + "\n" 1108 self.output_highlight(text) 1109 1110 def out_typedef(self, fname, name, args): 1111 module = self.modulename(args) 1112 purpose = args.get('purpose') 1113 out_name = self.arg_name(args, name) 1114 1115 self.emit_th(out_name, args) 1116 1117 self.data += ".SH NAME\n" 1118 self.data += f"typedef {name} \\- {purpose}\n" 1119 1120 for section, text in args.sections.items(): 1121 self.data += f'.SH "{section}"' + "\n" 1122 self.output_highlight(text) 1123 1124 def out_struct(self, fname, name, args): 1125 module = self.modulename(args) 1126 purpose = args.get('purpose') 1127 definition = args.get('definition') 1128 out_name = self.arg_name(args, name) 1129 1130 self.emit_th(out_name, args) 1131 1132 self.data += ".SH NAME\n" 1133 self.data += f"{args.type} {name} \\- {purpose}\n" 1134 1135 # Replace tabs with two spaces and handle newlines 1136 declaration = definition.replace("\t", " ") 1137 declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) 1138 1139 self.data += ".SH SYNOPSIS\n" 1140 self.data += f"{args.type} {name} " + "{" + "\n.br\n" 1141 self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" 1142 1143 self.data += ".SH Members\n" 1144 for parameter in args.parameterlist: 1145 if parameter.startswith("#"): 1146 continue 1147 1148 parameter_name = re.sub(r"\[.*", "", parameter) 1149 1150 if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed: 1151 continue 1152 1153 self.data += f'.IP "{parameter}" 12' + "\n" 1154 self.output_highlight(args.parameterdescs.get(parameter_name)) 1155 1156 for section, text in args.sections.items(): 1157 self.data += f'.SH "{section}"' + "\n" 1158 self.output_highlight(text) 1159