| /** @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 = 32; |
| constexpr int INDENT_TWO = 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(const Arguments &) = default; |
| Arguments(Arguments &&) = default; |
| Arguments &operator=(const Arguments &) = default; |
| Arguments &operator=(Arguments &&) = default; |
| ~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 |
| }; |
| |
| // Mutually exclusive group structure |
| // Options in the same group cannot be used together |
| struct MutexGroup { |
| std::string name; // group identifier |
| std::vector<std::string> options; // list of long option names in this group |
| bool required{false}; // if true, one option from group must be specified |
| std::string description; // optional description for help message |
| |
| MutexGroup(std::string const &n, bool req = false, std::string const &desc = "") : name(n), required(req), description(desc) {} |
| }; |
| |
| // Class for commands in a nested way |
| class Command |
| { |
| public: |
| // Constructor and destructor |
| Command(); |
| Command(const Command &) = default; |
| Command(Command &&) = default; |
| Command &operator=(const Command &) = default; |
| Command &operator=(Command &&) = default; |
| ~Command(); |
| /** Add an option to current command |
| @return The Command object for chaining. |
| */ |
| 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 = ""); |
| |
| /** Create a mutually exclusive group of options |
| @param group_name Identifier for the group |
| @return The Command object for chaining. |
| */ |
| Command &add_mutex_group(std::string const &group_name, bool required = false, std::string const &description = ""); |
| |
| /** Add an option to a mutually exclusive group |
| @param group_name The group to add the option to |
| @return The Command object for chaining. |
| */ |
| Command &add_option_to_group(std::string const &group_name, 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 = ""); |
| |
| /** Specify that the last added option requires another option to be present. |
| Must be called immediately after add_option() or add_option_to_group(). |
| @param required_option The option that must be present (e.g., "--tags") |
| @return The Command instance for chained calls. |
| */ |
| Command &with_required(std::string const &required_option); |
| |
| /** 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(); |
| /** set the current command as default |
| @return The Command instance for chained calls. |
| */ |
| Command &set_default(); |
| |
| 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::help_message |
| void output_option() 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; |
| // Helper method for parse() |
| void append_option_data(Arguments &ret, AP_StrVec &args, int index); |
| // Helper method to validate mutually exclusive groups |
| void validate_mutex_groups(Arguments &ret) const; |
| // Helper method to validate option dependencies |
| void validate_dependencies(Arguments &ret) const; |
| // 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; |
| // Example usages can be added for the help message |
| std::vector<std::string> _example_usages; |
| // 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; |
| |
| // Mutually exclusive groups |
| // Key: group name. Value: MutexGroup object |
| std::map<std::string, MutexGroup> _mutex_groups; |
| // Map option to its mutex group (for fast lookup during validation) |
| // Key: long option name. Value: group name |
| std::map<std::string, std::string> _option_to_group; |
| |
| // Option dependencies: dependent_option -> list of required options |
| std::map<std::string, std::vector<std::string>> _option_dependencies; |
| // Track the last added option for with_required() chaining |
| std::string _last_added_option; |
| |
| // 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 Command object for chaining. |
| */ |
| 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 = ""); |
| |
| /** Create a mutually exclusive group of options |
| @param group_name Identifier for the group |
| @return The Command object for chaining. |
| */ |
| Command &add_mutex_group(std::string const &group_name, bool required = false, std::string const &description = ""); |
| |
| /** Add an option to a mutually exclusive group |
| @param group_name The group to add the option to |
| @return The Command object for chaining. |
| */ |
| Command &add_option_to_group(std::string const &group_name, 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 = ""); |
| // give a default command to this parser |
| void set_default_command(std::string const &cmd); |
| /** 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; |
| |
| // Add App's description. |
| void add_description(std::string const &descr); |
| |
| protected: |
| // Exit handler - throws in test mode, calls exit() otherwise |
| static void do_exit(int code); |
| // Enable test mode - makes do_exit() throw instead of calling exit() for unit testing |
| static void set_test_mode(bool test = true); |
| |
| // When true, do_exit() throws instead of calling exit() |
| static bool _test_mode; |
| |
| // 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 |