xref: /linux/tools/lib/python/kdoc/kdoc_output.py (revision e394855fcc897f73f23c364a3a596b54cc879e4c)
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