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