[RFC,v2,1/6] argparse: add argparse library

Message ID 20231204075048.894-2-fengchengwen@huawei.com (mailing list archive)
State Superseded, archived
Headers
Series add argparse library |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

fengchengwen Dec. 4, 2023, 7:50 a.m. UTC
  Introduce argparse library (which was inspired by the thread [1]). This
commit provides public API and doc.

[1] https://patchwork.dpdk.org/project/dpdk/patch/20231105054539.22303-2-fengchengwen@huawei.com/

Signed-off-by: Chengwen Feng <fengchengwen@huawei.com>
---
 doc/api/doxy-api-index.md              |   1 +
 doc/api/doxy-api.conf.in               |   1 +
 doc/guides/prog_guide/argparse_lib.rst | 142 ++++++++++++++++++++
 doc/guides/prog_guide/index.rst        |   1 +
 lib/argparse/meson.build               |   7 +
 lib/argparse/rte_argparse.c            |  14 ++
 lib/argparse/rte_argparse.h            | 179 +++++++++++++++++++++++++
 lib/argparse/version.map               |   7 +
 lib/meson.build                        |   1 +
 9 files changed, 353 insertions(+)
 create mode 100644 doc/guides/prog_guide/argparse_lib.rst
 create mode 100644 lib/argparse/meson.build
 create mode 100644 lib/argparse/rte_argparse.c
 create mode 100644 lib/argparse/rte_argparse.h
 create mode 100644 lib/argparse/version.map
  

Comments

Stephen Hemminger Dec. 4, 2023, 5:10 p.m. UTC | #1
On Mon, 4 Dec 2023 07:50:43 +0000
Chengwen Feng <fengchengwen@huawei.com> wrote:

> +   static struct rte_argparse obj = {
> +      .prog_name = "test-demo",
> +      .usage = "[EAL options] -- [optional parameters] [positional parameters]",
> +      .descriptor = NULL,
> +      .epilog = NULL,
> +      .exit_on_error = true,
> +      .callback = argparse_user_callback,
> +      .args = {
> +         { "--aaa", "-a", "aaa argument", (void *)&aaa_val, (void *)100, RTE_ARGPARSE_ARG_NO_VALUE       | RTE_ARGPARSE_ARG_VALUE_INT },
> +         { "--bbb", "-b", "bbb argument", (void *)&bbb_val, NULL,        RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
> +         { "--ccc", "-c", "ccc argument", (void *)&ccc_val, (void *)200, RTE_ARGPARSE_ARG_OPTIONAL_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
> +         { "--ddd", "-d", "ddd argument", NULL,             (void *)1,   RTE_ARGPARSE_ARG_NO_VALUE       },
> +         { "--eee", "-e", "eee argument", NULL,             (void *)2,   RTE_ARGPARSE_ARG_REQUIRED_VALUE },
> +         { "--fff", "-f", "fff argument", NULL,             (void *)3,   RTE_ARGPARSE_ARG_OPTIONAL_VALUE },
> +         { "ooo",   NULL, "ooo argument", (void *)&ooo_val, NULL,        RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
> +         { "ppp",   NULL, "ppp argument", NULL,             (void *)300, RTE_ARGPARSE_ARG_REQUIRED_VALUE },
> +      },
> +   };
> +

Could the API be made to work with immutable initializers?
I.e allowing the application to use:
	static const struct rte_argparse_obj {

Also better to just skip the NULL elements here, and use field initializers for the args.
That way when structure layout changes, the example will still work.
Also, pointers do not have to be cast to void * in C code.
  
fengchengwen Dec. 5, 2023, 1:22 a.m. UTC | #2
Hi Stephen,

On 2023/12/5 1:10, Stephen Hemminger wrote:
> On Mon, 4 Dec 2023 07:50:43 +0000
> Chengwen Feng <fengchengwen@huawei.com> wrote:
> 
>> +   static struct rte_argparse obj = {
>> +      .prog_name = "test-demo",
>> +      .usage = "[EAL options] -- [optional parameters] [positional parameters]",
>> +      .descriptor = NULL,
>> +      .epilog = NULL,
>> +      .exit_on_error = true,
>> +      .callback = argparse_user_callback,
>> +      .args = {
>> +         { "--aaa", "-a", "aaa argument", (void *)&aaa_val, (void *)100, RTE_ARGPARSE_ARG_NO_VALUE       | RTE_ARGPARSE_ARG_VALUE_INT },
>> +         { "--bbb", "-b", "bbb argument", (void *)&bbb_val, NULL,        RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
>> +         { "--ccc", "-c", "ccc argument", (void *)&ccc_val, (void *)200, RTE_ARGPARSE_ARG_OPTIONAL_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
>> +         { "--ddd", "-d", "ddd argument", NULL,             (void *)1,   RTE_ARGPARSE_ARG_NO_VALUE       },
>> +         { "--eee", "-e", "eee argument", NULL,             (void *)2,   RTE_ARGPARSE_ARG_REQUIRED_VALUE },
>> +         { "--fff", "-f", "fff argument", NULL,             (void *)3,   RTE_ARGPARSE_ARG_OPTIONAL_VALUE },
>> +         { "ooo",   NULL, "ooo argument", (void *)&ooo_val, NULL,        RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
>> +         { "ppp",   NULL, "ppp argument", NULL,             (void *)300, RTE_ARGPARSE_ARG_REQUIRED_VALUE },
>> +      },
>> +   };
>> +
> 
> Could the API be made to work with immutable initializers?
> I.e allowing the application to use:
> 	static const struct rte_argparse_obj {

Current impl, it can't be immutable, because the API will modify some reserved bit in args.flags field.

> 
> Also better to just skip the NULL elements here, and use field initializers for the args.

Yes, both are OK.

> That way when structure layout changes, the example will still work.
> Also, pointers do not have to be cast to void * in C code.

OK, will modify in v3.

Thanks

> .
>
  

Patch

diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index a6a768bd7c..fe41fba6ec 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -220,6 +220,7 @@  The public API headers are grouped by topics:
   [random](@ref rte_random.h),
   [config file](@ref rte_cfgfile.h),
   [key/value args](@ref rte_kvargs.h),
+  [argument parse](@ref rte_argparse.h),
   [string](@ref rte_string_fns.h),
   [thread](@ref rte_thread.h)
 
diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in
index e94c9e4e46..76f89afe71 100644
--- a/doc/api/doxy-api.conf.in
+++ b/doc/api/doxy-api.conf.in
@@ -28,6 +28,7 @@  INPUT                   = @TOPDIR@/doc/api/doxy-api-index.md \
                           @TOPDIR@/lib/eal/include \
                           @TOPDIR@/lib/eal/include/generic \
                           @TOPDIR@/lib/acl \
+                          @TOPDIR@/lib/argparse \
                           @TOPDIR@/lib/bbdev \
                           @TOPDIR@/lib/bitratestats \
                           @TOPDIR@/lib/bpf \
diff --git a/doc/guides/prog_guide/argparse_lib.rst b/doc/guides/prog_guide/argparse_lib.rst
new file mode 100644
index 0000000000..d9813cbeff
--- /dev/null
+++ b/doc/guides/prog_guide/argparse_lib.rst
@@ -0,0 +1,142 @@ 
+.. SPDX-License-Identifier: BSD-3-Clause
+   Copyright 2023 HiSilicon Limited
+
+Argparse Library
+================
+
+The argparse library provides argument parse functionality, this library makes
+it easy to write user-friendly command-line program.
+
+Features and Capabilities
+-------------------------
+
+- Support parse optional argument (which could take with no-value,
+  required-value and optional-value).
+
+- Support parse positional argument (which must take with required-value).
+
+- Support automatic generate usage information.
+
+- Support issue errors when provide with invalid arguments.
+
+- Support parse argument by two way: 1) autosave: for which known value types,
+  this way can be used; 2) callback: will invoke user callback to parse.
+
+Usage Guide
+-----------
+
+The following code demonstrates how to initialize:
+
+.. code-block:: C
+
+   static int
+   argparse_user_callback(uint32_t index, const char *value, void *opaque)
+   {
+      if (index == 1) {
+         /* process "--ddd" argument, because it has no-value, the parameter value is NULL. */
+         ...
+      } else if (index == 2) {
+         /* process "--eee" argument, because it has required-value, the parameter value must not NULL. */
+         ...
+      } else if (index == 3) {
+         /* process "--fff" argument, because it has optional-value, the parameter value maybe NULL or not NULL, depend on input. */
+         ...
+      } else if (index == 300) {
+         /* process "ppp" argument, because it's a positional argument, the parameter value must not NULL. */
+         ...
+      } else {
+         return -EINVAL;
+      }
+   }
+
+   int aaa_val, bbb_val, ccc_val, ooo_val;
+
+   static struct rte_argparse obj = {
+      .prog_name = "test-demo",
+      .usage = "[EAL options] -- [optional parameters] [positional parameters]",
+      .descriptor = NULL,
+      .epilog = NULL,
+      .exit_on_error = true,
+      .callback = argparse_user_callback,
+      .args = {
+         { "--aaa", "-a", "aaa argument", (void *)&aaa_val, (void *)100, RTE_ARGPARSE_ARG_NO_VALUE       | RTE_ARGPARSE_ARG_VALUE_INT },
+         { "--bbb", "-b", "bbb argument", (void *)&bbb_val, NULL,        RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
+         { "--ccc", "-c", "ccc argument", (void *)&ccc_val, (void *)200, RTE_ARGPARSE_ARG_OPTIONAL_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
+         { "--ddd", "-d", "ddd argument", NULL,             (void *)1,   RTE_ARGPARSE_ARG_NO_VALUE       },
+         { "--eee", "-e", "eee argument", NULL,             (void *)2,   RTE_ARGPARSE_ARG_REQUIRED_VALUE },
+         { "--fff", "-f", "fff argument", NULL,             (void *)3,   RTE_ARGPARSE_ARG_OPTIONAL_VALUE },
+         { "ooo",   NULL, "ooo argument", (void *)&ooo_val, NULL,        RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
+         { "ppp",   NULL, "ppp argument", NULL,             (void *)300, RTE_ARGPARSE_ARG_REQUIRED_VALUE },
+      },
+   };
+
+   int
+   main(int argc, char **argv)
+   {
+      ...
+      ret = rte_argparse_parse(&obj, argc, argv);
+      ...
+   }
+
+Parsing by autosave way
+~~~~~~~~~~~~~~~~~~~~~~~
+
+For which known value types (just like ``RTE_ARGPARSE_ARG_VALUE_INT``"), could
+parse by autosave way, just like above "--aaa"/"--bbb"/"--ccc" optional
+arguments:
+
+If the user input parameter are: "program --aaa --bbb 1234 --ccc=20 ...", then
+the aaa_val will equal 100, the bbb_val will equal 1234 and the ccc_val will
+equal 20.
+
+If the user input parameter are: "program --ccc ...", then the aaa_val and
+bbb_val will not modify, and ccc_val will equal 200.
+
+Parsing by callback way
+~~~~~~~~~~~~~~~~~~~~~~~
+
+It could also choose to use callback to parse, just define a unique index for
+the argument and make the field val_save to be NULL also zero value-type. Just
+like above "--ddd"/"--eee"/"--fff" optional arguments:
+
+If the user input parameter are: "program --ddd --eee 2345 --fff=30 ...", the
+function argparse_user_callback() will be invoke to parse the value.
+
+Positional arguments
+~~~~~~~~~~~~~~~~~~~~
+
+The positional arguments could not start with a hyphen (-). The above code show
+that there are two positional arguments "ooo"/"ppp", it must be flags with
+``RTE_ARGPARSE_ARG_REQUIRED_VALUE``, and it also could use autosave or callback
+to parsing:
+
+If the user input parameter are: "program [optionals] 456 789", then the ooo_val
+will equal 456, and ppp_val will equal 789.
+
+Multiple times argument
+~~~~~~~~~~~~~~~~~~~~~~~
+
+If want to support the ability to enter the same argument multiple times, then
+should mark ``RTE_ARGPARSE_ARG_SUPPORT_MULTI`` in flags field. For examples:
+
+.. code-block:: C
+
+   ...
+   { "--xyz", "-x", "xyz argument", NULL, (void *)10, RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_SUPPORT_MULTI },
+   ...
+
+Then the user input parameter could be: "program --xyz 123 --xyz 456 ...".
+
+It's important to note that the multiple times flag only support with optional
+argument and must be parsing by callback way.
+
+Other Notes
+~~~~~~~~~~~
+
+For optional arguments, short-name can be defined or not defined. For arguments
+that have required value, the following inputs are supported:
+"program --bbb=123 --eee 456 ..." or "program -b=123 -e 456 ...".
+
+For arguments that have optional value, the following inputs are supported:
+"program --ccc --fff=100 ..." or "program -c -f=100".
+
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index 94964357ff..d09d958e6c 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -13,6 +13,7 @@  Programmer's Guide
     source_org
     env_abstraction_layer
     log_lib
+    argparse_lib
     cmdline
     service_cores
     trace_lib
diff --git a/lib/argparse/meson.build b/lib/argparse/meson.build
new file mode 100644
index 0000000000..14ea735fc0
--- /dev/null
+++ b/lib/argparse/meson.build
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 HiSilicon Limited.
+
+sources = files('rte_argparse.c')
+headers = files('rte_argparse.h')
+
+deps += ['log']
diff --git a/lib/argparse/rte_argparse.c b/lib/argparse/rte_argparse.c
new file mode 100644
index 0000000000..bf14c56858
--- /dev/null
+++ b/lib/argparse/rte_argparse.c
@@ -0,0 +1,14 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 HiSilicon Limited
+ */
+
+#include "rte_argparse.h"
+
+int
+rte_argparse_parse(struct rte_argparse *obj, int argc, char **argv)
+{
+	(void)obj;
+	(void)argc;
+	(void)argv;
+	return 0;
+}
diff --git a/lib/argparse/rte_argparse.h b/lib/argparse/rte_argparse.h
new file mode 100644
index 0000000000..45f7a58607
--- /dev/null
+++ b/lib/argparse/rte_argparse.h
@@ -0,0 +1,179 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 HiSilicon Limited
+ */
+
+#ifndef RTE_ARGPARSE_H
+#define RTE_ARGPARSE_H
+
+/**
+ * @file rte_argparse.h
+ *
+ * Argument parse API.
+ *
+ * The argument parse API makes it easy to write user-friendly command-line
+ * program. The program defines what arguments it requires, and the API
+ * will parse those arguments which described in [argc, argv].
+ *
+ * The API provides following functions:
+ * 1) Support parse optional argument (which could take with no-value,
+ *    required-value and optional-value.
+ * 2) Support parse positional argument (which must take with required-value).
+ * 3) Support automatic generate usage information.
+ * 4) Support issue errors when provided with invalid arguments.
+ *
+ * There are two ways to parse arguments:
+ * 1) AutoSave: for which known value types, the way can be used.
+ * 2) Callback: will invoke user callback to parse.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Flag definition (in bitmask form) for an argument.
+ */
+enum rte_argparse_flag {
+	/**
+	 * Bit0-1: represent the argument whether has value.
+	 */
+	RTE_ARGPARSE_ARG_NO_VALUE = 1u << 0, /**< The argument has no value. */
+	RTE_ARGPARSE_ARG_REQUIRED_VALUE = 2u << 0, /**< The argument must have a value. */
+	RTE_ARGPARSE_ARG_OPTIONAL_VALUE = 3u << 0, /**< The argument has optional value. */
+
+	/**
+	 * Bit2-10: represent the value type which used when autosave
+	 */
+	RTE_ARGPARSE_ARG_VALUE_INT = 1u << 2, /**< The argument's value is int type. */
+	RTE_ARGPARSE_ARG_VALUE_MAX = 2u << 2, /**< Max value type. */
+
+	/**
+	 * Bit16: flag for that argument support occur multiple times.
+	 * This flag can be set only when the argument is optional.
+	 * When this flag is set, the callback type must be used for parsing.
+	 */
+	RTE_ARGPARSE_ARG_SUPPORT_MULTI = 1u << 16,
+
+	/**
+	 * Bit30-31: reserved for this library implement usage.
+	 */
+	RTE_ARGPARSE_ARG_RESERVED_FIELD = 3u << 30,
+};
+
+/**
+ * A structure used to hold argument's configuration.
+ */
+struct rte_argparse_arg {
+	/**
+	 * Long name of the argument:
+	 * 1) If the argument is optional, it must start with '--'.
+	 * 2) If the argument is positional, it must not start with '-'.
+	 * 3) Other case will be considered as error.
+	 */
+	const char *name_long;
+	/**
+	 * Short name of the argument:
+	 * 1) This field could be set only when name_long is optional, and
+	 *    must start with a hyphen (-) followed by an English letter.
+	 * 2) Other case it should be set NULL.
+	 */
+	const char *name_short;
+
+	/** Help information of the argument, must not be NULL. */
+	const char *help;
+
+	/**
+	 * Saver for the argument's value.
+	 * 1) If the filed is NULL, the callback way is used for parsing
+	 *    argument.
+	 * 2) If the field is not NULL, the autosave way is used for parsing
+	 *    argument.
+	 */
+	void *val_saver;
+	/**
+	 * If val_saver is NULL, this filed (cast as (uint32_t)val_set) will be
+	 * used as the first parameter to invoke callback.
+	 *
+	 * If val_saver is not NULL, then:
+	 * 1) If argument has no value, val_saver will set to val_set when
+	 *    argument found.
+	 * 2) If argument has optional value but doesn't take value this
+	 *    time, val_saver will set to val_set when argument found.
+	 * 3) Other case it should be set NULL.
+	 */
+	void *val_set;
+
+	/** @see rte_argparse_flag */
+	uint32_t flags;
+};
+
+/**
+ * Callback prototype used by parsing specified arguments.
+ *
+ * @param index
+ *   The argument's index, coming from argument's val_set.
+ * @param value
+ *   The value corresponding to the argument, it may be NULL (e.g. the
+ *   argument has no value, or the argument has optional value but doesn't
+ *   provided value).
+ * @param opaque
+ *   An opaque pointer coming from the caller.
+ * @return
+ *   0 on success. Otherwise negative value is returned.
+ */
+typedef int (*arg_parser_t)(uint32_t index, const char *value, void *opaque);
+
+/**
+ * A structure used to hold argparse's configuration.
+ */
+struct rte_argparse {
+	/** Program name. Must not be NULL. */
+	const char *prog_name;
+	/** How to use the program. Must not be NULL. */
+	const char *usage;
+	/** Explain what the program does. Could be NULL. */
+	const char *descriptor;
+	/** Text at the bottom of help. Could be NULL. */
+	const char *epilog;
+	/** Whether exit when error. */
+	bool exit_on_error;
+	/** User callback for parsing arguments. */
+	arg_parser_t callback;
+	/** Opaque which used to invoke callback. */
+	void *opaque;
+	/** Arguments configuration. Must ended with ARGPARSE_ARG_END(). */
+	struct rte_argparse_arg args[];
+};
+
+#define ARGPARSE_ARG_END() { NULL }
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Parse parameters.
+ *
+ * @param obj
+ *   Parser object.
+ * @param argc
+ *   Parameters count.
+ * @param argv
+ *   Array of parameters points.
+ *
+ * @return
+ *   0 on success. Otherwise negative value is returned.
+ */
+__rte_experimental
+int rte_argparse_parse(struct rte_argparse *obj, int argc, char **argv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RTE_ARGPARSE_H */
diff --git a/lib/argparse/version.map b/lib/argparse/version.map
new file mode 100644
index 0000000000..1c176f69e9
--- /dev/null
+++ b/lib/argparse/version.map
@@ -0,0 +1,7 @@ 
+EXPERIMENTAL {
+	global:
+
+	rte_argparse_parse;
+
+	local: *;
+};
diff --git a/lib/meson.build b/lib/meson.build
index 6c143ce5a6..cdd2d3c536 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -11,6 +11,7 @@ 
 libraries = [
         'log',
         'kvargs', # eal depends on kvargs
+        'argparse',
         'telemetry', # basic info querying
         'eal', # everything depends on eal
         'ring',