xref: /linux/scripts/lib/kdoc/kdoc_output.py (revision 8d7338752d76c3854a5c54cf7df976c539baab5b)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
4#
5# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917
6
7"""
8Implement output filters to print kernel-doc documentation.
9
10The implementation uses a virtual base class (OutputFormat) which
11contains a dispatches to virtual methods, and some code to filter
12out output messages.
13
14The actual implementation is done on one separate class per each type
15of output. Currently, there are output classes for ReST and man/troff.
16"""
17
18import os
19import re
20from datetime import datetime
21
22from kdoc_parser import KernelDoc, type_param
23from kdoc_re import KernRe
24
25
26function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)
27
28# match expressions used to find embedded type information
29type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)
30type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)
31type_func = KernRe(r"(\w+)\(\)", cache=False)
32type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)
33
34# Special RST handling for func ptr params
35type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)
36
37# Special RST handling for structs with func ptr params
38type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)
39
40type_env = KernRe(r"(\$\w+)", cache=False)
41type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False)
42type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False)
43type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False)
44type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False)
45type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False)
46type_fallback = KernRe(r"\&([_\w]+)", cache=False)
47type_member_func = type_member + KernRe(r"\(\)", cache=False)
48
49
50class OutputFormat:
51    """
52    Base class for OutputFormat. If used as-is, it means that only
53    warnings will be displayed.
54    """
55
56    # output mode.
57    OUTPUT_ALL          = 0 # output all symbols and doc sections
58    OUTPUT_INCLUDE      = 1 # output only specified symbols
59    OUTPUT_EXPORTED     = 2 # output exported symbols
60    OUTPUT_INTERNAL     = 3 # output non-exported symbols
61
62    # Virtual member to be overriden at the  inherited classes
63    highlights = []
64
65    def __init__(self):
66        """Declare internal vars and set mode to OUTPUT_ALL"""
67
68        self.out_mode = self.OUTPUT_ALL
69        self.enable_lineno = None
70        self.nosymbol = {}
71        self.symbol = None
72        self.function_table = None
73        self.config = None
74        self.no_doc_sections = False
75
76        self.data = ""
77
78    def set_config(self, config):
79        """
80        Setup global config variables used by both parser and output.
81        """
82
83        self.config = config
84
85    def set_filter(self, export, internal, symbol, nosymbol, function_table,
86                   enable_lineno, no_doc_sections):
87        """
88        Initialize filter variables according with the requested mode.
89
90        Only one choice is valid between export, internal and symbol.
91
92        The nosymbol filter can be used on all modes.
93        """
94
95        self.enable_lineno = enable_lineno
96        self.no_doc_sections = no_doc_sections
97        self.function_table = function_table
98
99        if symbol:
100            self.out_mode = self.OUTPUT_INCLUDE
101        elif export:
102            self.out_mode = self.OUTPUT_EXPORTED
103        elif internal:
104            self.out_mode = self.OUTPUT_INTERNAL
105        else:
106            self.out_mode = self.OUTPUT_ALL
107
108        if nosymbol:
109            self.nosymbol = set(nosymbol)
110
111
112    def highlight_block(self, block):
113        """
114        Apply the RST highlights to a sub-block of text.
115        """
116
117        for r, sub in self.highlights:
118            block = r.sub(sub, block)
119
120        return block
121
122    def out_warnings(self, args):
123        """
124        Output warnings for identifiers that will be displayed.
125        """
126
127        warnings = args.get('warnings', [])
128
129        for log_msg in warnings:
130            self.config.warning(log_msg)
131
132    def check_doc(self, name, args):
133        """Check if DOC should be output"""
134
135        if self.no_doc_sections:
136            return False
137
138        if name in self.nosymbol:
139            return False
140
141        if self.out_mode == self.OUTPUT_ALL:
142            self.out_warnings(args)
143            return True
144
145        if self.out_mode == self.OUTPUT_INCLUDE:
146            if name in self.function_table:
147                self.out_warnings(args)
148                return True
149
150        return False
151
152    def check_declaration(self, dtype, name, args):
153        """
154        Checks if a declaration should be output or not based on the
155        filtering criteria.
156        """
157
158        if name in self.nosymbol:
159            return False
160
161        if self.out_mode == self.OUTPUT_ALL:
162            self.out_warnings(args)
163            return True
164
165        if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:
166            if name in self.function_table:
167                return True
168
169        if self.out_mode == self.OUTPUT_INTERNAL:
170            if dtype != "function":
171                self.out_warnings(args)
172                return True
173
174            if name not in self.function_table:
175                self.out_warnings(args)
176                return True
177
178        return False
179
180    def msg(self, fname, name, args):
181        """
182        Handles a single entry from kernel-doc parser
183        """
184
185        self.data = ""
186
187        dtype = args.get('type', "")
188
189        if dtype == "doc":
190            self.out_doc(fname, name, args)
191            return self.data
192
193        if not self.check_declaration(dtype, name, args):
194            return self.data
195
196        if dtype == "function":
197            self.out_function(fname, name, args)
198            return self.data
199
200        if dtype == "enum":
201            self.out_enum(fname, name, args)
202            return self.data
203
204        if dtype == "typedef":
205            self.out_typedef(fname, name, args)
206            return self.data
207
208        if dtype in ["struct", "union"]:
209            self.out_struct(fname, name, args)
210            return self.data
211
212        # Warn if some type requires an output logic
213        self.config.log.warning("doesn't now how to output '%s' block",
214                                dtype)
215
216        return None
217
218    # Virtual methods to be overridden by inherited classes
219    # At the base class, those do nothing.
220    def out_doc(self, fname, name, args):
221        """Outputs a DOC block"""
222
223    def out_function(self, fname, name, args):
224        """Outputs a function"""
225
226    def out_enum(self, fname, name, args):
227        """Outputs an enum"""
228
229    def out_typedef(self, fname, name, args):
230        """Outputs a typedef"""
231
232    def out_struct(self, fname, name, args):
233        """Outputs a struct"""
234
235
236class RestFormat(OutputFormat):
237    """Consts and functions used by ReST output"""
238
239    highlights = [
240        (type_constant, r"``\1``"),
241        (type_constant2, r"``\1``"),
242
243        # Note: need to escape () to avoid func matching later
244        (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
245        (type_member, r":c:type:`\1\2\3 <\1>`"),
246        (type_fp_param, r"**\1\\(\\)**"),
247        (type_fp_param2, r"**\1\\(\\)**"),
248        (type_func, r"\1()"),
249        (type_enum, r":c:type:`\1 <\2>`"),
250        (type_struct, r":c:type:`\1 <\2>`"),
251        (type_typedef, r":c:type:`\1 <\2>`"),
252        (type_union, r":c:type:`\1 <\2>`"),
253
254        # in rst this can refer to any type
255        (type_fallback, r":c:type:`\1`"),
256        (type_param_ref, r"**\1\2**")
257    ]
258    blankline = "\n"
259
260    sphinx_literal = KernRe(r'^[^.].*::$', cache=False)
261    sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)
262
263    def __init__(self):
264        """
265        Creates class variables.
266
267        Not really mandatory, but it is a good coding style and makes
268        pylint happy.
269        """
270
271        super().__init__()
272        self.lineprefix = ""
273
274    def print_lineno(self, ln):
275        """Outputs a line number"""
276
277        if self.enable_lineno and ln is not None:
278            ln += 1
279            self.data += f".. LINENO {ln}\n"
280
281    def output_highlight(self, args):
282        """
283        Outputs a C symbol that may require being converted to ReST using
284        the self.highlights variable
285        """
286
287        input_text = args
288        output = ""
289        in_literal = False
290        litprefix = ""
291        block = ""
292
293        for line in input_text.strip("\n").split("\n"):
294
295            # If we're in a literal block, see if we should drop out of it.
296            # Otherwise, pass the line straight through unmunged.
297            if in_literal:
298                if line.strip():  # If the line is not blank
299                    # If this is the first non-blank line in a literal block,
300                    # figure out the proper indent.
301                    if not litprefix:
302                        r = KernRe(r'^(\s*)')
303                        if r.match(line):
304                            litprefix = '^' + r.group(1)
305                        else:
306                            litprefix = ""
307
308                        output += line + "\n"
309                    elif not KernRe(litprefix).match(line):
310                        in_literal = False
311                    else:
312                        output += line + "\n"
313                else:
314                    output += line + "\n"
315
316            # Not in a literal block (or just dropped out)
317            if not in_literal:
318                block += line + "\n"
319                if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
320                    in_literal = True
321                    litprefix = ""
322                    output += self.highlight_block(block)
323                    block = ""
324
325        # Handle any remaining block
326        if block:
327            output += self.highlight_block(block)
328
329        # Print the output with the line prefix
330        for line in output.strip("\n").split("\n"):
331            self.data += self.lineprefix + line + "\n"
332
333    def out_section(self, args, out_docblock=False):
334        """
335        Outputs a block section.
336
337        This could use some work; it's used to output the DOC: sections, and
338        starts by putting out the name of the doc section itself, but that
339        tends to duplicate a header already in the template file.
340        """
341        for section, text in args.sections.items():
342            # Skip sections that are in the nosymbol_table
343            if section in self.nosymbol:
344                continue
345
346            if out_docblock:
347                if not self.out_mode == self.OUTPUT_INCLUDE:
348                    self.data += f".. _{section}:\n\n"
349                    self.data += f'{self.lineprefix}**{section}**\n\n'
350            else:
351                self.data += f'{self.lineprefix}**{section}**\n\n'
352
353            self.print_lineno(args.section_start_lines.get(section, 0))
354            self.output_highlight(text)
355            self.data += "\n"
356        self.data += "\n"
357
358    def out_doc(self, fname, name, args):
359        if not self.check_doc(name, args):
360            return
361        self.out_section(args, out_docblock=True)
362
363    def out_function(self, fname, name, args):
364
365        oldprefix = self.lineprefix
366        signature = ""
367
368        func_macro = args.get('func_macro', False)
369        if func_macro:
370            signature = args['function']
371        else:
372            if args.get('functiontype'):
373                signature = args['functiontype'] + " "
374            signature += args['function'] + " ("
375
376        parameterlist = args.get('parameterlist', [])
377        parameterdescs = args.get('parameterdescs', {})
378        parameterdesc_start_lines = args.get('parameterdesc_start_lines', {})
379
380        ln = args.get('declaration_start_line', 0)
381
382        count = 0
383        for parameter in parameterlist:
384            if count != 0:
385                signature += ", "
386            count += 1
387            dtype = args['parametertypes'].get(parameter, "")
388
389            if function_pointer.search(dtype):
390                signature += function_pointer.group(1) + parameter + function_pointer.group(3)
391            else:
392                signature += dtype
393
394        if not func_macro:
395            signature += ")"
396
397        self.print_lineno(ln)
398        if args.get('typedef') or not args.get('functiontype'):
399            self.data += f".. c:macro:: {args['function']}\n\n"
400
401            if args.get('typedef'):
402                self.data += "   **Typedef**: "
403                self.lineprefix = ""
404                self.output_highlight(args.get('purpose', ""))
405                self.data += "\n\n**Syntax**\n\n"
406                self.data += f"  ``{signature}``\n\n"
407            else:
408                self.data += f"``{signature}``\n\n"
409        else:
410            self.data += f".. c:function:: {signature}\n\n"
411
412        if not args.get('typedef'):
413            self.print_lineno(ln)
414            self.lineprefix = "   "
415            self.output_highlight(args.get('purpose', ""))
416            self.data += "\n"
417
418        # Put descriptive text into a container (HTML <div>) to help set
419        # function prototypes apart
420        self.lineprefix = "  "
421
422        if parameterlist:
423            self.data += ".. container:: kernelindent\n\n"
424            self.data += f"{self.lineprefix}**Parameters**\n\n"
425
426        for parameter in parameterlist:
427            parameter_name = KernRe(r'\[.*').sub('', parameter)
428            dtype = args['parametertypes'].get(parameter, "")
429
430            if dtype:
431                self.data += f"{self.lineprefix}``{dtype}``\n"
432            else:
433                self.data += f"{self.lineprefix}``{parameter}``\n"
434
435            self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0))
436
437            self.lineprefix = "    "
438            if parameter_name in parameterdescs and \
439               parameterdescs[parameter_name] != KernelDoc.undescribed:
440
441                self.output_highlight(parameterdescs[parameter_name])
442                self.data += "\n"
443            else:
444                self.data += f"{self.lineprefix}*undescribed*\n\n"
445            self.lineprefix = "  "
446
447        self.out_section(args)
448        self.lineprefix = oldprefix
449
450    def out_enum(self, fname, name, args):
451
452        oldprefix = self.lineprefix
453        name = args.get('enum', '')
454        parameterlist = args.get('parameterlist', [])
455        parameterdescs = args.get('parameterdescs', {})
456        ln = args.get('declaration_start_line', 0)
457
458        self.data += f"\n\n.. c:enum:: {name}\n\n"
459
460        self.print_lineno(ln)
461        self.lineprefix = "  "
462        self.output_highlight(args.get('purpose', ''))
463        self.data += "\n"
464
465        self.data += ".. container:: kernelindent\n\n"
466        outer = self.lineprefix + "  "
467        self.lineprefix = outer + "  "
468        self.data += f"{outer}**Constants**\n\n"
469
470        for parameter in parameterlist:
471            self.data += f"{outer}``{parameter}``\n"
472
473            if parameterdescs.get(parameter, '') != KernelDoc.undescribed:
474                self.output_highlight(parameterdescs[parameter])
475            else:
476                self.data += f"{self.lineprefix}*undescribed*\n\n"
477            self.data += "\n"
478
479        self.lineprefix = oldprefix
480        self.out_section(args)
481
482    def out_typedef(self, fname, name, args):
483
484        oldprefix = self.lineprefix
485        name = args.get('typedef', '')
486        ln = args.get('declaration_start_line', 0)
487
488        self.data += f"\n\n.. c:type:: {name}\n\n"
489
490        self.print_lineno(ln)
491        self.lineprefix = "   "
492
493        self.output_highlight(args.get('purpose', ''))
494
495        self.data += "\n"
496
497        self.lineprefix = oldprefix
498        self.out_section(args)
499
500    def out_struct(self, fname, name, args):
501
502        name = args.get('struct', "")
503        purpose = args.get('purpose', "")
504        declaration = args.get('definition', "")
505        dtype = args.get('type', "struct")
506        ln = args.get('declaration_start_line', 0)
507
508        parameterlist = args.get('parameterlist', [])
509        parameterdescs = args.get('parameterdescs', {})
510        parameterdesc_start_lines = args.get('parameterdesc_start_lines', {})
511
512        self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
513
514        self.print_lineno(ln)
515
516        oldprefix = self.lineprefix
517        self.lineprefix += "  "
518
519        self.output_highlight(purpose)
520        self.data += "\n"
521
522        self.data += ".. container:: kernelindent\n\n"
523        self.data += f"{self.lineprefix}**Definition**::\n\n"
524
525        self.lineprefix = self.lineprefix + "  "
526
527        declaration = declaration.replace("\t", self.lineprefix)
528
529        self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
530        self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
531
532        self.lineprefix = "  "
533        self.data += f"{self.lineprefix}**Members**\n\n"
534        for parameter in parameterlist:
535            if not parameter or parameter.startswith("#"):
536                continue
537
538            parameter_name = parameter.split("[", maxsplit=1)[0]
539
540            if parameterdescs.get(parameter_name) == KernelDoc.undescribed:
541                continue
542
543            self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0))
544
545            self.data += f"{self.lineprefix}``{parameter}``\n"
546
547            self.lineprefix = "    "
548            self.output_highlight(parameterdescs[parameter_name])
549            self.lineprefix = "  "
550
551            self.data += "\n"
552
553        self.data += "\n"
554
555        self.lineprefix = oldprefix
556        self.out_section(args)
557
558
559class ManFormat(OutputFormat):
560    """Consts and functions used by man pages output"""
561
562    highlights = (
563        (type_constant, r"\1"),
564        (type_constant2, r"\1"),
565        (type_func, r"\\fB\1\\fP"),
566        (type_enum, r"\\fI\1\\fP"),
567        (type_struct, r"\\fI\1\\fP"),
568        (type_typedef, r"\\fI\1\\fP"),
569        (type_union, r"\\fI\1\\fP"),
570        (type_param, r"\\fI\1\\fP"),
571        (type_param_ref, r"\\fI\1\2\\fP"),
572        (type_member, r"\\fI\1\2\3\\fP"),
573        (type_fallback, r"\\fI\1\\fP")
574    )
575    blankline = ""
576
577    date_formats = [
578        "%a %b %d %H:%M:%S %Z %Y",
579        "%a %b %d %H:%M:%S %Y",
580        "%Y-%m-%d",
581        "%b %d %Y",
582        "%B %d %Y",
583        "%m %d %Y",
584    ]
585
586    def __init__(self, modulename):
587        """
588        Creates class variables.
589
590        Not really mandatory, but it is a good coding style and makes
591        pylint happy.
592        """
593
594        super().__init__()
595        self.modulename = modulename
596
597        dt = None
598        tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
599        if tstamp:
600            for fmt in self.date_formats:
601                try:
602                    dt = datetime.strptime(tstamp, fmt)
603                    break
604                except ValueError:
605                    pass
606
607        if not dt:
608            dt = datetime.now()
609
610        self.man_date = dt.strftime("%B %Y")
611
612    def output_highlight(self, block):
613        """
614        Outputs a C symbol that may require being highlighted with
615        self.highlights variable using troff syntax
616        """
617
618        contents = self.highlight_block(block)
619
620        if isinstance(contents, list):
621            contents = "\n".join(contents)
622
623        for line in contents.strip("\n").split("\n"):
624            line = KernRe(r"^\s*").sub("", line)
625            if not line:
626                continue
627
628            if line[0] == ".":
629                self.data += "\\&" + line + "\n"
630            else:
631                self.data += line + "\n"
632
633    def out_doc(self, fname, name, args):
634        if not self.check_doc(name, args):
635            return
636
637        self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n"
638
639        for section, text in args.sections.items():
640            self.data += f'.SH "{section}"' + "\n"
641            self.output_highlight(text)
642
643    def out_function(self, fname, name, args):
644        """output function in man"""
645
646        parameterlist = args.get('parameterlist', [])
647        parameterdescs = args.get('parameterdescs', {})
648
649        self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"
650
651        self.data += ".SH NAME\n"
652        self.data += f"{args['function']} \\- {args['purpose']}\n"
653
654        self.data += ".SH SYNOPSIS\n"
655        if args.get('functiontype', ''):
656            self.data += f'.B "{args["functiontype"]}" {args["function"]}' + "\n"
657        else:
658            self.data += f'.B "{args["function"]}' + "\n"
659
660        count = 0
661        parenth = "("
662        post = ","
663
664        for parameter in parameterlist:
665            if count == len(parameterlist) - 1:
666                post = ");"
667
668            dtype = args['parametertypes'].get(parameter, "")
669            if function_pointer.match(dtype):
670                # Pointer-to-function
671                self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
672            else:
673                dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
674
675                self.data += f'.BI "{parenth}{dtype}"  "{post}"' + "\n"
676            count += 1
677            parenth = ""
678
679        if parameterlist:
680            self.data += ".SH ARGUMENTS\n"
681
682        for parameter in parameterlist:
683            parameter_name = re.sub(r'\[.*', '', parameter)
684
685            self.data += f'.IP "{parameter}" 12' + "\n"
686            self.output_highlight(parameterdescs.get(parameter_name, ""))
687
688        for section, text in args.sections.items():
689            self.data += f'.SH "{section.upper()}"' + "\n"
690            self.output_highlight(text)
691
692    def out_enum(self, fname, name, args):
693
694        name = args.get('enum', '')
695        parameterlist = args.get('parameterlist', [])
696
697        self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n"
698
699        self.data += ".SH NAME\n"
700        self.data += f"enum {args['enum']} \\- {args['purpose']}\n"
701
702        self.data += ".SH SYNOPSIS\n"
703        self.data += f"enum {args['enum']}" + " {\n"
704
705        count = 0
706        for parameter in parameterlist:
707            self.data += f'.br\n.BI "    {parameter}"' + "\n"
708            if count == len(parameterlist) - 1:
709                self.data += "\n};\n"
710            else:
711                self.data += ", \n.br\n"
712
713            count += 1
714
715        self.data += ".SH Constants\n"
716
717        for parameter in parameterlist:
718            parameter_name = KernRe(r'\[.*').sub('', parameter)
719            self.data += f'.IP "{parameter}" 12' + "\n"
720            self.output_highlight(args['parameterdescs'].get(parameter_name, ""))
721
722        for section, text in args.sections.items():
723            self.data += f'.SH "{section}"' + "\n"
724            self.output_highlight(text)
725
726    def out_typedef(self, fname, name, args):
727        module = self.modulename
728        typedef = args.get('typedef')
729        purpose = args.get('purpose')
730
731        self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n"
732
733        self.data += ".SH NAME\n"
734        self.data += f"typedef {typedef} \\- {purpose}\n"
735
736        for section, text in args.sections.items():
737            self.data += f'.SH "{section}"' + "\n"
738            self.output_highlight(text)
739
740    def out_struct(self, fname, name, args):
741        module = self.modulename
742        struct_type = args.get('type')
743        struct_name = args.get('struct')
744        purpose = args.get('purpose')
745        definition = args.get('definition')
746        parameterlist = args.get('parameterlist', [])
747        parameterdescs = args.get('parameterdescs', {})
748
749        self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
750
751        self.data += ".SH NAME\n"
752        self.data += f"{struct_type} {struct_name} \\- {purpose}\n"
753
754        # Replace tabs with two spaces and handle newlines
755        declaration = definition.replace("\t", "  ")
756        declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
757
758        self.data += ".SH SYNOPSIS\n"
759        self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n"
760        self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
761
762        self.data += ".SH Members\n"
763        for parameter in parameterlist:
764            if parameter.startswith("#"):
765                continue
766
767            parameter_name = re.sub(r"\[.*", "", parameter)
768
769            if parameterdescs.get(parameter_name) == KernelDoc.undescribed:
770                continue
771
772            self.data += f'.IP "{parameter}" 12' + "\n"
773            self.output_highlight(parameterdescs.get(parameter_name))
774
775        for section, text in args.sections.items():
776            self.data += f'.SH "{section}"' + "\n"
777            self.output_highlight(text)
778