Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'tools-ynl-cli-improve-the-help-and-doc'

Jakub Kicinski says:

====================
tools: ynl: cli: improve the help and doc

I had some time on the plane to LPC, so here are improvements
to the --help and --list-attrs handling of YNL CLI which seem
in order given growing use of YNL as a real CLI tool.
====================

Link: https://patch.msgid.link/20260110233142.3921386-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+144 -63
+144 -63
tools/net/ynl/pyynl/cli.py
··· 10 10 import os 11 11 import pathlib 12 12 import pprint 13 + import shutil 13 14 import sys 14 15 import textwrap 15 16 ··· 20 19 21 20 SYS_SCHEMA_DIR='/usr/share/ynl' 22 21 RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink' 22 + 23 + # pylint: disable=too-few-public-methods,too-many-locals 24 + class Colors: 25 + """ANSI color and font modifier codes""" 26 + RESET = '\033[0m' 27 + 28 + BOLD = '\033[1m' 29 + ITALICS = '\033[3m' 30 + UNDERLINE = '\033[4m' 31 + INVERT = '\033[7m' 32 + 33 + 34 + def color(text, modifiers): 35 + """Add color to text if output is a TTY 36 + 37 + Returns: 38 + Colored text if stdout is a TTY, otherwise plain text 39 + """ 40 + if sys.stdout.isatty(): 41 + # Join the colors if they are a list, if it's a string this a noop 42 + modifiers = "".join(modifiers) 43 + return f"{modifiers}{text}{Colors.RESET}" 44 + return text 23 45 24 46 def schema_dir(): 25 47 """ ··· 84 60 for attr_name in attr_names: 85 61 if attr_name in attr_set.attrs: 86 62 attr = attr_set.attrs[attr_name] 87 - attr_info = f'{prefix}- {attr_name}: {attr.type}' 63 + attr_info = f'{prefix}- {color(attr_name, Colors.BOLD)}: {attr.type}' 88 64 if 'enum' in attr.yaml: 89 65 enum_name = attr.yaml['enum'] 90 66 attr_info += f" (enum: {enum_name})" ··· 92 68 if enum_name in ynl.consts: 93 69 const = ynl.consts[enum_name] 94 70 enum_values = list(const.entries.keys()) 95 - attr_info += f"\n{prefix} {const.type.capitalize()}: {', '.join(enum_values)}" 71 + type_fmted = color(const.type.capitalize(), Colors.ITALICS) 72 + attr_info += f"\n{prefix} {type_fmted}: {', '.join(enum_values)}" 96 73 97 74 # Show nested attributes reference and recursively display them 98 75 nested_set_name = None ··· 102 77 attr_info += f" -> {nested_set_name}" 103 78 104 79 if attr.yaml.get('doc'): 105 - doc_text = textwrap.indent(attr.yaml['doc'], prefix + ' ') 80 + doc_prefix = prefix + ' ' * 4 81 + term_width = shutil.get_terminal_size().columns 82 + doc_text = textwrap.fill(attr.yaml['doc'], width=term_width, 83 + initial_indent=doc_prefix, 84 + subsequent_indent=doc_prefix) 106 85 attr_info += f"\n{doc_text}" 107 86 print(attr_info) 108 87 ··· 120 91 print_attr_list(ynl, nested_names, nested_set, indent + 4) 121 92 122 93 123 - def print_mode_attrs(ynl, mode, mode_spec, attr_set, print_request=True): 94 + def print_mode_attrs(ynl, mode, mode_spec, attr_set, consistent_dd_reply=None): 124 95 """Print a given mode (do/dump/event/notify).""" 125 96 mode_title = mode.capitalize() 126 97 127 - if print_request and 'request' in mode_spec and 'attributes' in mode_spec['request']: 98 + if 'request' in mode_spec and 'attributes' in mode_spec['request']: 128 99 print(f'\n{mode_title} request attributes:') 129 100 print_attr_list(ynl, mode_spec['request']['attributes'], attr_set) 130 101 131 102 if 'reply' in mode_spec and 'attributes' in mode_spec['reply']: 132 - print(f'\n{mode_title} reply attributes:') 133 - print_attr_list(ynl, mode_spec['reply']['attributes'], attr_set) 103 + if consistent_dd_reply and mode == "do": 104 + title = None # Dump handling will print in combined format 105 + elif consistent_dd_reply and mode == "dump": 106 + title = 'Do and Dump' 107 + else: 108 + title = f'{mode_title}' 109 + if title: 110 + print(f'\n{title} reply attributes:') 111 + print_attr_list(ynl, mode_spec['reply']['attributes'], attr_set) 134 112 135 - if 'attributes' in mode_spec: 136 - print(f'\n{mode_title} attributes:') 137 - print_attr_list(ynl, mode_spec['attributes'], attr_set) 113 + 114 + def do_doc(ynl, op): 115 + """Handle --list-attrs $op, print the attr information to stdout""" 116 + print(f'Operation: {color(op.name, Colors.BOLD)}') 117 + print(op.yaml['doc']) 118 + 119 + consistent_dd_reply = False 120 + if 'do' in op.yaml and 'dump' in op.yaml and 'reply' in op.yaml['do'] and \ 121 + op.yaml['do']['reply'] == op.yaml['dump'].get('reply'): 122 + consistent_dd_reply = True 123 + 124 + for mode in ['do', 'dump']: 125 + if mode in op.yaml: 126 + print_mode_attrs(ynl, mode, op.yaml[mode], op.attr_set, 127 + consistent_dd_reply=consistent_dd_reply) 128 + 129 + if 'attributes' in op.yaml.get('event', {}): 130 + print('\nEvent attributes:') 131 + print_attr_list(ynl, op.yaml['event']['attributes'], op.attr_set) 132 + 133 + if 'notify' in op.yaml: 134 + mode_spec = op.yaml['notify'] 135 + ref_spec = ynl.msgs.get(mode_spec).yaml.get('do') 136 + if not ref_spec: 137 + ref_spec = ynl.msgs.get(mode_spec).yaml.get('dump') 138 + if ref_spec: 139 + print('\nNotification attributes:') 140 + print_attr_list(ynl, ref_spec['reply']['attributes'], op.attr_set) 141 + 142 + if 'mcgrp' in op.yaml: 143 + print(f"\nMulticast group: {op.yaml['mcgrp']}") 138 144 139 145 140 146 # pylint: disable=too-many-locals,too-many-branches,too-many-statements ··· 186 122 """ 187 123 188 124 parser = argparse.ArgumentParser(description=description, 189 - epilog=epilog) 190 - spec_group = parser.add_mutually_exclusive_group(required=True) 191 - spec_group.add_argument('--family', dest='family', type=str, 192 - help='name of the netlink FAMILY') 193 - spec_group.add_argument('--list-families', action='store_true', 194 - help='list all netlink families supported by YNL (has spec)') 195 - spec_group.add_argument('--spec', dest='spec', type=str, 196 - help='choose the family by SPEC file path') 125 + epilog=epilog, add_help=False) 197 126 198 - parser.add_argument('--schema', dest='schema', type=str) 199 - parser.add_argument('--no-schema', action='store_true') 200 - parser.add_argument('--json', dest='json_text', type=str) 127 + gen_group = parser.add_argument_group('General options') 128 + gen_group.add_argument('-h', '--help', action='help', 129 + help='show this help message and exit') 201 130 202 - group = parser.add_mutually_exclusive_group() 203 - group.add_argument('--do', dest='do', metavar='DO-OPERATION', type=str) 204 - group.add_argument('--multi', dest='multi', nargs=2, action='append', 205 - metavar=('DO-OPERATION', 'JSON_TEXT'), type=str) 206 - group.add_argument('--dump', dest='dump', metavar='DUMP-OPERATION', type=str) 207 - group.add_argument('--list-ops', action='store_true') 208 - group.add_argument('--list-msgs', action='store_true') 209 - group.add_argument('--list-attrs', dest='list_attrs', metavar='OPERATION', type=str, 210 - help='List attributes for an operation') 211 - group.add_argument('--validate', action='store_true') 131 + spec_group = parser.add_argument_group('Netlink family selection') 132 + spec_sel = spec_group.add_mutually_exclusive_group(required=True) 133 + spec_sel.add_argument('--list-families', action='store_true', 134 + help=('list Netlink families supported by YNL ' 135 + '(which have a spec available in the standard ' 136 + 'system path)')) 137 + spec_sel.add_argument('--family', dest='family', type=str, 138 + help='name of the Netlink FAMILY to use') 139 + spec_sel.add_argument('--spec', dest='spec', type=str, 140 + help='full file path to the YAML spec file') 212 141 213 - parser.add_argument('--duration', dest='duration', type=int, 214 - help='when subscribed, watch for DURATION seconds') 215 - parser.add_argument('--sleep', dest='duration', type=int, 216 - help='alias for duration') 217 - parser.add_argument('--subscribe', dest='ntf', type=str) 218 - parser.add_argument('--replace', dest='flags', action='append_const', 219 - const=Netlink.NLM_F_REPLACE) 220 - parser.add_argument('--excl', dest='flags', action='append_const', 221 - const=Netlink.NLM_F_EXCL) 222 - parser.add_argument('--create', dest='flags', action='append_const', 223 - const=Netlink.NLM_F_CREATE) 224 - parser.add_argument('--append', dest='flags', action='append_const', 225 - const=Netlink.NLM_F_APPEND) 226 - parser.add_argument('--process-unknown', action=argparse.BooleanOptionalAction) 227 - parser.add_argument('--output-json', action='store_true') 228 - parser.add_argument('--dbg-small-recv', default=0, const=4000, 229 - action='store', nargs='?', type=int) 142 + ops_group = parser.add_argument_group('Operations') 143 + ops = ops_group.add_mutually_exclusive_group() 144 + ops.add_argument('--do', dest='do', metavar='DO-OPERATION', type=str) 145 + ops.add_argument('--dump', dest='dump', metavar='DUMP-OPERATION', type=str) 146 + ops.add_argument('--multi', dest='multi', nargs=2, action='append', 147 + metavar=('DO-OPERATION', 'JSON_TEXT'), type=str, 148 + help="Multi-message operation sequence (for nftables)") 149 + ops.add_argument('--list-ops', action='store_true', 150 + help="List available --do and --dump operations") 151 + ops.add_argument('--list-msgs', action='store_true', 152 + help="List all messages of the family (incl. notifications)") 153 + ops.add_argument('--list-attrs', '--doc', dest='list_attrs', metavar='MSG', 154 + type=str, help='List attributes for a message / operation') 155 + ops.add_argument('--validate', action='store_true', 156 + help="Validate the spec against schema and exit") 157 + 158 + io_group = parser.add_argument_group('Input / Output') 159 + io_group.add_argument('--json', dest='json_text', type=str, 160 + help=('Specify attributes of the message to send ' 161 + 'to the kernel in JSON format. Can be left out ' 162 + 'if the message is expected to be empty.')) 163 + io_group.add_argument('--output-json', action='store_true', 164 + help='Format output as JSON') 165 + 166 + ntf_group = parser.add_argument_group('Notifications') 167 + ntf_group.add_argument('--subscribe', dest='ntf', type=str) 168 + ntf_group.add_argument('--duration', dest='duration', type=int, 169 + help='when subscribed, watch for DURATION seconds') 170 + ntf_group.add_argument('--sleep', dest='duration', type=int, 171 + help='alias for duration') 172 + 173 + nlflags = parser.add_argument_group('Netlink message flags (NLM_F_*)', 174 + ('Extra flags to set in nlmsg_flags of ' 175 + 'the request, used mostly by older ' 176 + 'Classic Netlink families.')) 177 + nlflags.add_argument('--replace', dest='flags', action='append_const', 178 + const=Netlink.NLM_F_REPLACE) 179 + nlflags.add_argument('--excl', dest='flags', action='append_const', 180 + const=Netlink.NLM_F_EXCL) 181 + nlflags.add_argument('--create', dest='flags', action='append_const', 182 + const=Netlink.NLM_F_CREATE) 183 + nlflags.add_argument('--append', dest='flags', action='append_const', 184 + const=Netlink.NLM_F_APPEND) 185 + 186 + schema_group = parser.add_argument_group('Development options') 187 + schema_group.add_argument('--schema', dest='schema', type=str, 188 + help="JSON schema to validate the spec") 189 + schema_group.add_argument('--no-schema', action='store_true') 190 + 191 + dbg_group = parser.add_argument_group('Debug options') 192 + dbg_group.add_argument('--dbg-small-recv', default=0, const=4000, 193 + action='store', nargs='?', type=int, metavar='INT', 194 + help="Length of buffers used for recv()") 195 + dbg_group.add_argument('--process-unknown', action=argparse.BooleanOptionalAction) 196 + 230 197 args = parser.parse_args() 231 198 232 199 def output(msg): ··· 321 226 print(f'Operation {args.list_attrs} not found') 322 227 sys.exit(1) 323 228 324 - print(f'Operation: {op.name}') 325 - print(op.yaml['doc']) 326 - 327 - for mode in ['do', 'dump', 'event']: 328 - if mode in op.yaml: 329 - print_mode_attrs(ynl, mode, op.yaml[mode], op.attr_set, True) 330 - 331 - if 'notify' in op.yaml: 332 - mode_spec = op.yaml['notify'] 333 - ref_spec = ynl.msgs.get(mode_spec).yaml.get('do') 334 - if ref_spec: 335 - print_mode_attrs(ynl, 'notify', ref_spec, op.attr_set, False) 336 - 337 - if 'mcgrp' in op.yaml: 338 - print(f"\nMulticast group: {op.yaml['mcgrp']}") 229 + do_doc(ynl, op) 339 230 340 231 try: 341 232 if args.do: