diff mbox series

[RFC,Draft,iproute2-next] tools: add a tool to generate bridge man doc

Message ID 20230913092854.1027336-3-liuhangbin@gmail.com (mailing list archive)
State RFC
Delegated to: David Ahern
Headers show
Series [RFC,Draft,iproute2-next] tools: add a tool to generate bridge man doc | expand

Checks

Context Check Description
netdev/tree_selection success Not a local patch

Commit Message

Hangbin Liu Sept. 13, 2023, 9:28 a.m. UTC
This tool will generate the ip link bridge parameters and find related
attr description in if_link.h. And convert to the new man doc.

To use it. You need to run the script first before your patch. With this
we can update the man page to latest version. e.g.

./generate_bridge_man.py -i iproute2_dir -n net-next_dir

The script will generate a ip-link.8.out page. You can check if it's OK
to move to ip-link.8.in.

After adding new parameter. You need to re-run the script to generate the man
doc from net-next header file.

The script check the existing parameter. So if the attr in net-next has not
added to iproute2, it will not generate the man doc.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 man/man8/ip-link.8.in        |   2 +
 tools/generate_bridge_man.py | 188 +++++++++++++++++++++++++++++++++++
 2 files changed, 190 insertions(+)
 create mode 100755 tools/generate_bridge_man.py
diff mbox series

Patch

diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index 8f07de9a8a25..cbf6d9a9d9c4 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -1611,6 +1611,7 @@  For a link of type
 the following additional arguments are supported:
 
 .BI "ip link add " DEVICE " type bridge "
+.\" bridge man doc starts
 [
 .BI ageing_time " AGEING_TIME "
 ] [
@@ -1887,6 +1888,7 @@  arptables hooks on the bridge.
 
 
 .in -8
+.\" bridge man doc ends
 
 .TP
 MACsec Type Support
diff --git a/tools/generate_bridge_man.py b/tools/generate_bridge_man.py
new file mode 100755
index 000000000000..45947abda382
--- /dev/null
+++ b/tools/generate_bridge_man.py
@@ -0,0 +1,188 @@ 
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+
+import re, sys, os
+import argparse
+from docutils import core
+
+man_bridge = ""
+
+def handle_args():
+    parser = argparse.ArgumentParser(description="""Convert bridge header
+                                     comments to iproute2 man doc.""")
+    parser.add_argument('-i', '--iproute2-dir', required=True,
+                        help='iproute code path')
+    parser.add_argument('-n', '--net-dir', required=True,
+                        help='net-next code path')
+    args = parser.parse_args()
+    return args
+
+def write_man_doc(input_file, output_file):
+    try:
+        with open(input_file, 'r') as infile, open(output_file, 'w') as outfile:
+            remove_flag = False
+            for line in infile:
+                if line.find("bridge man doc starts") != -1:
+                    remove_flag = True
+                    outfile.write(line)
+                    outfile.write(man_bridge + '\n')
+                elif remove_flag and line.find("bridge man doc ends") != -1:
+                    remove_flag = False
+                elif not remove_flag:
+                    outfile.write(line)
+    except Exception as e:
+        sys.exit(e)
+
+def append_man_line(line, end = '\n'):
+    global man_bridge
+    man_bridge = man_bridge + line + end
+
+def rst2man(node):
+    if not hasattr(node, 'astext'):
+        return
+
+    if node.tagname == 'bullet_list':
+        append_man_line('.in +8\n.sp')
+    if node.tagname == 'list_item':
+        append_man_line('')
+    elif node.tagname == 'emphasis':
+        append_man_line('.I ', end = '')
+    elif node.tagname == 'strong':
+        append_man_line('.B ', end = '')
+
+    for child_node in node.children:
+        rst2man(child_node)
+
+    if len(node.children) == 0:
+        text = node.astext().strip()
+        append_man_line(text)
+
+    if node.tagname == 'bullet_list':
+        append_man_line('.in -8')
+
+def convert2man(params):
+    first = True
+    for p in params:
+        if first:
+            first = False
+            append_man_line('[')
+        else:
+            append_man_line(' [')
+        append_man_line('.BI ' + p + ' " ' + params[p]['param'] + ' "')
+        append_man_line(']', end = '')
+
+    append_man_line('\n\n.in +8\n.sp')
+
+    for p in params:
+        if 'alias' in params[p]:
+            # TODO: fix the vlan_protocol output, e.g. {|}
+            append_man_line('\n.BR ' + p + ' " ' + params[p]['alias'] + ' "')
+        else:
+            append_man_line('\n.BI ' + p + ' " ' + params[p]['param'] + ' "')
+        # Add the '- ' at begining of each description
+        append_man_line('- ', end = '')
+        if 'doc' in params[p]:
+            rst_nodes = core.publish_doctree(params[p]['doc'])
+            rst2man(rst_nodes)
+
+def strip_doc_line(line, param):
+    # Remove the first " *   "
+    line = line[5:-1]
+    # replace attr with param, e.g. IFLA_BR_STP_STATE -> STP_STATE
+    line = line.replace(param['attr'], param['param'])
+    return line
+
+def get_attr(line):
+    match = re.search(r'@(\w+):', line)
+    if match:
+        return match.group(1)
+    return None
+
+# Deal some irregular namings
+def param2attr(param):
+    param = param.replace('count', 'cnt')
+    param = param.replace('interval', 'intvl')
+    param = param.replace('group_address', 'group_addr')
+    return param.upper()
+
+def get_bridge_doc(bridge_header, params):
+    find_attr = False
+    p = None
+    try:
+        with open(bridge_header, 'r') as f:
+            line = f.readline()
+            # Find the start of the doc
+            while line.find("DOC: The bridge emum defination") == -1:
+                if not line:
+                    print("Unable to find bridge DOC");
+                    return None
+                line = f.readline()
+            # Till the end of the doc
+            while line.find("*/") == -1:
+                line = f.readline()
+                # Start of a parameter
+                if line.find(' * @') == 0:
+                    find_attr = False
+                    for p in params:
+                        if line.find(param2attr(p) + ':') != -1:
+                            attr = get_attr(line)
+                            if attr is not None:
+                                params[p]['attr'] = attr
+                                find_attr = True
+                            break
+                elif find_attr and p:
+                    if 'doc' in params[p]:
+                        params[p]['doc'] = params[p]['doc'] + '\n' + strip_doc_line(line, params[p])
+                    else:
+                        params[p]['doc'] = strip_doc_line(line, params[p])
+
+    except Exception as e:
+        sys.exit(e)
+
+# replace multi text
+def strip_line(line):
+    return line.replace(' ', '').replace('"', '').replace('\\n', '').strip()
+
+def get_bridge_parameter(bridge_file):
+    params = {}
+    try:
+        with open(bridge_file, 'r') as f:
+            line = f.readline()
+            # Find the start of parameters
+            while line.find("Usage: ... bridge") == -1:
+                line = f.readline()
+            # Till the end of the parameters
+            while line.find("Where:") == -1:
+                line = f.readline()
+                parts = line.strip().split()
+                if len(parts) >= 4:
+                    if parts[1] == '[':
+                        params[parts[2]] = {}
+                        params[parts[2]]['param'] = parts[3]
+            # Till the end of usage function to get the alias
+            while line.find(");") == -1:
+                parts = line.strip().replace('Where:', '').split(':=')
+                alias = strip_line(parts[0])
+                for p in params:
+                    if params[p]['param'] == alias:
+                        params[p]['alias'] = strip_line(parts[1])
+                line = f.readline()
+    except Exception as e:
+        sys.exit(e)
+
+    return params
+
+def main():
+    args = handle_args()
+    bridge_file = args.iproute2_dir.rstrip('/') + "/ip/iplink_bridge.c"
+    ip_link_in = args.iproute2_dir.rstrip('/') + "/man/man8/ip-link.8.in"
+    ip_link_out = args.iproute2_dir.rstrip('/') + "/man/man8/ip-link.8.out"
+    bridge_header = args.net_dir.rstrip('/') + '/include/uapi/linux/if_link.h'
+
+    bridge_params = get_bridge_parameter(bridge_file)
+    get_bridge_doc(bridge_header, bridge_params)
+    convert2man(bridge_params)
+    write_man_doc(ip_link_in, ip_link_out)
+
+if __name__ == '__main__':
+    main()