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