[02/12] argparse: add argparse library

Message ID 20240122035802.31491-3-fengchengwen@huawei.com (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers
Series add argparse library |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

fengchengwen Jan. 22, 2024, 3:57 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 | 141 ++++++++++++++++++
 doc/guides/prog_guide/index.rst        |   1 +
 doc/guides/rel_notes/release_24_03.rst |   5 +
 lib/argparse/meson.build               |   7 +
 lib/argparse/rte_argparse.c            |  14 ++
 lib/argparse/rte_argparse.h            | 191 +++++++++++++++++++++++++
 lib/argparse/version.map               |   7 +
 lib/meson.build                        |   1 +
 10 files changed, 369 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 Jan. 22, 2024, 4:54 a.m. UTC | #1
On Mon, 22 Jan 2024 03:57:52 +0000
Chengwen Feng <fengchengwen@huawei.com> wrote:

> +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.

These paragraphs are awkwardly worded.
  
fengchengwen Jan. 22, 2024, 6:06 a.m. UTC | #2
Hi Stephen,

On 2024/1/22 12:54, Stephen Hemminger wrote:
> On Mon, 22 Jan 2024 03:57:52 +0000
> Chengwen Feng <fengchengwen@huawei.com> wrote:
> 
>> +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.
> 
> These paragraphs are awkwardly worded.

I will try to refine it, Thanks.

> .
>
  
Thomas Monjalon Jan. 24, 2024, 1:24 p.m. UTC | #3
22/01/2024 04:57, Chengwen Feng:
> 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/

I'm not sure how this helps with the initial problem
when using kvargs with key-only.
I think you should continue fixing kvargs API and usage.

About a generic argparse library, I imagine it could simplify DPDK internal parsing.
I have doubts about making it a public library as it is not really a DPDK goal.
The DMA example looks better with argparse so I imagine we want it.

I think this library would have a bigger value
if we integrate some specific syntax parsing
like coremask/corelist as done in another patchset:
https://patches.dpdk.org/project/dpdk/list/?series=30582
  
fengchengwen Jan. 25, 2024, 3:44 a.m. UTC | #4
Hi Thomas,

On 2024/1/24 21:24, Thomas Monjalon wrote:
> 22/01/2024 04:57, Chengwen Feng:
>> 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/
> 
> I'm not sure how this helps with the initial problem
> when using kvargs with key-only.
> I think you should continue fixing kvargs API and usage.

This argparse couldn't direct help to fix kvargs with key-only.

I propose a patchset [1] to fix kvargs with key-only, waiting for more discussion.

[1] https://patchwork.dpdk.org/project/dpdk/cover/20231106073125.55280-1-fengchengwen@huawei.com/

> 
> About a generic argparse library, I imagine it could simplify DPDK internal parsing.
> I have doubts about making it a public library as it is not really a DPDK goal.
> The DMA example looks better with argparse so I imagine we want it.
> 
> I think this library would have a bigger value

In addition to service [argc, argv] parsing, This library also provide API which parse specific type.

For example, user could invoke:
  int value, ret;
  ret = rte_argparse_parse_type(str, RTE_ARGPARSE_ARG_VALUE_INT, &value);

to parse int from a input str.

This API then could used by other library/drivers.

> if we integrate some specific syntax parsing
> like coremask/corelist as done in another patchset:
> https://patches.dpdk.org/project/dpdk/list/?series=30582

Yes, I think it could integrate syntax parsing which providing from above patchset, just:

Define two arg-value type and specify their value storage type requirements:
  /** The argument's value is uint16_t xxx[RTE_MAX_LCORE] type. */
  RTE_ARGPARSE_ARG_VALUE_COREMASK,
  /** The argument's value is uint16_t xxx[RTE_MAX_LCORE] type. */
  RTE_ARGPARSE_ARG_VALUE_CORELIST,

Other library/driver could then invoke:
  uint16_t lcores[RTE_MAX_LCORE];
  int ret;
  ret = rte_argparse_parse_type(str, RTE_ARGPARSE_ARG_VALUE_COREMASK, lcores);
  or
  ret = rte_argparse_parse_type(str, RTE_ARGPARSE_ARG_VALUE_CORELIST, lcores)

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..012b29bbfb
--- /dev/null
+++ b/doc/guides/prog_guide/argparse_lib.rst
@@ -0,0 +1,141 @@ 
+.. SPDX-License-Identifier: BSD-3-Clause
+   Copyright(c) 2024 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", &aaa_val, (void *)100, RTE_ARGPARSE_ARG_NO_VALUE       | RTE_ARGPARSE_ARG_VALUE_INT },
+         { "--bbb", "-b", "bbb argument", &bbb_val, NULL,        RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT },
+         { "--ccc", "-c", "ccc argument", &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", &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/doc/guides/rel_notes/release_24_03.rst b/doc/guides/rel_notes/release_24_03.rst
index 6f8ad27808..724b7b673e 100644
--- a/doc/guides/rel_notes/release_24_03.rst
+++ b/doc/guides/rel_notes/release_24_03.rst
@@ -55,6 +55,11 @@  New Features
      Also, make sure to start the actual text at the margin.
      =======================================================
 
+* **Introduce argument parse library.**
+
+  Introduce argparse library, compared with getopt, it makes it easy to write
+  user-friendly command-like program.
+
 
 Removed Items
 -------------
diff --git a/lib/argparse/meson.build b/lib/argparse/meson.build
new file mode 100644
index 0000000000..b6a08ca049
--- /dev/null
+++ b/lib/argparse/meson.build
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2024 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..3471c5e757
--- /dev/null
+++ b/lib/argparse/rte_argparse.c
@@ -0,0 +1,14 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 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..3e94711280
--- /dev/null
+++ b/lib/argparse/rte_argparse.h
@@ -0,0 +1,191 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 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_bitops.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.
+	 */
+
+	/** The argument has no value. */
+	RTE_ARGPARSE_ARG_NO_VALUE = RTE_MBIT64(1, 0),
+	/** The argument must have a value. */
+	RTE_ARGPARSE_ARG_REQUIRED_VALUE = RTE_MBIT64(2, 0),
+	/** The argument has optional value. */
+	RTE_ARGPARSE_ARG_OPTIONAL_VALUE = RTE_MBIT64(3, 0),
+
+
+	/*
+	 * Bit2-9: represent the value type which used when autosave
+	 */
+
+	/** The argument's value is int type. */
+	RTE_ARGPARSE_ARG_VALUE_INT = RTE_MBIT64(1, 2),
+	/** Max value type. */
+	RTE_ARGPARSE_ARG_VALUE_MAX = RTE_MBIT64(2, 2),
+
+
+	/**
+	 * Bit10: 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 = RTE_BIT64(10),
+
+	/**
+	 * Bit48-63: reserved for this library implement usage.
+	 */
+	RTE_ARGPARSE_ARG_RESERVED_FIELD = RTE_GENMASK64(63, 48),
+};
+
+/**
+ * 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 */
+	uint64_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;
+	/** Reserved field used for future extension. */
+	void *reserved[16];
+	/** 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',