xref: /linux/tools/lib/python/kdoc/enrich_formatter.py (revision 37a93dd5c49b5fda807fd204edf2547c3493319c)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3# Copyright (c) 2025 by Mauro Carvalho Chehab <mchehab@kernel.org>.
4
5"""
6Ancillary argparse HelpFormatter class that works on a similar way as
7argparse.RawDescriptionHelpFormatter, e.g. description maintains line
8breaks, but it also implement transformations to the help text. The
9actual transformations ar given by enrich_text(), if the output is tty.
10
11Currently, the follow transformations are done:
12
13    - Positional arguments are shown in upper cases;
14    - if output is TTY, ``var`` and positional arguments are shown prepended
15      by an ANSI SGR code. This is usually translated to bold. On some
16      terminals, like, konsole, this is translated into a colored bold text.
17"""
18
19import argparse
20import re
21import sys
22
23class EnrichFormatter(argparse.HelpFormatter):
24    """
25    Better format the output, making easier to identify the positional args
26    and how they're used at the __doc__ description.
27    """
28    def __init__(self, *args, **kwargs):
29        """
30        Initialize class and check if is TTY.
31        """
32        super().__init__(*args, **kwargs)
33        self._tty = sys.stdout.isatty()
34
35    def enrich_text(self, text):
36        r"""
37        Handle ReST markups (currently, only \`\`text\`\` markups).
38        """
39        if self._tty and text:
40            # Replace ``text`` with ANSI SGR (bold)
41            return re.sub(r'\`\`(.+?)\`\`',
42                          lambda m: f'\033[1m{m.group(1)}\033[0m', text)
43        return text
44
45    def _fill_text(self, text, width, indent):
46        """
47        Enrich descriptions with markups on it.
48        """
49        enriched = self.enrich_text(text)
50        return "\n".join(indent + line for line in enriched.splitlines())
51
52    def _format_usage(self, usage, actions, groups, prefix):
53        """
54        Enrich positional arguments at usage: line.
55        """
56
57        prog = self._prog
58        parts = []
59
60        for action in actions:
61            if action.option_strings:
62                opt = action.option_strings[0]
63                if action.nargs != 0:
64                    opt += f" {action.dest.upper()}"
65                parts.append(f"[{opt}]")
66            else:
67                # Positional argument
68                parts.append(self.enrich_text(f"``{action.dest.upper()}``"))
69
70        usage_text = f"{prefix or 'usage: '} {prog} {' '.join(parts)}\n"
71        return usage_text
72
73    def _format_action_invocation(self, action):
74        """
75        Enrich argument names.
76        """
77        if not action.option_strings:
78            return self.enrich_text(f"``{action.dest.upper()}``")
79
80        return ", ".join(action.option_strings)
81