| /** @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; | |
| // zero or one argument | |
| constexpr unsigned AT_MOST_ONE_ARG_N = ~0 - 2; | |
| // customizable indent for help message | |
| constexpr int INDENT_ONE = 32; | |
| constexpr int INDENT_TWO = 46; | |
| /** Whether @a arg_num asks for a variable rather than a fixed number of values. | |
| Use this in preference to comparing against the sentinels, so that adding another | |
| variable arity does not silently leave a sentinel being treated as a literal count. | |
| */ | |
| constexpr bool | |
| is_variable_arg_num(unsigned arg_num) | |
| { | |
| return arg_num == MORE_THAN_ZERO_ARG_N || arg_num == MORE_THAN_ONE_ARG_N || arg_num == AT_MOST_ONE_ARG_N; | |
| } | |
| 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); | |
| /** Whether @a name has an entry. | |
| @return @c true when the command or option has been parsed. Unlike get(), the called | |
| flag is left alone, so this can be asked while parsing. | |
| */ | |
| bool has(std::string const &name) const noexcept; | |
| 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 collect the values of an option or command into @a ret. | |
| @param env_value The current value of the option's declared environment variable, or | |
| empty when none is declared or it is currently unset. A missing value is reported | |
| immediately whenever this is empty, matching behaviour with no variable support at all. | |
| @param was_bare Set to @c true when @a env_value is non-empty and the occurrence just | |
| parsed had no value at all; left @c false otherwise, including for @c | |
| AT_MOST_ONE_ARG_N, which fills immediately instead since repetition of that arity is | |
| refused outright elsewhere. The caller resolves the variable once every occurrence has | |
| been seen, so the result does not depend on the order occurrences appear in. | |
| */ | |
| std::string handle_args(Arguments &ret, AP_StrVec &args, std::string const &name, unsigned arg_num, unsigned &index, | |
| std::string const &env_value, bool &was_bare) const; | |
| // Whether @a token names an option registered on this command | |
| bool is_registered_option(std::string const &token) const; | |
| // 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; | |
| // Helper method to apply default values for options not explicitly set | |
| void apply_option_defaults(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 |