xref: /linux/tools/lib/python/kdoc/kdoc_output.py (revision 197bbebd25810c5218e3347d61641be8e49c5404)
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 == "var":
203            self.out_var(fname, name, args)
204            return self.data
205
206        if dtype == "typedef":
207            self.out_typedef(fname, name, args)
208            return self.data
209
210        if dtype in ["struct", "union"]:
211            self.out_struct(fname, name, args)
212            return self.data
213
214        # Warn if some type requires an output logic
215        self.config.log.warning("doesn't know how to output '%s' block",
216                                dtype)
217
218        return None
219
220    # Virtual methods to be overridden by inherited classes
221    # At the base class, those do nothing.
222    def set_symbols(self, symbols):
223        """Get a list of all symbols from kernel_doc"""
224
225    def out_doc(self, fname, name, args):
226        """Outputs a DOC block"""
227
228    def out_function(self, fname, name, args):
229        """Outputs a function"""
230
231    def out_enum(self, fname, name, args):
232        """Outputs an enum"""
233
234    def out_var(self, fname, name, args):
235        """Outputs a variable"""
236
237    def out_typedef(self, fname, name, args):
238        """Outputs a typedef"""
239
240    def out_struct(self, fname, name, args):
241        """Outputs a struct"""
242
243
244class RestFormat(OutputFormat):
245    """Consts and functions used by ReST output"""
246
247    highlights = [
248        (type_constant, r"``\1``"),
249        (type_constant2, r"``\1``"),
250
251        # Note: need to escape () to avoid func matching later
252        (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
253        (type_member, r":c:type:`\1\2\3 <\1>`"),
254        (type_fp_param, r"**\1\\(\\)**"),
255        (type_fp_param2, r"**\1\\(\\)**"),
256        (type_func, r"\1()"),
257        (type_enum, r":c:type:`\1 <\2>`"),
258        (type_struct, r":c:type:`\1 <\2>`"),
259        (type_typedef, r":c:type:`\1 <\2>`"),
260        (type_union, r":c:type:`\1 <\2>`"),
261
262        # in rst this can refer to any type
263        (type_fallback, r":c:type:`\1`"),
264        (type_param_ref, r"**\1\2**")
265    ]
266    blankline = "\n"
267
268    sphinx_literal = KernRe(r'^[^.].*::$', cache=False)
269    sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)
270
271    def __init__(self):
272        """
273        Creates class variables.
274
275        Not really mandatory, but it is a good coding style and makes
276        pylint happy.
277        """
278
279        super().__init__()
280        self.lineprefix = ""
281
282    def print_lineno(self, ln):
283        """Outputs a line number"""
284
285        if self.enable_lineno and ln is not None:
286            ln += 1
287            self.data += f".. LINENO {ln}\n"
288
289    def output_highlight(self, args):
290        """
291        Outputs a C symbol that may require being converted to ReST using
292        the self.highlights variable
293        """
294
295        input_text = args
296        output = ""
297        in_literal = False
298        litprefix = ""
299        block = ""
300
301        for line in input_text.strip("\n").split("\n"):
302
303            # If we're in a literal block, see if we should drop out of it.
304            # Otherwise, pass the line straight through unmunged.
305            if in_literal:
306                if line.strip():  # If the line is not blank
307                    # If this is the first non-blank line in a literal block,
308                    # figure out the proper indent.
309                    if not litprefix:
310                        r = KernRe(r'^(\s*)')
311                        if r.match(line):
312                            litprefix = '^' + r.group(1)
313                        else:
314                            litprefix = ""
315
316                        output += line + "\n"
317                    elif not KernRe(litprefix).match(line):
318                        in_literal = False
319                    else:
320                        output += line + "\n"
321                else:
322                    output += line + "\n"
323
324            # Not in a literal block (or just dropped out)
325            if not in_literal:
326                block += line + "\n"
327                if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
328                    in_literal = True
329                    litprefix = ""
330                    output += self.highlight_block(block)
331                    block = ""
332
333        # Handle any remaining block
334        if block:
335            output += self.highlight_block(block)
336
337        # Print the output with the line prefix
338        for line in output.strip("\n").split("\n"):
339            self.data += self.lineprefix + line + "\n"
340
341    def out_section(self, args, out_docblock=False):
342        """
343        Outputs a block section.
344
345        This could use some work; it's used to output the DOC: sections, and
346        starts by putting out the name of the doc section itself, but that
347        tends to duplicate a header already in the template file.
348        """
349        for section, text in args.sections.items():
350            # Skip sections that are in the nosymbol_table
351            if section in self.nosymbol:
352                continue
353
354            if out_docblock:
355                if not self.out_mode == self.OUTPUT_INCLUDE:
356                    self.data += f".. _{section}:\n\n"
357                    self.data += f'{self.lineprefix}**{section}**\n\n'
358            else:
359                self.data += f'{self.lineprefix}**{section}**\n\n'
360
361            self.print_lineno(args.section_start_lines.get(section, 0))
362            self.output_highlight(text)
363            self.data += "\n"
364        self.data += "\n"
365
366    def out_doc(self, fname, name, args):
367        if not self.check_doc(name, args):
368            return
369        self.out_section(args, out_docblock=True)
370
371    def out_function(self, fname, name, args):
372
373        oldprefix = self.lineprefix
374        signature = ""
375
376        func_macro = args.get('func_macro', False)
377        if func_macro:
378            signature = name
379        else:
380            if args.get('functiontype'):
381                signature = args['functiontype'] + " "
382            signature += name + " ("
383
384        ln = args.declaration_start_line
385        count = 0
386        for parameter in args.parameterlist:
387            if count != 0:
388                signature += ", "
389            count += 1
390            dtype = args.parametertypes.get(parameter, "")
391
392            if function_pointer.search(dtype):
393                signature += function_pointer.group(1) + parameter + function_pointer.group(3)
394            else:
395                signature += dtype
396
397        if not func_macro:
398            signature += ")"
399
400        self.print_lineno(ln)
401        if args.get('typedef') or not args.get('functiontype'):
402            self.data += f".. c:macro:: {name}\n\n"
403
404            if args.get('typedef'):
405                self.data += "   **Typedef**: "
406                self.lineprefix = ""
407                self.output_highlight(args.get('purpose', ""))
408                self.data += "\n\n**Syntax**\n\n"
409                self.data += f"  ``{signature}``\n\n"
410            else:
411                self.data += f"``{signature}``\n\n"
412        else:
413            self.data += f".. c:function:: {signature}\n\n"
414
415        if not args.get('typedef'):
416            self.print_lineno(ln)
417            self.lineprefix = "   "
418            self.output_highlight(args.get('purpose', ""))
419            self.data += "\n"
420
421        # Put descriptive text into a container (HTML <div>) to help set
422        # function prototypes apart
423        self.lineprefix = "  "
424
425        if args.parameterlist:
426            self.data += ".. container:: kernelindent\n\n"
427            self.data += f"{self.lineprefix}**Parameters**\n\n"
428
429        for parameter in args.parameterlist:
430            parameter_name = KernRe(r'\[.*').sub('', parameter)
431            dtype = args.parametertypes.get(parameter, "")
432
433            if dtype:
434                self.data += f"{self.lineprefix}``{dtype}``\n"
435            else:
436                self.data += f"{self.lineprefix}``{parameter}``\n"
437
438            self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
439
440            self.lineprefix = "    "
441            if parameter_name in args.parameterdescs and \
442               args.parameterdescs[parameter_name] != KernelDoc.undescribed:
443
444                self.output_highlight(args.parameterdescs[parameter_name])
445                self.data += "\n"
446            else:
447                self.data += f"{self.lineprefix}*undescribed*\n\n"
448            self.lineprefix = "  "
449
450        self.out_section(args)
451        self.lineprefix = oldprefix
452
453    def out_enum(self, fname, name, args):
454
455        oldprefix = self.lineprefix
456        ln = args.declaration_start_line
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 args.parameterlist:
471            self.data += f"{outer}``{parameter}``\n"
472
473            if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:
474                self.output_highlight(args.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_var(self, fname, name, args):
483        oldprefix = self.lineprefix
484        ln = args.declaration_start_line
485        full_proto = args.other_stuff["full_proto"]
486
487        self.lineprefix = "  "
488
489        self.data += f"\n\n.. c:macro:: {name}\n\n{self.lineprefix}``{full_proto}``\n\n"
490
491        self.print_lineno(ln)
492        self.output_highlight(args.get('purpose', ''))
493        self.data += "\n"
494
495        if args.other_stuff["default_val"]:
496            self.data += f'{self.lineprefix}**Initialization**\n\n'
497            self.output_highlight(f'default: ``{args.other_stuff["default_val"]}``')
498
499        self.out_section(args)
500
501    def out_typedef(self, fname, name, args):
502
503        oldprefix = self.lineprefix
504        ln = args.declaration_start_line
505
506        self.data += f"\n\n.. c:type:: {name}\n\n"
507
508        self.print_lineno(ln)
509        self.lineprefix = "   "
510
511        self.output_highlight(args.get('purpose', ''))
512
513        self.data += "\n"
514
515        self.lineprefix = oldprefix
516        self.out_section(args)
517
518    def out_struct(self, fname, name, args):
519
520        purpose = args.get('purpose', "")
521        declaration = args.get('definition', "")
522        dtype = args.type
523        ln = args.declaration_start_line
524
525        self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
526
527        self.print_lineno(ln)
528
529        oldprefix = self.lineprefix
530        self.lineprefix += "  "
531
532        self.output_highlight(purpose)
533        self.data += "\n"
534
535        self.data += ".. container:: kernelindent\n\n"
536        self.data += f"{self.lineprefix}**Definition**::\n\n"
537
538        self.lineprefix = self.lineprefix + "  "
539
540        declaration = declaration.replace("\t", self.lineprefix)
541
542        self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
543        self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
544
545        self.lineprefix = "  "
546        self.data += f"{self.lineprefix}**Members**\n\n"
547        for parameter in args.parameterlist:
548            if not parameter or parameter.startswith("#"):
549                continue
550
551            parameter_name = parameter.split("[", maxsplit=1)[0]
552
553            if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
554                continue
555
556            self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
557
558            self.data += f"{self.lineprefix}``{parameter}``\n"
559
560            self.lineprefix = "    "
561            self.output_highlight(args.parameterdescs[parameter_name])
562            self.lineprefix = "  "
563
564            self.data += "\n"
565
566        self.data += "\n"
567
568        self.lineprefix = oldprefix
569        self.out_section(args)
570
571
572class ManFormat(OutputFormat):
573    """Consts and functions used by man pages output"""
574
575    highlights = (
576        (type_constant, r"\1"),
577        (type_constant2, r"\1"),
578        (type_func, r"\\fB\1\\fP"),
579        (type_enum, r"\\fI\1\\fP"),
580        (type_struct, r"\\fI\1\\fP"),
581        (type_typedef, r"\\fI\1\\fP"),
582        (type_union, r"\\fI\1\\fP"),
583        (type_param, r"\\fI\1\\fP"),
584        (type_param_ref, r"\\fI\1\2\\fP"),
585        (type_member, r"\\fI\1\2\3\\fP"),
586        (type_fallback, r"\\fI\1\\fP")
587    )
588    blankline = ""
589
590    date_formats = [
591        "%a %b %d %H:%M:%S %Z %Y",
592        "%a %b %d %H:%M:%S %Y",
593        "%Y-%m-%d",
594        "%b %d %Y",
595        "%B %d %Y",
596        "%m %d %Y",
597    ]
598
599    def __init__(self, modulename):
600        """
601        Creates class variables.
602
603        Not really mandatory, but it is a good coding style and makes
604        pylint happy.
605        """
606
607        super().__init__()
608        self.modulename = modulename
609        self.symbols = []
610
611        dt = None
612        tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
613        if tstamp:
614            for fmt in self.date_formats:
615                try:
616                    dt = datetime.strptime(tstamp, fmt)
617                    break
618                except ValueError:
619                    pass
620
621        if not dt:
622            dt = datetime.now()
623
624        self.man_date = dt.strftime("%B %Y")
625
626    def arg_name(self, args, name):
627        """
628        Return the name that will be used for the man page.
629
630        As we may have the same name on different namespaces,
631        prepend the data type for all types except functions and typedefs.
632
633        The doc section is special: it uses the modulename.
634        """
635
636        dtype = args.type
637
638        if dtype == "doc":
639            return self.modulename
640
641        if dtype in ["function", "typedef"]:
642            return name
643
644        return f"{dtype} {name}"
645
646    def set_symbols(self, symbols):
647        """
648        Get a list of all symbols from kernel_doc.
649
650        Man pages will uses it to add a SEE ALSO section with other
651        symbols at the same file.
652        """
653        self.symbols = symbols
654
655    def out_tail(self, fname, name, args):
656        """Adds a tail for all man pages"""
657
658        # SEE ALSO section
659        self.data += f'.SH "SEE ALSO"' + "\n.PP\n"
660        self.data += (f"Kernel file \\fB{args.fname}\\fR\n")
661        if len(self.symbols) >= 2:
662            cur_name = self.arg_name(args, name)
663
664            related = []
665            for arg in self.symbols:
666                out_name = self.arg_name(arg, arg.name)
667
668                if cur_name == out_name:
669                    continue
670
671                related.append(f"\\fB{out_name}\\fR(9)")
672
673            self.data += ",\n".join(related) + "\n"
674
675        # TODO: does it make sense to add other sections? Maybe
676        # REPORTING ISSUES? LICENSE?
677
678    def msg(self, fname, name, args):
679        """
680        Handles a single entry from kernel-doc parser.
681
682        Add a tail at the end of man pages output.
683        """
684        super().msg(fname, name, args)
685        self.out_tail(fname, name, args)
686
687        return self.data
688
689    def output_highlight(self, block):
690        """
691        Outputs a C symbol that may require being highlighted with
692        self.highlights variable using troff syntax
693        """
694
695        contents = self.highlight_block(block)
696
697        if isinstance(contents, list):
698            contents = "\n".join(contents)
699
700        for line in contents.strip("\n").split("\n"):
701            line = KernRe(r"^\s*").sub("", line)
702            if not line:
703                continue
704
705            if line[0] == ".":
706                self.data += "\\&" + line + "\n"
707            else:
708                self.data += line + "\n"
709
710    def out_doc(self, fname, name, args):
711        if not self.check_doc(name, args):
712            return
713
714        out_name = self.arg_name(args, name)
715
716        self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
717
718        for section, text in args.sections.items():
719            self.data += f'.SH "{section}"' + "\n"
720            self.output_highlight(text)
721
722    def out_function(self, fname, name, args):
723        """output function in man"""
724
725        out_name = self.arg_name(args, name)
726
727        self.data += f'.TH "{name}" 9 "{out_name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"
728
729        self.data += ".SH NAME\n"
730        self.data += f"{name} \\- {args['purpose']}\n"
731
732        self.data += ".SH SYNOPSIS\n"
733        if args.get('functiontype', ''):
734            self.data += f'.B "{args["functiontype"]}" {name}' + "\n"
735        else:
736            self.data += f'.B "{name}' + "\n"
737
738        count = 0
739        parenth = "("
740        post = ","
741
742        for parameter in args.parameterlist:
743            if count == len(args.parameterlist) - 1:
744                post = ");"
745
746            dtype = args.parametertypes.get(parameter, "")
747            if function_pointer.match(dtype):
748                # Pointer-to-function
749                self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
750            else:
751                dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
752
753                self.data += f'.BI "{parenth}{dtype}"  "{post}"' + "\n"
754            count += 1
755            parenth = ""
756
757        if args.parameterlist:
758            self.data += ".SH ARGUMENTS\n"
759
760        for parameter in args.parameterlist:
761            parameter_name = re.sub(r'\[.*', '', parameter)
762
763            self.data += f'.IP "{parameter}" 12' + "\n"
764            self.output_highlight(args.parameterdescs.get(parameter_name, ""))
765
766        for section, text in args.sections.items():
767            self.data += f'.SH "{section.upper()}"' + "\n"
768            self.output_highlight(text)
769
770    def out_enum(self, fname, name, args):
771        out_name = self.arg_name(args, name)
772
773        self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
774
775        self.data += ".SH NAME\n"
776        self.data += f"enum {name} \\- {args['purpose']}\n"
777
778        self.data += ".SH SYNOPSIS\n"
779        self.data += f"enum {name}" + " {\n"
780
781        count = 0
782        for parameter in args.parameterlist:
783            self.data += f'.br\n.BI "    {parameter}"' + "\n"
784            if count == len(args.parameterlist) - 1:
785                self.data += "\n};\n"
786            else:
787                self.data += ", \n.br\n"
788
789            count += 1
790
791        self.data += ".SH Constants\n"
792
793        for parameter in args.parameterlist:
794            parameter_name = KernRe(r'\[.*').sub('', parameter)
795            self.data += f'.IP "{parameter}" 12' + "\n"
796            self.output_highlight(args.parameterdescs.get(parameter_name, ""))
797
798        for section, text in args.sections.items():
799            self.data += f'.SH "{section}"' + "\n"
800            self.output_highlight(text)
801
802    def out_var(self, fname, name, args):
803        out_name = self.arg_name(args, name)
804        full_proto = args.other_stuff["full_proto"]
805
806        self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
807
808        self.data += ".SH NAME\n"
809        self.data += f"{name} \\- {args['purpose']}\n"
810
811        self.data += ".SH SYNOPSIS\n"
812        self.data += f"{full_proto}\n"
813
814        if args.other_stuff["default_val"]:
815            self.data += f'.SH "Initialization"' + "\n"
816            self.output_highlight(f'default: {args.other_stuff["default_val"]}')
817
818        for section, text in args.sections.items():
819            self.data += f'.SH "{section}"' + "\n"
820            self.output_highlight(text)
821
822    def out_typedef(self, fname, name, args):
823        module = self.modulename
824        purpose = args.get('purpose')
825        out_name = self.arg_name(args, name)
826
827        self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
828
829        self.data += ".SH NAME\n"
830        self.data += f"typedef {name} \\- {purpose}\n"
831
832        for section, text in args.sections.items():
833            self.data += f'.SH "{section}"' + "\n"
834            self.output_highlight(text)
835
836    def out_struct(self, fname, name, args):
837        module = self.modulename
838        purpose = args.get('purpose')
839        definition = args.get('definition')
840        out_name = self.arg_name(args, name)
841
842        self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
843
844        self.data += ".SH NAME\n"
845        self.data += f"{args.type} {name} \\- {purpose}\n"
846
847        # Replace tabs with two spaces and handle newlines
848        declaration = definition.replace("\t", "  ")
849        declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
850
851        self.data += ".SH SYNOPSIS\n"
852        self.data += f"{args.type} {name} " + "{" + "\n.br\n"
853        self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
854
855        self.data += ".SH Members\n"
856        for parameter in args.parameterlist:
857            if parameter.startswith("#"):
858                continue
859
860            parameter_name = re.sub(r"\[.*", "", parameter)
861
862            if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
863                continue
864
865            self.data += f'.IP "{parameter}" 12' + "\n"
866            self.output_highlight(args.parameterdescs.get(parameter_name))
867
868        for section, text in args.sections.items():
869            self.data += f'.SH "{section}"' + "\n"
870            self.output_highlight(text)
871