From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <dev-bounces@dpdk.org>
Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124])
	by inbox.dpdk.org (Postfix) with ESMTP id ED1AC42FB8;
	Wed,  2 Aug 2023 19:01:09 +0200 (CEST)
Received: from mails.dpdk.org (localhost [127.0.0.1])
	by mails.dpdk.org (Postfix) with ESMTP id D93D642D0C;
	Wed,  2 Aug 2023 19:01:09 +0200 (CEST)
Received: from mgamail.intel.com (unknown [192.55.52.120])
 by mails.dpdk.org (Postfix) with ESMTP id 4C50B40DDB
 for <dev@dpdk.org>; Wed,  2 Aug 2023 19:01:08 +0200 (CEST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple;
 d=intel.com; i=@intel.com; q=dns/txt; s=Intel;
 t=1690995668; x=1722531668;
 h=from:to:cc:subject:date:message-id:in-reply-to:
 references:mime-version:content-transfer-encoding;
 bh=X6ZI85gtwK8LfRQcNZRcc6dweP+cwOg8uiM3yUQHZRE=;
 b=Jx9r/Lu6LgDCwqW5f4uBt5iZDe4+gtcxNEd9fKO0WEbMJKJJlVEdhCm3
 5Vvv1ekpmwovR8oFFbPKMwGXNApYlRGDlVfgqGJhsHutJ/XPQemllk/rt
 uhQCQMXolPir09O2VHmCaSus2Hzroi+5D6CJ6ErIQvWQUljG02mIxEShZ
 3lnvq3IIEwvOV5FgnGpAaSTqtERVD6q5ElKShv+xiqnvEPxYM+V6mp54Q
 HwXmB17t5NawegPOwFJdmcvvwekWAibs0aWPxAczEChfzyr7L18FoUj99
 EgXhxnRe+BxdxEphRGEj8ek3uRN+v5ckfxrdlk/kqrA7douk0hMnPscRd A==;
X-IronPort-AV: E=McAfee;i="6600,9927,10790"; a="368545988"
X-IronPort-AV: E=Sophos;i="6.01,249,1684825200"; d="scan'208";a="368545988"
Received: from orsmga004.jf.intel.com ([10.7.209.38])
 by fmsmga104.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;
 02 Aug 2023 10:01:07 -0700
X-ExtLoop1: 1
X-IronPort-AV: E=McAfee;i="6600,9927,10790"; a="852944035"
X-IronPort-AV: E=Sophos;i="6.01,249,1684825200"; d="scan'208";a="852944035"
Received: from silpixa00401385.ir.intel.com ([10.237.214.162])
 by orsmga004.jf.intel.com with ESMTP; 02 Aug 2023 10:01:05 -0700
From: Bruce Richardson <bruce.richardson@intel.com>
To: dev@dpdk.org,
	Olivier Matz <olivier.matz@6wind.com>
Cc: Bruce Richardson <bruce.richardson@intel.com>
Subject: [RFC PATCH 1/1] cmdline/dpdk-cmdline-gen: generate boilerplate for
 simple cmds
Date: Wed,  2 Aug 2023 18:00:52 +0100
Message-Id: <20230802170052.955323-2-bruce.richardson@intel.com>
X-Mailer: git-send-email 2.39.2
In-Reply-To: <20230802170052.955323-1-bruce.richardson@intel.com>
References: <20230802170052.955323-1-bruce.richardson@intel.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-BeenThere: dev@dpdk.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: DPDK patches and discussions <dev.dpdk.org>
List-Unsubscribe: <https://mails.dpdk.org/options/dev>,
 <mailto:dev-request@dpdk.org?subject=unsubscribe>
List-Archive: <http://mails.dpdk.org/archives/dev/>
List-Post: <mailto:dev@dpdk.org>
List-Help: <mailto:dev-request@dpdk.org?subject=help>
List-Subscribe: <https://mails.dpdk.org/listinfo/dev>,
 <mailto:dev-request@dpdk.org?subject=subscribe>
Errors-To: dev-bounces@dpdk.org

Provide a script for application developers to quickly generate the
boilerplate code necessary for using the cmdline library.

This initial version works only with commands using simple strings and
numbers, but this is sufficient for many use-cases. Future extensions
could, no doubt, expand support to expose more capabilities of the
library.

Example of use:
The script takes an input file with a list of commands the user wants in
the app, where the parameter variables are tagged with the type.
For example:

	$ cat commands.list
	list
	add <UINT16>x <UINT16>y
	echo <STRING>message
	add socket <STRING>path
	quit

When run through the script as "./dpdk-cmdline-gen.py commands.list",
the output will be the contents of a header file with all the
boilerplate necessary for a commandline instance with those commands.

If the flag --stubs is passed, an output header filename must also be
passed, in which case both a header file with the definitions and a C
file with function stubs in it is written to disk. The separation is so
that the header file can be rewritten at any future point to add more
commands, while the C file can be kept as-is and extended by the user
with any additional functions needed.

Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
 lib/cmdline/dpdk-cmdline-gen.py | 143 ++++++++++++++++++++++++++++++++
 1 file changed, 143 insertions(+)
 create mode 100755 lib/cmdline/dpdk-cmdline-gen.py

diff --git a/lib/cmdline/dpdk-cmdline-gen.py b/lib/cmdline/dpdk-cmdline-gen.py
new file mode 100755
index 0000000000..1b81c4f9dc
--- /dev/null
+++ b/lib/cmdline/dpdk-cmdline-gen.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Intel Corporation
+#
+
+import sys
+import os
+import argparse
+
+PARSE_FN_PARAMS = "void *parsed_result, struct cmdline *cl, void *data"
+PARSE_FN_BODY = """
+    /* TODO: command action */
+    RTE_SET_USED(parsed_result);
+    RTE_SET_USED(cl);
+    RTE_SET_USED(data);
+"""
+
+def process_command(tokens, cfile):
+    name = []
+
+    if tokens[0].startswith('<'):
+        print("Error: each command must start with at least one literal string",
+                file=sys.stderr)
+        sys.exit(1)
+    for t in tokens:
+        if t.startswith('<'):
+            break;
+        name.append(t)
+    name = "_".join(name)
+
+    result_struct = []
+    initializers = []
+    token_list = []
+    for t in tokens:
+        if t.startswith('<'):
+            t_type, t_name = t[1:].split('>')
+            t_val = "NULL"
+        else:
+            t_type = "STRING"
+            t_name = t
+            t_val = f'"{t}"'
+
+        if t_type == "STRING":
+            result_struct.append(f"\tcmdline_fixed_string_t {t_name};")
+            initializers.append(f"static cmdline_parse_token_string_t cmd_{name}_{t_name}_tok =\n"
+                    + f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});")
+        elif t_type in ['UINT8', 'UINT16', 'UINT32', 'UINT64',
+                    'INT8', 'INT16', 'INT32', 'INT64']:
+            result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+            initializers.append(f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
+                    + f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});")
+        else:
+            print(f"Error: unknown token-type {t}", file=sys.stderr)
+            sys.exit(1)
+        token_list.append(f"cmd_{name}_{t_name}_tok")
+
+    print(f"/* Auto-generated handling for command '{' '.join(tokens)}' */")
+    # output function prototype
+    func_sig = f"void\ncmd_{name}_parsed({PARSE_FN_PARAMS})"
+    print(f"extern {func_sig};\n")
+    # output function template if C file being written
+    if (cfile):
+        print(f"{func_sig}\n{{{PARSE_FN_BODY}}}\n", file=cfile)
+    # output result data structure
+    print(f"struct cmd_{name}_result {{\n"
+            + '\n'.join(result_struct)
+            + "\n};\n")
+    # output the initializer tokens
+    print("\n".join(initializers) + '\n')
+    # output the instance structure
+    print(f"static cmdline_parse_inst_t cmd_{name} = {{\n"
+            + f'\t.f = cmd_{name}_parsed,\n'
+            + f'\t.data = NULL,\n'
+            + f'\t.help_str = "",\n'
+            + f'\t.tokens = {{')
+    for t in token_list:
+        print(f"\t\t(void *)&{t},")
+    print("\t\tNULL\n"
+            + "\t}\n"
+            + "};\n")
+
+    # return the instance structure name
+    return f"cmd_{name}"
+
+def process_commands(infile, hfile, cfile):
+    instances = []
+
+    # redirect stdout to output the header, to save passing file= each print
+    old_sys_stdout = sys.stdout
+    sys.stdout = hfile
+
+    print(f"/* File autogenerated by {sys.argv[0]} */")
+    print('#ifndef GENERATED_COMMANDS_H')
+    print('#define GENERATED_COMMANDS_H')
+    print("#include <cmdline.h>")
+    print("#include <cmdline_parse_string.h>")
+    print("#include <cmdline_parse_num.h>")
+    print("")
+
+    for line in infile.readlines():
+        if line.lstrip().startswith('#'):
+            continue
+        instances.append(process_command(line.strip().split(), cfile))
+
+    print('static cmdline_parse_ctx_t ctx[] = {')
+    for inst in instances:
+        print(f'\t&{inst},')
+    print('\tNULL')
+    print('};\n')
+    print('#endif /* GENERATED_COMMANDS_H */')
+
+    sys.stdout = old_sys_stdout
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument("--stubs", action="store_true",
+            help="Produce C file with empty function stubs for each command")
+    ap.add_argument("--output-file", "-o", default="-",
+            help="Output header filename [default to stdout]")
+    ap.add_argument("infile", type=argparse.FileType('r'),
+            help="File with list of commands")
+    args = ap.parse_args()
+
+    if not args.stubs:
+        if args.output_file == '-':
+            process_commands(args.infile, sys.stdout, None)
+        else:
+            with open(args.output_file, "w") as hfile:
+                process_commands(args.infile, hfile, None)
+    else:
+        if not args.output_file.endswith('.h'):
+            print("Error: output filename must end with '.h' extension when creating stubs",
+                    file=sys.stderr)
+            sys.exit(1)
+
+        cfilename = args.output_file[:-2] + '.c'
+        with open(args.output_file, "w") as hfile:
+            with open(cfilename, "w") as cfile:
+                print(f"#include \"{args.output_file}\"\n", file=cfile)
+                process_commands(args.infile, hfile, cfile)
+
+if __name__ == "__main__":
+    main()
-- 
2.39.2