trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dra...@apache.org
Subject [trafficserver] branch master updated: ArgParser: 'ArgParser' Class for command line arguments parsing
Date Thu, 20 Sep 2018 18:19:29 GMT
This is an automated email from the ASF dual-hosted git repository.

dragon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new d9b06a1  ArgParser: 'ArgParser' Class for command line arguments parsing
d9b06a1 is described below

commit d9b06a12acf2a2ac97b9bc74a4eea6466d9d4bfe
Author: Xavier Chi <chitianhaoxp@gmail.com>
AuthorDate: Thu Sep 20 10:43:10 2018 -0500

    ArgParser: 'ArgParser' Class for command line arguments parsing
---
 .../internal-libraries/ArgParser.en.rst            | 352 ++++++++++++
 .../internal-libraries/index.en.rst                |   1 +
 include/tscore/ArgParser.h                         | 255 +++++++++
 src/tscore/ArgParser.cc                            | 611 +++++++++++++++++++++
 src/tscore/Makefile.am                             |   3 +
 src/tscore/unit_tests/test_ArgParser.cc            | 150 +++++
 6 files changed, 1372 insertions(+)

diff --git a/doc/developer-guide/internal-libraries/ArgParser.en.rst b/doc/developer-guide/internal-libraries/ArgParser.en.rst
new file mode 100644
index 0000000..2dcb463
--- /dev/null
+++ b/doc/developer-guide/internal-libraries/ArgParser.en.rst
@@ -0,0 +1,352 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. include:: ../../common.defs
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _ArgParser:
+
+ArgParser
+*********
+
+Synopsis
+++++++++
+
+.. code-block:: cpp
+
+   #include <ts/ArgParser.h>
+
+Description
++++++++++++
+
+:class:`ArgParser` is a powerful and easy-to-use command line Parsing library for ATS.
+The program defines what it requires by adding commands and options.
+Then :class:`ArgParser` will figure out the related information from the command line.
+All parsed arguments and function will be put in a key-value pairs structure
+:class:`Arguments` available for users to use.
+
+Usage
++++++
+
+The usage of the ArgParser is straightforward. The user is expected to create an
+ArgParser for the program. Commands and options can be added to the parser with details
+including ENV variable, arguments expected, etc. After a single method :code:`parse(argv)` is called,
+An object containing all information and parsed arguments available to use will be returned.
+
+Create a parser
+---------------
+The first step to use ArgParser is to create a parser.
+The parser can be created simply by calling:
+
+.. code-block:: cpp
+
+   ts::ArgParser parser;
+
+or initialize with the following arguments: 
+*name, help description, environment variable, argument number expected, function*
+
+.. code-block:: cpp
+
+   ts::ArgParser parser("name", "description", "ENV_VAR", 0, &function);
+
+To add the usage for the help message of this program:
+
+.. code-block:: cpp
+
+   parser.add_global_usage("traffic_blabla [--SWITCH]");
+
+Add commands and options
+------------------------
+
+We can perform all kinds of operations on the parser which is the :class:`ArgParser` or command which is a :class:`Command`.
+
+To add commands to the program or current command:
+
+.. code-block:: cpp
+
+   ts::ArgParser &command1 = parser.add_command("command1", "description");
+   command1.add_command("command2", "description");
+   command1.add_command("command3", "description", "ENV_VAR", 0);
+   command1.add_command("command4", "description", "ENV_VAR", 0, &function, "lookup_key");
+
+This function call returns the new :class:`Command` instance added.
+
+.. Note::
+
+   The 0 here is the number of arguments we expected. It can be also set to `MORE_THAN_ZERO_ARG_N` or `MORE_THAN_ONE_ARG_N`
+   to specify that this command expects all the arguments come later (more than zero or more than one).
+
+To add options to the parser or current command:
+
+.. code-block:: cpp
+
+    parser.add_option("--switch", "-s", "switch description");
+    command1.add_option("--switch", "-s", "switch description", "", 0);
+    command1.add_option("--switch", "-s", "switch description", "ENV_VAR", 1, "default", "lookup_key");
+
+This function call returns the new :class:`Option` instance. (0 is also number of arguments expected)
+
+We can also use the following chained way to add subcommand or option:
+
+.. code-block:: cpp
+
+    command.add_command("init", "description").add_command("subinit", "description");
+    command.add_command("remove", "description").add_option("-i", "--initoption");
+
+which is equivalent to
+
+.. code-block:: cpp
+
+    ts::ArgParser::Command &init_command = command.add_command("init", "description");
+    ts::ArgParser::Command &remove_command = command.add_command("remove", "description");
+    init_command.add_command("subinit", "description");
+    remove_command.add_option("-i", "--initoption");
+
+In this case, `subinit` is the subcommand of `init` and `--initoption` is a switch of command `remove`.
+
+
+Parsing Arguments
+-----------------
+
+:class:`ArgParser` parses arguments through the :code:`parse(argv)` method. This will inspect the command line
+and walk through it. A :class:`Arguments` object will be built up from attributes
+parsed out of the command line holding key-value pairs all the parsed data and the function.
+
+.. code-block:: cpp
+
+    Arguments args = parser.parse(argv);
+
+Invoke functions
+----------------
+
+To invoke the function associated with certain commands, we can perform this by simply calling :code:`invoke()`
+from the :class:`Arguments` object returned from the parsing. The function can be a lambda.
+
+.. code-block:: cpp
+
+    args.invoke();
+
+Help and Version messages
+-------------------------
+
+- Help message will be outputted when a wrong usage of the program is detected or `--help` option found.
+
+- Version message is defined unified in :code:`ArgParser::version_message()`.
+
+Classes
++++++++
+
+.. class:: ArgParser
+
+   .. function:: Option &add_option(std::string const &long_option, std::string const &short_option, std::string const &description, std::string const &envvar = "", unsigned arg_num = 0, std::string const &default_value = "", std::string const &key = "")
+
+      Add an option to current command with *long name*, *short name*, *help description*, *environment variable*, *arguments expected*, *default value* and *lookup key*. Return The Option object itself.
+
+   .. function:: Command &add_command(std::string const &cmd_name, std::string const &cmd_description, std::function<void()> const &f = nullptr, std::string const &key = "")
+
+      Add a command with only *name* and *description*, *function to invoke* and *lookup key*. Return the new :class:`Command` object.
+
+   .. function:: Command &add_command(std::string const &cmd_name, std::string const &cmd_description, std::string const &cmd_envvar, unsigned cmd_arg_num, std::function<void()> const &f = nullptr, std::string const &key = "")
+
+      Add a command with *name*, *description*, *environment variable*, *number of arguments expected*, *function to invoke* and *lookup key*.
+      The function can be passed by reference or be a lambda. It returns the new :class:`Command` object.
+
+   .. function:: void parse(const char **argv)
+
+      Parse the command line by calling :code:`parser.parse(argv)`. Return the new :class:`Arguments` instance.
+
+   .. function:: void help_message() const
+
+      Output usage to the console.
+
+   .. function:: void version_message() const
+
+      Output version string to the console.
+
+   .. function:: void add_global_usage(std::string const &usage)
+
+      Add a global_usage for :code:`help_message()`. Example: `traffic_blabla [--SWITCH [ARG]]`.
+
+   .. function:: Command &require_commands()
+
+      Make the parser require commands. If no command is found, output help message. Return The :class:`Command` instance for chained calls.
+
+   .. function:: void set_error(std::string e)
+
+      Set the user customized error message for the parser.
+
+   .. function:: std::string get_error() const
+
+      Return the error message of the parser.
+
+.. class:: Option
+
+   :class:`Option` is a data struct containing information about an option.
+
+.. code-block:: cpp
+
+   struct Option {
+      std::string long_option;   // long option: --arg
+      std::string short_option;  // short option: -a
+      std::string description;   // help description
+      std::string envvar;        // stored ENV variable
+      unsigned arg_num;          // number of argument expected
+      std::string default_value; // default value of option
+      std::string key;           // look-up key
+   };
+
+.. class:: Command
+
+   :class:`Command` is a nested structure of command for :class:`ArgParser`. The :code:`add_option()`, :code:`add_command()` and
+   :code:`require_command()` are the same with those in :class:`ArgParser`. When :code:`add_command()`
+   is called under certain command, it will be added as a subcommand for the current command. For Example, :code:`command1.add_command("command2", "description")`
+   will make :code:`command2` a subcommand of :code:`command1`. :code:`require_commands()` is also available within :class:`Command`.
+
+   .. function:: void add_example_usage(std::string const &usage)
+
+      Add an example usage for the command to output in `help_message`.
+      For Example: :code:`command.add_example_usage("traffic_blabla init --path=/path/to/file")`.
+
+.. class:: Arguments
+
+   :class:`Arguments` holds the parsed arguments and function to invoke.
+   It basically contains a function to invoke and a private map holding key-value pairs.
+   The key is the command or option name string and the value is the Parsed data object which
+   contains the environment variable and arguments that belong to this certain command or option.
+
+   .. function:: std::string get(std::string const &name)
+
+      Return the :class:`ArgumentData` object related to the name.
+
+   .. function:: std::string set_env(std::string const &key, std::string const &value)
+
+      Set the environment variable given `key`.
+
+   .. function:: void append(std::string const &key, ArgumentData const &value)
+
+      Append key-value pairs to the map in :class:`Arguments`.
+
+   .. function:: void append_arg(std::string const &key, std::string const &value)
+
+      Append `value` to the data of `key`.
+
+   .. function:: void show_all_configuration() const
+
+      Show all the called commands, options, and associated arguments.
+
+   .. function:: void invoke()
+
+      Invoke the function associated with the parsed command.
+
+   .. function:: void has_action() const
+
+      return true if there is any function to invoke.
+
+.. class:: ArgumentData
+
+   :class:`ArgumentData` is a struct containing the parsed Environment variable and command line arguments.
+   There are methods to get the data out of it.
+
+   Note: More methods can be added later to get specific types from the arguments.
+
+   .. function:: operator bool() const noexcept
+
+      `bool` for checking if certain command or option is called.
+
+   .. function:: std::string const &operator[](int x) const
+
+      Index accessing operator.
+
+   .. function:: std::string const &env() const noexcept
+
+      Return the environment variable associated with the argument.
+
+   .. function:: std::vector<std::string>::const_iterator begin() const noexcept
+
+      Begin iterator for iterating the arguments data.
+
+   .. function:: std::vector<std::string>::const_iterator end() const noexcept
+
+      End iterator for iterating the arguments data.
+
+   .. function:: std::string const &at(unsigned index) const
+
+      Index accessing method.
+
+   .. function:: std::string const &value() const noexcept
+
+      Return the first element of the arguments data.
+
+   .. function:: size_t size() const noexcept
+
+      Return the size of the arguments vector
+
+   .. function:: size_t empty() const noexcept
+
+      Return true if the arguments vector and env variable are both empty.
+
+Example
++++++++
+
+ArgParser
+---------
+
+Below is a short example of using the ArgParser. We add some options and some commands to it using different ways.
+This program will have such functionality:
+
+- ``--switch``, ``-s`` as a global switch.
+- Command ``func`` will call ``function()`` and this command takes 2 argument.
+- Command ``func2`` will call ``function2(int num)``.
+- Command ``init`` has subcommand ``subinit`` and option ``--path`` which take 1 argument.
+- Command ``remove`` has option ``--path`` which takes 1 argument and has ``HOME`` as the environment variable.
+
+.. code-block:: cpp
+
+    #include "ts/ArgParser.h"
+
+    void function() {
+        ...
+    }
+
+    void function2(int num) {
+        ...
+    }
+
+    int main (int, const char **argv) {
+        ts::ArgParser parser;
+        parser.add_global_usage("traffic_blabla [some stuff]");
+
+        parser.add_option("--switch", "-s", "top level switch");
+        parser.add_command("func", "some function", "ENV_VAR", 2, &function);
+        parser.add_command("func2", "some function2", [&]() { return function2(100); });
+
+        auto &init_command = parser.add_command("init", "initialize");
+        init_command.add_option("--path", "-p", "specify the path", "", 1);
+        init_command.add_command("subinit", "sub initialize");
+
+        parser.add_command("remove", "remove things").add_option("--path", "-p", "specify the path", "HOME", 1);
+        
+        ts::Arguments parsed_data = parser.parse(argv);
+        parsed_data.invoke();
+        ...
+    }
+
+Arguments
+---------
+To get the values from the arguments data, please refer to the methods in :class:`Arguments` and :class:`ArgumentData`
diff --git a/doc/developer-guide/internal-libraries/index.en.rst b/doc/developer-guide/internal-libraries/index.en.rst
index 0800562..81a7528 100644
--- a/doc/developer-guide/internal-libraries/index.en.rst
+++ b/doc/developer-guide/internal-libraries/index.en.rst
@@ -37,3 +37,4 @@ development team.
    MemArena.en
    AcidPtr.en
    Extendible.en
+   ArgParser.en
diff --git a/include/tscore/ArgParser.h b/include/tscore/ArgParser.h
new file mode 100644
index 0000000..b8a5e6a
--- /dev/null
+++ b/include/tscore/ArgParser.h
@@ -0,0 +1,255 @@
+/** @file
+
+  Powerful and easy-to-use command line parsing for ATS
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <map>
+#include <vector>
+#include <functional>
+#include <string_view>
+
+// more than zero arguments
+constexpr unsigned MORE_THAN_ZERO_ARG_N = ~0;
+// more than one arguments
+constexpr unsigned MORE_THAN_ONE_ARG_N = ~0 - 1;
+// customizable indent for help message
+constexpr int INDENT_ONE   = 24;
+constexpr int INDENT_TWO   = 32;
+constexpr int INDENT_THREE = 46;
+
+namespace ts
+{
+using AP_StrVec = std::vector<std::string>;
+// The class holding both the ENV and String arguments
+class ArgumentData
+{
+public:
+  // bool to check if certain command/option is called
+  operator bool() const noexcept { return _is_called; }
+  // index accessing []
+  std::string const &operator[](int x) const { return _values.at(x); }
+  // return the Environment variable
+  std::string const &env() const noexcept;
+  // iterator for arguments
+  AP_StrVec::const_iterator begin() const noexcept;
+  AP_StrVec::const_iterator end() const noexcept;
+  // index accessing
+  std::string const &at(unsigned index) const;
+  // access the first index, equivalent to at(0)
+  std::string const &value() const noexcept;
+  // size of _values
+  size_t size() const noexcept;
+  // return true if _values and _env_value are both empty
+  bool empty() const noexcept;
+
+private:
+  bool _is_called = false;
+  // the environment variable
+  std::string _env_value;
+  // the arguments stored
+  AP_StrVec _values;
+
+  friend class Arguments;
+};
+
+// The class holding all the parsed data after ArgParser::parse()
+class Arguments
+{
+public:
+  Arguments();
+  ~Arguments();
+
+  ArgumentData get(std::string const &name);
+
+  void append(std::string const &key, ArgumentData const &value);
+  // Append value to the arg to the map of key
+  void append_arg(std::string const &key, std::string const &value);
+  // append env value to the map with key
+  void set_env(std::string const &key, std::string const &value);
+  // Print all we have in the parsed data to the console
+  void show_all_configuration() const;
+  /** Invoke the function associated with the parsed command.
+      @return The return value of the executed command (int).
+  */
+  void invoke();
+  // return true if there is any function to invoke
+  bool has_action() const;
+
+private:
+  // A map of all the called parsed args/data
+  // Key: "command/option", value: ENV and args
+  std::map<std::string, ArgumentData> _data_map;
+  // The function associated. invoke() will call this func
+  std::function<void()> _action;
+
+  friend class ArgParser;
+  friend class ArgumentData;
+};
+
+// Class of the ArgParser
+class ArgParser
+{
+  using Function = std::function<void()>;
+
+public:
+  // Option structure: e.x. --arg -a
+  // Contains all information about certain option(--switch)
+  struct Option {
+    std::string long_option;   // long option: --arg
+    std::string short_option;  // short option: -a
+    std::string description;   // help description
+    std::string envvar;        // stored ENV variable
+    unsigned arg_num;          // number of argument expected
+    std::string default_value; // default value of option
+    std::string key;           // look-up key
+  };
+
+  // Class for commands in a nested way
+  class Command
+  {
+  public:
+    // Constructor and destructor
+    Command();
+    ~Command();
+    /** Add an option to current command
+        @return The Option object.
+    */
+    Command &add_option(std::string const &long_option, std::string const &short_option, std::string const &description,
+                        std::string const &envvar = "", unsigned arg_num = 0, std::string const &default_value = "",
+                        std::string const &key = "");
+
+    /** Two ways of adding a sub-command to current command:
+        @return The new sub-command instance.
+    */
+    Command &add_command(std::string const &cmd_name, std::string const &cmd_description, Function const &f = nullptr,
+                         std::string const &key = "");
+    Command &add_command(std::string const &cmd_name, std::string const &cmd_description, std::string const &cmd_envvar,
+                         unsigned cmd_arg_num, Function const &f = nullptr, std::string const &key = "");
+    /** Add an example usage of current command for the help message
+        @return The Command instance for chained calls.
+    */
+    Command &add_example_usage(std::string const &usage);
+    /** Require subcommand/options for this command
+        @return The Command instance for chained calls.
+    */
+    Command &require_commands();
+
+  protected:
+    // Main constructor called by add_command()
+    Command(std::string const &name, std::string const &description, std::string const &envvar, unsigned arg_num, Function const &f,
+            std::string const &key = "");
+    // Helper method for add_option to check the validity of option
+    void check_option(std::string const &long_option, std::string const &short_option, std::string const &key) const;
+    // Helper method for add_command to check the validity of command
+    void check_command(std::string const &name, std::string const &key) const;
+    // Helper method for ArgParser::help_message
+    void output_command(std::ostream &out, std::string const &prefix) const;
+    // Helper method for ArgParser::parse
+    bool parse(Arguments &ret, AP_StrVec &args);
+    // The help & version messages
+    void help_message(std::string_view err = "") const;
+    void version_message() const;
+    // Helpr method for parse()
+    void append_option_data(Arguments &ret, AP_StrVec &args, int index);
+    // The command name and help message
+    std::string _name;
+    std::string _description;
+
+    // Expected argument number
+    unsigned _arg_num = 0;
+    // Stored Env variable
+    std::string _envvar;
+    // An example usage can be added for the help message
+    std::string _example_usage;
+    // Function associated with this command
+    Function _f;
+    // look up key
+    std::string _key;
+
+    // list of all subcommands of current command
+    // Key: command name. Value: Command object
+    std::map<std::string, Command> _subcommand_list;
+    // list of all options of current command
+    // Key: option name. Value: Option object
+    std::map<std::string, Option> _option_list;
+    // Map for fast searching: <short option: long option>
+    std::map<std::string, std::string> _option_map;
+
+    // require command / option for this parser
+    bool _command_required = false;
+
+    friend class ArgParser;
+  };
+  // Base class constructors and destructor
+  ArgParser();
+  ArgParser(std::string const &name, std::string const &description, std::string const &envvar, unsigned arg_num,
+            Function const &f);
+  ~ArgParser();
+
+  /** Add an option to current command with arguments
+      @return The Option object.
+  */
+  Command &add_option(std::string const &long_option, std::string const &short_option, std::string const &description,
+                      std::string const &envvar = "", unsigned arg_num = 0, std::string const &default_value = "",
+                      std::string const &key = "");
+
+  /** Two ways of adding command to the parser:
+      @return The new command instance.
+  */
+  Command &add_command(std::string const &cmd_name, std::string const &cmd_description, Function const &f = nullptr,
+                       std::string const &key = "");
+  Command &add_command(std::string const &cmd_name, std::string const &cmd_description, std::string const &cmd_envvar,
+                       unsigned cmd_arg_num, Function const &f = nullptr, std::string const &key = "");
+
+  /** Main parsing function
+      @return The Arguments object available for program using
+  */
+  Arguments parse(const char **argv);
+  // Add the usage to global_usage for help_message(). Something like: traffic_blabla [--SWITCH [ARG]]
+  void add_global_usage(std::string const &usage);
+  // help message that can be called
+  void help_message(std::string_view err = "") const;
+  /** Require subcommand/options for this command
+      @return The Command instance for chained calls.
+  */
+  Command &require_commands();
+  // set the error message
+  void set_error(std::string e);
+  // get the error message
+  std::string get_error() const;
+
+protected:
+  // Converted from 'const char **argv' for the use of parsing and help
+  AP_StrVec _argv;
+  // the top level command object for program use
+  Command _top_level_command;
+  // user-customized error message output
+  std::string _error_msg;
+
+  friend class Command;
+  friend class Arguments;
+};
+
+} // namespace ts
diff --git a/src/tscore/ArgParser.cc b/src/tscore/ArgParser.cc
new file mode 100644
index 0000000..4f63479
--- /dev/null
+++ b/src/tscore/ArgParser.cc
@@ -0,0 +1,611 @@
+/** @file
+
+  Powerful and easy-to-use command line parsing for ATS
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "tscore/ArgParser.h"
+#include "tscore/ink_file.h"
+#include "tscore/I_Version.h"
+
+#include <iostream>
+#include <set>
+#include <sstream>
+
+std::string global_usage;
+
+namespace ts
+{
+ArgParser::ArgParser() {}
+
+ArgParser::ArgParser(std::string const &name, std::string const &description, std::string const &envvar, unsigned arg_num,
+                     Function const &f)
+{
+  // initialize _top_level_command according to the provided message
+  _top_level_command = ArgParser::Command(name, description, envvar, arg_num, f);
+}
+
+ArgParser::~ArgParser() {}
+
+// add new options with args
+ArgParser::Command &
+ArgParser::add_option(std::string const &long_option, std::string const &short_option, std::string const &description,
+                      std::string const &envvar, unsigned arg_num, std::string const &default_value, std::string const &key)
+{
+  return _top_level_command.add_option(long_option, short_option, description, envvar, arg_num, default_value, key);
+}
+
+// add sub-command with only function
+ArgParser::Command &
+ArgParser::add_command(std::string const &cmd_name, std::string const &cmd_description, Function const &f, std::string const &key)
+{
+  return _top_level_command.add_command(cmd_name, cmd_description, f, key);
+}
+
+// add sub-command without args and function
+ArgParser::Command &
+ArgParser::add_command(std::string const &cmd_name, std::string const &cmd_description, std::string const &cmd_envvar,
+                       unsigned cmd_arg_num, Function const &f, std::string const &key)
+{
+  return _top_level_command.add_command(cmd_name, cmd_description, cmd_envvar, cmd_arg_num, f, key);
+}
+
+void
+ArgParser::add_global_usage(std::string const &usage)
+{
+  global_usage = usage;
+}
+
+void
+ArgParser::help_message(std::string_view err) const
+{
+  return _top_level_command.help_message(err);
+}
+
+// handle the output of arguments for help message
+static std::string
+argument_number_output(unsigned num)
+{
+  std::string arg_num_msg;
+  if (num == 0) {
+    arg_num_msg = "";
+  } else if (num == MORE_THAN_ONE_ARG_N) {
+    arg_num_msg = "1+";
+  } else if (num == MORE_THAN_ZERO_ARG_N) {
+    arg_num_msg = "0+";
+  } else {
+    arg_num_msg = std::to_string(num);
+  }
+  return arg_num_msg;
+}
+
+// a graceful way to output help message
+void
+ArgParser::Command::help_message(std::string_view err) const
+{
+  if (!err.empty()) {
+    std::cout << "Error: " << err << std::endl;
+  }
+  // output global usage
+  if (global_usage.size() > 0) {
+    std::cout << "\nUsage: " + global_usage << std::endl;
+  }
+  // output subcommands
+  std::cout << "\nCommands -------------- Args -- Description -----------------------" << std::endl;
+  std::string prefix = "- ";
+  output_command(std::cout, prefix);
+  // output options
+  if (_option_list.size() > 0) {
+    std::cout << "\nOptions =============== Args == Default ===== Description =============" << std::endl;
+    for (auto it : _option_list) {
+      // nice formated way for output
+      std::string msg = it.first;
+      if (!it.second.short_option.empty()) {
+        msg = msg + ", " + it.second.short_option;
+      }
+      if (INDENT_ONE - static_cast<int>(msg.size()) < 0) {
+        msg = msg.substr(0, INDENT_ONE - 3) + "...";
+      }
+      msg = msg + std::string(INDENT_ONE - msg.size(), ' ') + argument_number_output(it.second.arg_num);
+      if (INDENT_TWO - static_cast<int>(msg.size()) < 0) {
+        msg = msg.substr(0, INDENT_TWO - 3) + "...";
+      }
+      msg = msg + std::string(INDENT_TWO - msg.size(), ' ') + it.second.default_value;
+      if (INDENT_THREE - static_cast<int>(msg.size()) < 0) {
+        msg = msg.substr(0, INDENT_THREE - 3) + "...";
+      }
+      std::cout << msg << std::string(INDENT_THREE - msg.size(), ' ') << it.second.description << std::endl;
+    }
+  }
+  // output example usage
+  if (!_example_usage.empty()) {
+    std::cout << "\nExample Usage: " << _example_usage << std::endl;
+  }
+  exit(0);
+}
+
+void
+ArgParser::Command::version_message() const
+{
+  // unified version message of ATS
+  AppVersionInfo appVersionInfo;
+  appVersionInfo.setup(PACKAGE_NAME, _name.c_str(), PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
+  ink_fputln(stdout, appVersionInfo.FullVersionInfoStr);
+  exit(0);
+}
+
+// Top level call of parsing
+Arguments
+ArgParser::parse(const char **argv)
+{
+  // deal with argv first
+  int size = 0;
+  _argv.clear();
+  while (argv[size]) {
+    _argv.push_back(argv[size]);
+    size++;
+  }
+  if (size == 0) {
+    std::cout << "Error: invalid argv provided" << std::endl;
+    exit(1);
+  }
+  // the name of the program only
+  _argv[0]                 = _argv[0].substr(_argv[0].find_last_of('/') + 1);
+  _top_level_command._name = _argv[0];
+  _top_level_command._key  = _argv[0];
+  Arguments ret; // the parsed arg object to return
+  AP_StrVec args = _argv;
+  // call the recrusive parse method in Command
+  _top_level_command.parse(ret, args);
+  // if there is anything left, then output usage
+  if (!args.empty()) {
+    std::string msg = "Unkown command, option or args:";
+    for (auto it : args) {
+      msg = msg + " '" + it + "'";
+    }
+    _top_level_command.help_message(msg);
+  }
+  return ret;
+}
+
+ArgParser::Command &
+ArgParser::require_commands()
+{
+  return _top_level_command.require_commands();
+}
+
+void
+ArgParser::set_error(std::string e)
+{
+  _error_msg = e;
+}
+
+std::string 
+ArgParser::get_error() const
+{
+  return _error_msg;
+}
+
+//=========================== Command class ================================
+ArgParser::Command::Command() {}
+
+ArgParser::Command::~Command() {}
+
+ArgParser::Command::Command(std::string const &name, std::string const &description, std::string const &envvar, unsigned arg_num,
+                            Function const &f, std::string const &key)
+  : _name(name), _description(description), _arg_num(arg_num), _envvar(envvar), _f(f), _key(key)
+{
+}
+
+// check if this is a valid option before adding
+void
+ArgParser::Command::check_option(std::string const &long_option, std::string const &short_option, std::string const &key) const
+{
+  if (long_option.size() < 3 || long_option[0] != '-' || long_option[1] != '-') {
+    // invalid name
+    std::cerr << "Error: invalid long option added: '" + long_option + "'" << std::endl;
+    exit(1);
+  }
+  if (short_option.size() > 2 || (short_option.size() > 0 && short_option[0] != '-')) {
+    // invalid short option
+    std::cerr << "Error: invalid short option added: '" + short_option + "'" << std::endl;
+    exit(1);
+  }
+  // find if existing in option list
+  if (_option_list.find(long_option) != _option_list.end()) {
+    std::cerr << "Error: long option '" + long_option + "' already existed" << std::endl;
+    exit(1);
+  } else if (_option_map.find(short_option) != _option_map.end()) {
+    std::cerr << "Error: short option '" + short_option + "' already existed" << std::endl;
+    exit(1);
+  }
+}
+
+// check if this is a valid command before adding
+void
+ArgParser::Command::check_command(std::string const &name, std::string const &key) const
+{
+  if (name.empty()) {
+    // invalid name
+    std::cerr << "Error: empty command cannot be added" << std::endl;
+    exit(1);
+  }
+  // find if existing in subcommand list
+  if (_subcommand_list.find(name) != _subcommand_list.end()) {
+    std::cerr << "Error: command already exists: '" + name + "'" << std::endl;
+    exit(1);
+  }
+}
+
+// add new options with args
+ArgParser::Command &
+ArgParser::Command::add_option(std::string const &long_option, std::string const &short_option, std::string const &description,
+                               std::string const &envvar, unsigned arg_num, std::string const &default_value,
+                               std::string const &key)
+{
+  std::string lookup_key = key.empty() ? long_option.substr(2) : key;
+  check_option(long_option, short_option, lookup_key);
+  _option_list[long_option] = {long_option, short_option == "-" ? "" : short_option, description, envvar, arg_num, default_value,
+                               lookup_key};
+  if (short_option != "-" && !short_option.empty()) {
+    _option_map[short_option] = long_option;
+  }
+  return *this;
+}
+
+// add sub-command with only function
+ArgParser::Command &
+ArgParser::Command::add_command(std::string const &cmd_name, std::string const &cmd_description, Function const &f,
+                                std::string const &key)
+{
+  std::string lookup_key = key.empty() ? cmd_name : key;
+  check_command(cmd_name, lookup_key);
+  _subcommand_list[cmd_name] = ArgParser::Command(cmd_name, cmd_description, "", 0, f, lookup_key);
+  return _subcommand_list[cmd_name];
+}
+
+// add sub-command without args and function
+ArgParser::Command &
+ArgParser::Command::add_command(std::string const &cmd_name, std::string const &cmd_description, std::string const &cmd_envvar,
+                                unsigned cmd_arg_num, Function const &f, std::string const &key)
+{
+  std::string lookup_key = key.empty() ? cmd_name : key;
+  check_command(cmd_name, lookup_key);
+  _subcommand_list[cmd_name] = ArgParser::Command(cmd_name, cmd_description, cmd_envvar, cmd_arg_num, f, lookup_key);
+  return _subcommand_list[cmd_name];
+}
+
+ArgParser::Command &
+ArgParser::Command::add_example_usage(std::string const &usage)
+{
+  _example_usage = usage;
+  return *this;
+}
+
+// method used by help_message()
+void
+ArgParser::Command::output_command(std::ostream &out, std::string const &prefix) const
+{
+  // a nicely formated way to output command usage
+  std::string msg = prefix + _name;
+  if (INDENT_ONE - static_cast<int>(msg.size()) < 0) {
+    msg = msg.substr(0, INDENT_ONE - 3) + "...";
+  }
+  msg = msg + std::string(INDENT_ONE - msg.size(), ' ') + argument_number_output(_arg_num);
+  if (INDENT_TWO - static_cast<int>(msg.size()) < 0) {
+    msg = msg.substr(0, INDENT_TWO - 3) + "...";
+  }
+  // output with a nice format
+  std::cout << msg << std::string(INDENT_TWO - msg.size(), ' ') << _description << std::endl;
+  // recursive call
+  for (auto it : _subcommand_list) {
+    it.second.output_command(out, "  " + prefix);
+  }
+}
+
+// helper method to handle the arguments and put them nicely in arguments
+// can be switched to ts::errata
+static std::string
+handle_args(Arguments &ret, AP_StrVec &args, std::string const &name, unsigned arg_num, unsigned &index)
+{
+  ArgumentData data;
+  ret.append(name, data);
+  // handle the args
+  if (arg_num == MORE_THAN_ZERO_ARG_N || arg_num == MORE_THAN_ONE_ARG_N) {
+    // infinite arguments
+    if (arg_num == MORE_THAN_ONE_ARG_N && args.size() <= index + 1) {
+      return "at least one argument expected by " + name;
+    }
+    for (unsigned j = index + 1; j < args.size(); j++) {
+      ret.append_arg(name, args[j]);
+    }
+    args.erase(args.begin() + index, args.end());
+    return "";
+  }
+  // finite number of argument handling
+  for (unsigned j = 0; j < arg_num; j++) {
+    if (args.size() < index + j + 2 || args[index + j + 1].empty()) {
+      return std::to_string(arg_num) + " argument(s) expected by " + name;
+    }
+    ret.append_arg(name, args[index + j + 1]);
+  }
+  // erase the used arguments and append the data to the return structure
+  args.erase(args.begin() + index, args.begin() + index + arg_num + 1);
+  index -= 1;
+  return "";
+}
+
+// Append the args of option to parsed data. Return true if there is any option called
+void
+ArgParser::Command::append_option_data(Arguments &ret, AP_StrVec &args, int index)
+{
+  std::map<std::string, unsigned> check_map;
+  for (unsigned i = index; i < args.size(); i++) {
+    // find matches of the arg
+    if (args[i][0] == '-' && args[i][1] == '-' && args[i].find('=') != std::string::npos) {
+      // deal with --args=
+      std::string option_name = args[i].substr(0, args[i].find_first_of('='));
+      std::string value       = args[i].substr(args[i].find_last_of('=') + 1);
+      if (value.empty()) {
+        help_message("missing argument for '" + option_name + "'");
+      }
+      auto it = _option_list.find(option_name);
+      if (it != _option_list.end()) {
+        ArgParser::Option cur_option = it->second;
+        // handle environment variable
+        if (!cur_option.envvar.empty()) {
+          ret.set_env(cur_option.key, getenv(cur_option.envvar.c_str()) ? getenv(cur_option.envvar.c_str()) : "");
+        }
+        ret.append_arg(cur_option.key, value);
+        check_map[cur_option.long_option] += 1;
+        args.erase(args.begin() + i);
+        i -= 1;
+      }
+    } else {
+      // output version message
+      if (args[i] == "--version" || args[i] == "-V") {
+        version_message();
+      }
+      // output help message
+      if (args[i] == "--help" || args[i] == "-h") {
+        ArgParser::Command *command = this;
+        // find the correct level to output help messsage
+        for (unsigned i = 1; i < args.size(); i++) {
+          auto it = command->_subcommand_list.find(args[i]);
+          if (it == command->_subcommand_list.end()) {
+            break;
+          }
+          command = &it->second;
+        }
+        command->help_message();
+      }
+      // deal with normal --arg val1 val2 ...
+      auto long_it  = _option_list.find(args[i]);
+      auto short_it = _option_map.find(args[i]);
+      // long option match or short option match
+      if (long_it != _option_list.end() || short_it != _option_map.end()) {
+        ArgParser::Option cur_option;
+        if (long_it != _option_list.end()) {
+          cur_option = long_it->second;
+        } else {
+          cur_option = _option_list.at(short_it->second);
+        }
+        // handle the arguments
+        std::string err = handle_args(ret, args, cur_option.key, cur_option.arg_num, i);
+        if (!err.empty()) {
+          help_message(err);
+        }
+        // handle environment variable
+        if (!cur_option.envvar.empty()) {
+          ret.set_env(cur_option.key, getenv(cur_option.envvar.c_str()) ? getenv(cur_option.envvar.c_str()) : "");
+        }
+      }
+    }
+  }
+  // check for wrong number of arguments for --arg=...
+  for (auto it : check_map) {
+    unsigned num = _option_list.at(it.first).arg_num;
+    if (num != it.second && num < MORE_THAN_ONE_ARG_N) {
+      help_message(std::to_string(_option_list.at(it.first).arg_num) + " arguments expected by " + it.first);
+    }
+  }
+  // put in the default value of options
+  for (auto it : _option_list) {
+    if (!it.second.default_value.empty() && ret.get(it.second.key).empty()) {
+      std::istringstream ss(it.second.default_value);
+      std::string token;
+      while (std::getline(ss, token, ' ')) {
+        ret.append_arg(it.second.key, token);
+      }
+    }
+  }
+}
+
+// Main recursive logic of Parsing
+bool
+ArgParser::Command::parse(Arguments &ret, AP_StrVec &args)
+{
+  bool command_called = false;
+  // iterate through all arguments
+  for (unsigned i = 0; i < args.size(); i++) {
+    if (_name == args[i]) {
+      command_called = true;
+      // handle the option
+      append_option_data(ret, args, i);
+      // handle the action
+      if (_f) {
+        ret._action = _f;
+      }
+      std::string err = handle_args(ret, args, _key, _arg_num, i);
+      if (!err.empty()) {
+        help_message(err);
+      }
+      // set ENV var
+      if (!_envvar.empty()) {
+        ret.set_env(_key, getenv(_envvar.c_str()) ? getenv(_envvar.c_str()) : "");
+      }
+      break;
+    }
+  }
+  if (command_called) {
+    bool flag = false;
+    // recursively call subcommand
+    for (auto it : _subcommand_list) {
+      if (it.second.parse(ret, args)) {
+        flag = true;
+        break;
+      }
+    }
+    // check for command required
+    if (!flag && _command_required) {
+      help_message("No subcommand found for " + _name);
+    }
+  }
+  return command_called;
+}
+
+ArgParser::Command &
+ArgParser::Command::require_commands()
+{
+  _command_required = true;
+  return *this;
+}
+
+//=========================== Arguments class ================================
+
+Arguments::Arguments() {}
+Arguments::~Arguments() {}
+
+ArgumentData
+Arguments::get(std::string const &name)
+{
+  if (_data_map.find(name) != _data_map.end()) {
+    _data_map[name]._is_called = true;
+    return _data_map[name];
+  }
+  return ArgumentData();
+}
+
+void
+Arguments::append(std::string const &key, ArgumentData const &value)
+{
+  // perform overwrite for now
+  _data_map[key] = value;
+}
+
+void
+Arguments::append_arg(std::string const &key, std::string const &value)
+{
+  _data_map[key]._values.push_back(value);
+}
+
+void
+Arguments::set_env(std::string const &key, std::string const &value)
+{
+  // perform overwrite for now
+  _data_map[key]._env_value = value;
+}
+
+void
+Arguments::show_all_configuration() const
+{
+  for (auto it : _data_map) {
+    std::cout << "name: " + it.first << std::endl;
+    std::string msg;
+    msg = "args value:";
+    for (auto it_data : it.second._values) {
+      msg += " " + it_data;
+    }
+    std::cout << msg << std::endl;
+    std::cout << "env value: " + it.second._env_value << std::endl << std::endl;
+  }
+}
+
+// invoke the function with the args
+void
+Arguments::invoke()
+{
+  if (_action) {
+    // call the std::function
+    _action();
+  } else {
+    throw std::runtime_error("no function to invoke");
+  }
+}
+
+bool
+Arguments::has_action() const
+{
+  return _action != nullptr;
+}
+
+//=========================== ArgumentData class ================================
+
+std::string const &
+ArgumentData::env() const noexcept
+{
+  return _env_value;
+}
+
+std::string const &
+ArgumentData::at(unsigned index) const
+{
+  if (index >= _values.size()) {
+    throw std::out_of_range("argument not fonud at index: " + std::to_string(index));
+  }
+  return _values.at(index);
+}
+
+std::string const &
+ArgumentData::value() const noexcept
+{
+  if (_values.empty()) {
+    // To prevent compiler warning
+    static const std::string empty = "";
+    return empty;
+  }
+  return _values.at(0);
+}
+
+size_t
+ArgumentData::size() const noexcept
+{
+  return _values.size();
+}
+
+bool
+ArgumentData::empty() const noexcept
+{
+  return _values.empty() && _env_value.empty();
+}
+
+AP_StrVec::const_iterator
+ArgumentData::begin() const noexcept
+{
+  return _values.begin();
+}
+
+AP_StrVec::const_iterator
+ArgumentData::end() const noexcept
+{
+  return _values.end();
+}
+
+} // namespace ts
diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am
index 3d1fab3..aa1724f 100644
--- a/src/tscore/Makefile.am
+++ b/src/tscore/Makefile.am
@@ -54,6 +54,8 @@ libtscore_la_SOURCES = \
 	AcidPtr.h \
 	Arena.cc \
 	Arena.h \
+	ArgParser.cc \
+	ArgParser.h \
 	BaseLogFile.cc \
 	BaseLogFile.h \
 	Bitops.cc \
@@ -256,6 +258,7 @@ test_tscore_LDADD = libtscore.la $(top_builddir)/src/tscpp/util/libtscpputil.la
 test_tscore_SOURCES = \
 	unit_tests/unit_test_main.cc \
 	unit_tests/test_AcidPtr.cc \
+	unit_tests/test_ArgParser.cc \
 	unit_tests/test_BufferWriter.cc \
 	unit_tests/test_BufferWriterFormat.cc \
 	unit_tests/test_Extendible.cc \
diff --git a/src/tscore/unit_tests/test_ArgParser.cc b/src/tscore/unit_tests/test_ArgParser.cc
new file mode 100644
index 0000000..8405bcf
--- /dev/null
+++ b/src/tscore/unit_tests/test_ArgParser.cc
@@ -0,0 +1,150 @@
+/** @file
+
+  Unit test for ArgParser
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "catch.hpp"
+#include "tscore/ArgParser.h"
+
+int global;
+ts::ArgParser parser;
+ts::ArgParser parser2;
+
+TEST_CASE("Parsing test", "[parse]")
+{
+  // initialize and construct the parser
+  parser.add_global_usage("traffic_blabla [--SWITCH]");
+
+  setenv("ENV_TEST", "env_test", 0);
+  setenv("ENV_TEST2", "env_test2", 0);
+  parser.add_option("--globalx", "-x", "global switch x", "ENV_TEST", 2, "", "globalx_key");
+  parser.add_option("--globaly", "-y", "global switch y", "", 2, "default1 default2");
+  parser.add_option("--globalz", "-z", "global switch z", "", MORE_THAN_ONE_ARG_N);
+
+  ts::ArgParser::Command &init_command   = parser.add_command("init", "initialize traffic blabla", "ENV_TEST2", 1, nullptr);
+  ts::ArgParser::Command &remove_command = parser.add_command("remove", "remove traffic blabla");
+
+  init_command.add_option("--initoption", "-i", "init option");
+  init_command.add_option("--initoption2", "-j", "init2 option", "", 1, "");
+  init_command.add_command("subinit", "sub initialize traffic blabla", "", 2, nullptr, "subinit_key")
+    .add_option("--subinitopt", "-s", "sub init option");
+
+  remove_command.add_command("subremove", "sub remove traffic blabla").add_command("subsubremove", "sub sub remove");
+
+  ts::Arguments parsed_data;
+
+  // first run of arguments
+  const char *argv1[] = {"traffic_blabla", "init", "a", "--initoption", "--globalx", "x", "y", NULL};
+  parsed_data         = parser.parse(argv1);
+
+  REQUIRE(parsed_data.get("init") == true);
+  REQUIRE(parsed_data.get("init").env() == "env_test2");
+  REQUIRE(parsed_data.get("globalx_key") == true);
+  REQUIRE(parsed_data.get("globalx_key").env() == "env_test");
+  REQUIRE(parsed_data.get("globaly") == true);
+  REQUIRE(parsed_data.get("globaly").size() == 2);
+  REQUIRE(parsed_data.get("globaly").value() == "default1");
+  REQUIRE(parsed_data.get("globaly").at(1) == "default2");
+  REQUIRE(parsed_data.get("initoption") == true);
+  REQUIRE(parsed_data.get("a") == false);
+  REQUIRE(parsed_data.get("init").env().size() != 0);
+  REQUIRE(parsed_data.get("init").size() == 1);
+  REQUIRE(parsed_data.get("init").at(0) == "a");
+  REQUIRE(parsed_data.get("globalx_key").size() == 2);
+  REQUIRE(parsed_data.get("globalx_key").value() == "x");
+  REQUIRE(parsed_data.get("globalx_key")[1] == "y");
+
+  // second run of arguments
+  const char *argv2[] = {"traffic_blabla",    "init",         "i",  "subinit", "a",  "b",
+                         "--initoption2=abc", "--subinitopt", "-y", "y1",      "y2", NULL};
+
+  parsed_data = parser.parse(argv2);
+  REQUIRE(parsed_data.get("init") == true);
+  REQUIRE(parsed_data.get("subinitopt") == true);
+  REQUIRE(parsed_data.get("globaly") == true);
+  REQUIRE(parsed_data.get("globaly").size() == 2);
+  REQUIRE(parsed_data.get("globaly")[0] == "y1");
+  REQUIRE(parsed_data.get("globaly")[1] == "y2");
+  REQUIRE(parsed_data.get("subinit_key").size() == 2);
+  REQUIRE(parsed_data.get("subinit").size() == false);
+  REQUIRE(parsed_data.get("initoption2").size() == 1);
+  REQUIRE(parsed_data.get("initoption2")[0] == "abc");
+
+  // third run of arguments
+  const char *argv3[] = {"traffic_blabla", "-x",           "abc",          "xyz",          "remove", "subremove",
+                         "subsubremove",   "--globalz=z1", "--globalz=z2", "--globalz=z3", NULL};
+
+  parsed_data = parser.parse(argv3);
+  REQUIRE(parsed_data.has_action() == false);
+  REQUIRE(parsed_data.get("remove") == true);
+  REQUIRE(parsed_data.get("subremove") == true);
+  REQUIRE(parsed_data.get("subsubremove") == true);
+  REQUIRE(parsed_data.get("globalx_key").size() == 2);
+  REQUIRE(parsed_data.get("globalz").size() == 3);
+}
+
+void
+test_method_1()
+{
+  global = 0;
+  parser2.set_error("error");
+  return;
+}
+
+void
+test_method_2(int num)
+{
+  if (num == 1) {
+    global = 1;
+  } else {
+    global = 2;
+  }
+}
+
+TEST_CASE("Invoke test", "[invoke]")
+{
+  int num = 1;
+
+  parser2.add_global_usage("traffic_blabla [--SWITCH]");
+  // function by reference
+  parser2.add_command("func", "some test function 1", "", 0, &test_method_1);
+  // lambda
+  parser2.add_command("func2", "some test function 2", "", 0, [&]() { return test_method_2(num); });
+
+  ts::Arguments parsed_data;
+
+  const char *argv1[] = {"traffic_blabla", "func", NULL};
+
+  parsed_data = parser2.parse(argv1);
+  REQUIRE(parsed_data.has_action() == true);
+  parsed_data.invoke();
+  REQUIRE(global == 0);
+  REQUIRE(parser2.get_error() == "error");
+
+  const char *argv2[] = {"traffic_blabla", "func2", NULL};
+
+  parsed_data = parser2.parse(argv2);
+  parsed_data.invoke();
+  REQUIRE(global == 1);
+  num = 3;
+  parsed_data.invoke();
+  REQUIRE(global == 2);
+}


Mime
View raw message