blob: a4bd5916feb4aa3dee202bfaef3822b410405beb [file] [log] [blame]
/** @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