diff --git a/source/cppassist/CMakeLists.txt b/source/cppassist/CMakeLists.txt index 89e05c5..5a87b6f 100644 --- a/source/cppassist/CMakeLists.txt +++ b/source/cppassist/CMakeLists.txt @@ -101,7 +101,6 @@ set(headers ${include_path}/cmdline/ArgumentParser.h ${include_path}/cmdline/ArgumentParser.inl ${include_path}/cmdline/CommandLineProgram.h - ${include_path}/cmdline/CommandLineAction.h ${include_path}/cmdline/CommandLineCommand.h ${include_path}/cmdline/CommandLineOption.h ${include_path}/cmdline/CommandLineSwitch.h @@ -130,7 +129,6 @@ set(sources ${source_path}/cmdline/ArgumentParser.cpp ${source_path}/cmdline/CommandLineProgram.cpp - ${source_path}/cmdline/CommandLineAction.cpp ${source_path}/cmdline/CommandLineCommand.cpp ${source_path}/cmdline/CommandLineOption.cpp ${source_path}/cmdline/CommandLineSwitch.cpp diff --git a/source/cppassist/include/cppassist/cmdline/CommandLineAction.h b/source/cppassist/include/cppassist/cmdline/CommandLineAction.h deleted file mode 100644 index 79186e3..0000000 --- a/source/cppassist/include/cppassist/cmdline/CommandLineAction.h +++ /dev/null @@ -1,388 +0,0 @@ - -#pragma once - - -#include <string> -#include <vector> - -#include <cppassist/cppassist_api.h> - - -namespace cppassist -{ - - -class CommandLineCommand; -class CommandLineOption; -class CommandLineSwitch; -class CommandLineParameter; - - -/** -* @brief -* Command line action -* -* Specifies an action that can be invoked on the command line. -* An action is defined by a set of items (commands, options, -* switches, and parameters), which may be present on the command line. -* If all non-optional commands, switches, and options are found, -* the action is invoked. -* -* Example: -* `myapp list <category> [<type>] [--show-details]` -* -* @code -* CommandLineAction actionList("list", "List objects"); -* -* CommandLineCommand cmdList("list") -* actionList.add(&cmdList); -* -* CommandLineParameter paramCategory( -* "category", -* CommandLineParameter::NonOptional -* ); -* actionList.add(¶mCategory); -* -* CommandLineParameter paramType("type", CommandLineParameter::Optional); -* actionList.add(¶mType); -* -* CommandLineSwitch swHelp( -* "--show-details", "-d", -* "Show detailed object information", -* CommandLineSwitch::Optional -* ); -* actionList.add(&swHelp); -* @endcode -*/ -class CPPASSIST_API CommandLineAction -{ -public: - /** - * @brief - * Constructor - * - * @param[in] name - * Program name - * @param[in] description - * Description text - */ - CommandLineAction(const std::string & name = "", const std::string & description = ""); - - /** - * @brief - * Destructor - */ - virtual ~CommandLineAction(); - - /** - * @brief - * Get action name - * - * @return - * Action name - */ - const std::string & name() const; - - /** - * @brief - * Set action name - * - * @param[in] name - * Action name - */ - void setName(const std::string & name); - - /** - * @brief - * Get description - * - * @return - * Description text - */ - const std::string & description() const; - - /** - * @brief - * Set description - * - * @param[in] description - * Description text - */ - void setDescription(const std::string & description); - - /** - * @brief - * Get commands - * - * @return - * List of commands - */ - const std::vector<CommandLineCommand *> & commands() const; - - /** - * @brief - * Get command by name - * - * @param[in] name - * Command name - * - * @return - * Pointer to command, null on error - */ - CommandLineCommand * getCommand(const std::string & name) const; - - /** - * @brief - * Add command - * - * @param[in] command - * Command line command (must NOT be null!) - */ - void add(CommandLineCommand * command); - - /** - * @brief - * Get options - * - * @return - * List of options - */ - const std::vector<CommandLineOption *> & options() const; - - /** - * @brief - * Get option by name - * - * @param[in] name - * Option name - * - * @return - * Pointer to option, null on error - */ - CommandLineOption * getOption(const std::string & name) const; - - /** - * @brief - * Add option - * - * @param[in] option - * Command line option (must NOT be null!) - */ - void add(CommandLineOption * option); - - /** - * @brief - * Get switches - * - * @return - * List of switches - */ - const std::vector<CommandLineSwitch *> & switches() const; - - /** - * @brief - * Get switch by name - * - * @param[in] name - * Switch name - * - * @return - * Pointer to switch, null on error - */ - CommandLineSwitch * getSwitch(const std::string & name) const; - - /** - * @brief - * Add switch - * - * @param[in] sw - * Command line switch (must NOT be null!) - */ - void add(CommandLineSwitch * sw); - - /** - * @brief - * Get parameters - * - * @return - * List of parameters - */ - const std::vector<CommandLineParameter *> & parameters() const; - - /** - * @brief - * Get parameter by name - * - * @param[in] name - * Parameter name - * - * @return - * Pointer to parameter, null on error - */ - CommandLineParameter * getParameter(const std::string & name) const; - - /** - * @brief - * Get parameter by index - * - * @param[in] index - * Parameter index - * - * @return - * Pointer to parameter, null on error - */ - CommandLineParameter * getParameter(size_t index) const; - - /** - * @brief - * Add parameter - * - * @param[in] parameter - * Command line parameter (must NOT be null!) - */ - void add(CommandLineParameter * parameter); - - /** - * @brief - * Check if optional parameters are allowed by the action - * - * @return - * `true` if optional parameters are allowed, else `false` - */ - bool optionalParametersAllowed() const; - - /** - * @brief - * Set if optional parameters are allowed by the action - * - * @param[in] allowed - * `true` if optional parameters are allowed, else `false` - */ - void setOptionalParametersAllowed(bool allowed); - - /** - * @brief - * Get optional parameter name - * - * @return - * Parameter name (e.g., "path") - */ - const std::string & optionalParameterName() const; - - /** - * @brief - * Set optional parameter name - * - * @param[in] name - * Parameter name (e.g., "path") - */ - void setOptionalParameterName(const std::string & name); - - /** - * @brief - * Get usage text - * - * @return - * Usage text - */ - std::string usage() const; - - /** - * @brief - * Reset state (forget all information from previous parsings) - */ - void reset(); - - /** - * @brief - * Parse command line - * - * @param[in] argc - * Number of arguments - * @param[in] argv - * List of arguments - */ - void parse(int argc, char * argv[]); - - /** - * @brief - * Check if there were any errors during parsing - * - * @return - * `true` if errors have been found, else `false` - */ - bool hasErrors() const; - - /** - * @brief - * Get errors - * - * @return - * List of parsing errors - */ - const std::vector<std::string> & errors() const; - - /** - * @brief - * Check if action has been activated - * - * @return - * `true` if activated, else `false` - */ - bool activated() const; - - /** - * @brief - * Get optional parameters specified on the command line - * - * @return - * List of optional parameters - */ - const std::vector<std::string> & optionalParameters() const; - - /** - * @brief - * Execute action - * - * @return - * Error code (0 on success) - */ - virtual int execute(); - - -protected: - /** - * @brief - * Check if this action has been activated - * - * @return - * `true` if activated, else `false` - */ - bool checkActivated(); - - /** - * @brief - * Checks for errors and collects error messages - * - * @see - * errors - */ - void checkErrors(); - - -protected: - std::string m_name; ///< Action name - std::string m_description; ///< Description text - std::vector<CommandLineCommand *> m_commands; ///< List of commands - std::vector<CommandLineOption *> m_options; ///< List of options - std::vector<CommandLineSwitch *> m_switches; ///< List of switches - std::vector<CommandLineParameter *> m_parameters; ///< List of parameters - bool m_optionalParametersAllowed; ///< `true` if optional parameters are allowed, else `false` - std::string m_optionalParameterName; ///< Name for optional parameters - bool m_activated; ///< `true` if activated, else `false` - std::vector<std::string> m_optionalParameters; ///< List of optional parameters - std::vector<std::string> m_errors; ///< List of parsing errors -}; - - -} // namespace cppassist diff --git a/source/cppassist/include/cppassist/cmdline/CommandLineProgram.h b/source/cppassist/include/cppassist/cmdline/CommandLineProgram.h index fd9698d..3fd11fd 100644 --- a/source/cppassist/include/cppassist/cmdline/CommandLineProgram.h +++ b/source/cppassist/include/cppassist/cmdline/CommandLineProgram.h @@ -12,7 +12,11 @@ namespace cppassist { -class CommandLineAction; +class CommandLineProgram; +class CommandLineCommand; +class CommandLineOption; +class CommandLineSwitch; +class CommandLineParameter; /** @@ -108,7 +112,7 @@ class CPPASSIST_API CommandLineProgram * @return * List of actions */ - const std::vector<CommandLineAction *> & actions() const; + const std::vector<CommandLineProgram *> & actions() const; /** * @brief @@ -120,7 +124,7 @@ class CPPASSIST_API CommandLineProgram * @return * Pointer to action, null on error */ - CommandLineAction * getAction(const std::string & name) const; + CommandLineProgram * getAction(const std::string & name) const; /** * @brief @@ -129,7 +133,175 @@ class CPPASSIST_API CommandLineProgram * @param[in] action * Command line action (must NOT be null!) */ - void add(CommandLineAction * action); + void add(CommandLineProgram * action); + + /** + * @brief + * Get commands + * + * @return + * List of commands + */ + const std::vector<CommandLineCommand *> & commands() const; + + /** + * @brief + * Get command by name + * + * @param[in] name + * Command name + * + * @return + * Pointer to command, null on error + */ + CommandLineCommand * getCommand(const std::string & name) const; + + /** + * @brief + * Add command + * + * @param[in] command + * Command line command (must NOT be null!) + */ + void add(CommandLineCommand * command); + + /** + * @brief + * Get options + * + * @return + * List of options + */ + const std::vector<CommandLineOption *> & options() const; + + /** + * @brief + * Get option by name + * + * @param[in] name + * Option name + * + * @return + * Pointer to option, null on error + */ + CommandLineOption * getOption(const std::string & name) const; + + /** + * @brief + * Add option + * + * @param[in] option + * Command line option (must NOT be null!) + */ + void add(CommandLineOption * option); + + /** + * @brief + * Get switches + * + * @return + * List of switches + */ + const std::vector<CommandLineSwitch *> & switches() const; + + /** + * @brief + * Get switch by name + * + * @param[in] name + * Switch name + * + * @return + * Pointer to switch, null on error + */ + CommandLineSwitch * getSwitch(const std::string & name) const; + + /** + * @brief + * Add switch + * + * @param[in] sw + * Command line switch (must NOT be null!) + */ + void add(CommandLineSwitch * sw); + + /** + * @brief + * Get parameters + * + * @return + * List of parameters + */ + const std::vector<CommandLineParameter *> & parameters() const; + + /** + * @brief + * Get parameter by name + * + * @param[in] name + * Parameter name + * + * @return + * Pointer to parameter, null on error + */ + CommandLineParameter * getParameter(const std::string & name) const; + + /** + * @brief + * Get parameter by index + * + * @param[in] index + * Parameter index + * + * @return + * Pointer to parameter, null on error + */ + CommandLineParameter * getParameter(size_t index) const; + + /** + * @brief + * Add parameter + * + * @param[in] parameter + * Command line parameter (must NOT be null!) + */ + void add(CommandLineParameter * parameter); + + /** + * @brief + * Check if optional parameters are allowed by the action + * + * @return + * `true` if optional parameters are allowed, else `false` + */ + bool optionalParametersAllowed() const; + + /** + * @brief + * Set if optional parameters are allowed by the action + * + * @param[in] allowed + * `true` if optional parameters are allowed, else `false` + */ + void setOptionalParametersAllowed(bool allowed); + + /** + * @brief + * Get optional parameter name + * + * @return + * Parameter name (e.g., "path") + */ + const std::string & optionalParameterName() const; + + /** + * @brief + * Set optional parameter name + * + * @param[in] name + * Parameter name (e.g., "path") + */ + void setOptionalParameterName(const std::string & name); /** * @brief @@ -148,7 +320,7 @@ class CPPASSIST_API CommandLineProgram * options for that action are given. * You can change this behaviour by overriding this function. */ - virtual std::string help(CommandLineAction * forAction = nullptr) const; + virtual std::string help(CommandLineProgram * forAction = nullptr) const; /** * @brief @@ -163,6 +335,15 @@ class CPPASSIST_API CommandLineProgram */ virtual void print(const std::string & msg); + /** + * @brief + * Get usage text + * + * @return + * Usage text + */ + std::string usage() const; + /** * @brief * Reset state (forget all information from previous parsings) @@ -217,7 +398,7 @@ class CPPASSIST_API CommandLineProgram * @return * Error code (0 on success) */ - virtual int executeAction(CommandLineAction * action); + virtual int executeAction(CommandLineProgram * action); /** * @brief @@ -235,15 +416,92 @@ class CPPASSIST_API CommandLineProgram * @return * Action, can be null */ - CommandLineAction * selectedAction() const; + CommandLineProgram * selectedAction() const; + + /** + * @brief + * Get errors + * + * @return + * List of parsing errors + */ + const std::vector<std::string> & errors() const; + + /** + * @brief + * Check if action has been activated + * + * @return + * `true` if activated, else `false` + */ + bool activated() const; + + /** + * @brief + * Get optional parameters specified on the command line + * + * @return + * List of optional parameters + */ + const std::vector<std::string> & unknownArguments() const; + + /** + * @brief + * Execute action + * + * @return + * Error code (0 on success) + */ + virtual int execute(); + + +protected: + /** + * @brief + * Try to parse arguments + * + * @param[in] args + * Arguments to parse. + * + * @return + * `true` if parsing resulted in no errors, `false` otherwise + */ + bool tryParse(std::vector<std::string> & args); + + /** + * @brief + * Check if this action has been activated + * + * @return + * `true` if activated, else `false` + */ + bool checkActivated(); + + /** + * @brief + * Checks for errors and collects error messages + * + * @see + * errors + */ + void checkErrors(); protected: - std::string m_name; ///< Program name - std::string m_shortDesc; ///< Short description (e.g., version and license) - std::string m_description; ///< Description text - std::vector<CommandLineAction *> m_actions; ///< List of registered actions - CommandLineAction * m_selectedAction; ///< Action that has been selected (can be null) + std::string m_name; ///< Program name + std::string m_shortDesc; ///< Short description (e.g., version and license) + std::string m_description; ///< Description text + CommandLineProgram * m_selectedAction; ///< Action that has been selected (can be null) + std::vector<CommandLineProgram *> m_actions; ///< List of registered actions + std::vector<CommandLineCommand *> m_commands; ///< List of commands + std::vector<CommandLineOption *> m_options; ///< List of options + std::vector<CommandLineSwitch *> m_switches; ///< List of switches + std::vector<CommandLineParameter *> m_parameters; ///< List of parameters + bool m_optionalParametersAllowed; ///< `true` if optional parameters are allowed, else `false` + std::string m_optionalParameterName; ///< Name for optional parameters + bool m_activated; ///< `true` if activated, else `false` + std::vector<std::string> m_unknownArgs; ///< List of optional parameters + std::vector<std::string> m_errors; ///< List of parsing errors }; diff --git a/source/cppassist/source/cmdline/CommandLineAction.cpp b/source/cppassist/source/cmdline/CommandLineAction.cpp deleted file mode 100644 index 29b0505..0000000 --- a/source/cppassist/source/cmdline/CommandLineAction.cpp +++ /dev/null @@ -1,440 +0,0 @@ - -#include <cppassist/cmdline/CommandLineAction.h> - -#include <cppassist/string/manipulation.h> -#include <cppassist/cmdline/CommandLineCommand.h> -#include <cppassist/cmdline/CommandLineOption.h> -#include <cppassist/cmdline/CommandLineSwitch.h> -#include <cppassist/cmdline/CommandLineParameter.h> - - -namespace cppassist -{ - - -CommandLineAction::CommandLineAction(const std::string & name, const std::string & description) -: m_name(name) -, m_description(description) -, m_optionalParametersAllowed(false) -, m_optionalParameterName("arg") -, m_activated(false) -{ -} - -CommandLineAction::~CommandLineAction() -{ -} - -const std::string & CommandLineAction::name() const -{ - return m_name; -} - -void CommandLineAction::setName(const std::string & name) -{ - m_name = name; -} - -const std::string & CommandLineAction::description() const -{ - return m_description; -} - -void CommandLineAction::setDescription(const std::string & description) -{ - m_description = description; -} - -const std::vector<CommandLineCommand *> & CommandLineAction::commands() const -{ - return m_commands; -} - -CommandLineCommand * CommandLineAction::getCommand(const std::string & name) const -{ - for (auto * command : m_commands) - { - if (command->name() == name) - { - return command; - } - } - - return nullptr; -} - -void CommandLineAction::add(CommandLineCommand * command) -{ - m_commands.push_back(command); -} - -const std::vector<CommandLineOption *> & CommandLineAction::options() const -{ - return m_options; -} - -CommandLineOption * CommandLineAction::getOption(const std::string & name) const -{ - for (auto * option : m_options) - { - if (option->shortName() == name || option->longName() == name) - { - return option; - } - } - - return nullptr; -} - -void CommandLineAction::add(CommandLineOption * option) -{ - m_options.push_back(option); -} - -const std::vector<CommandLineSwitch *> & CommandLineAction::switches() const -{ - return m_switches; -} - -CommandLineSwitch * CommandLineAction::getSwitch(const std::string & name) const -{ - for (auto * sw : m_switches) - { - if (sw->shortName() == name || sw->longName() == name) - { - return sw; - } - } - - return nullptr; -} - -void CommandLineAction::add(CommandLineSwitch * sw) -{ - m_switches.push_back(sw); -} - -const std::vector<CommandLineParameter *> & CommandLineAction::parameters() const -{ - return m_parameters; -} - -CommandLineParameter * CommandLineAction::getParameter(const std::string & name) const -{ - for (auto * parameter : m_parameters) - { - if (parameter->name() == name) - { - return parameter; - } - } - - return nullptr; -} - -CommandLineParameter * CommandLineAction::getParameter(size_t index) const -{ - if (index < m_parameters.size()) - { - return m_parameters[index]; - } - - return nullptr; -} - -bool CommandLineAction::optionalParametersAllowed() const -{ - return m_optionalParametersAllowed; -} - -void CommandLineAction::setOptionalParametersAllowed(bool allowed) -{ - m_optionalParametersAllowed = allowed; -} - -const std::string & CommandLineAction::optionalParameterName() const -{ - return m_optionalParameterName; -} - -void CommandLineAction::setOptionalParameterName(const std::string & name) -{ - m_optionalParameterName = name; -} - -void CommandLineAction::add(CommandLineParameter * parameter) -{ - m_parameters.push_back(parameter); -} - -std::string CommandLineAction::usage() const -{ - std::string usage; - - for (auto * command : m_commands) - { - if (usage.size() > 0) usage += " "; - - usage += command->name(); - } - - for (auto * sw : m_switches) - { - if (usage.size() > 0) usage += " "; - if (sw->isOptional()) usage += "["; - usage += sw->name(); - if (sw->isOptional()) usage += "]"; - } - - for (auto * option : m_options) - { - if (usage.size() > 0) usage += " "; - if (option->isOptional()) usage += "["; - usage += option->name() + " <" + option->valueName() + ">"; - if (option->isOptional()) usage += "]"; - } - - for (auto * param : m_parameters) - { - if (usage.size() > 0) usage += " "; - if (param->isOptional()) usage += "["; - usage += "<" + param->name() + ">"; - if (param->isOptional()) usage += "]"; - } - - if (m_optionalParametersAllowed) - { - if (usage.size() > 0) usage += " "; - usage += "[<" + m_optionalParameterName + ">]*"; - } - - return usage; -} - -void CommandLineAction::reset() -{ - m_activated = false; - - m_optionalParameters.clear(); - m_errors.clear(); - - for (auto * command : m_commands) - { - command->setActivated(false); - } - - for (auto * sw : m_switches) - { - sw->setActivated(false); - sw->setCount(0); - } - - for (auto * option : m_options) - { - option->setValue(""); - } - - for (auto * parameter : m_parameters) - { - parameter->setValue(""); - } -} - -void CommandLineAction::parse(int argc, char * argv[]) -{ - std::vector<std::string> args; - - // Reset internal state - reset(); - - // List arguments - for (int i=1; i<argc; i++) - { - std::string arg = argv[i]; - - // Expand grouped options (e.g., "-abc" -> "-a -b -c") - if (!string::hasPrefix(arg, "--") && string::hasPrefix(arg, "-") && arg.size() > 2) - { - for (size_t j=0; j<arg.size()-1; j++) - { - std::string opt = std::string("-") + arg[j+1]; - args.push_back(opt); - } - } - else - { - args.push_back(arg); - } - } - - // Parse command line - size_t numParameters = 0; - for (size_t i=0; i<args.size(); i++) - { - // Get current and next argument - std::string arg = args[i]; - std::string next = (i+1 < args.size() ? args[i+1] : ""); - - // If item starts with "--" or "-", it may be an option or a switch - if (string::hasPrefix(arg, "-")) - { - CommandLineOption * option = getOption(arg); - CommandLineSwitch * sw = getSwitch(arg); - - // Option - if (option) - { - // Check that next item is a valid value (and not empty or an option) - if (!next.empty() && !string::hasPrefix(next, "-")) - { - option->setValue(next); - i++; - } - else - { - // Error: Value expected - m_errors.push_back("Expected value for '" + arg + "'"); - } - } - - // Switch - else if (sw) - { - sw->setActivated(true); - sw->setCount(sw->count() + 1); - } - - // Error: Unknown option - else - { - m_errors.push_back("Unknown option '" + arg + "'"); - } - } - - // Otherwise, it may be a command or a parameter - else - { - CommandLineCommand * command = getCommand(arg); - - // Command - if (command) - { - command->setActivated(true); - } - - // Parameter - else - { - auto * parameter = getParameter(numParameters); - - // Parameter found - if (parameter) - { - parameter->setValue(arg); - numParameters++; - } - - // Unknown parameter - else - { - m_optionalParameters.push_back(arg); - } - } - } - } - - // Check if this action is activated - m_activated = checkActivated(); - - // Check for errors - checkErrors(); -} - -bool CommandLineAction::hasErrors() const -{ - return m_errors.size() > 0; -} - -const std::vector<std::string> & CommandLineAction::errors() const -{ - return m_errors; -} - -bool CommandLineAction::activated() const -{ - return m_activated; -} - -const std::vector<std::string> & CommandLineAction::optionalParameters() const -{ - return m_optionalParameters; -} - -int CommandLineAction::execute() -{ - // To be implement in derived classes - return 0; -} - -bool CommandLineAction::checkActivated() -{ - // Check if this action has been activated - for (auto * command : m_commands) - { - if (!command->activated()) - { - return false; - } - } - - for (auto * sw : m_switches) - { - if (sw->optional() == CommandLineSwitch::NonOptional && !sw->activated()) - { - return false; - } - } - - for (auto * option : m_options) - { - if (option->optional() == CommandLineOption::NonOptional && option->value().empty()) - { - return false; - } - } - - return true; -} - -void CommandLineAction::checkErrors() -{ - // Look for options that miss a value - for (auto * option : m_options) - { - if (option->optional() == CommandLineOption::NonOptional && option->value().empty()) - { - m_errors.push_back("Expected value for '" + option->name() + "'"); - } - } - - // Look for parameters that have not been specified - for (auto * parameter : m_parameters) - { - if (parameter->optional() == CommandLineParameter::NonOptional && parameter->value().empty()) - { - m_errors.push_back("Expected <" + parameter->name() + ">"); - } - } - - // Look for unrecognized parameters - if (!m_optionalParametersAllowed) - { - for (auto parameter : m_optionalParameters) - { - m_errors.push_back("Unknown parameter '" + parameter + "'"); - } - } -} - - -} // namespace cppassist diff --git a/source/cppassist/source/cmdline/CommandLineProgram.cpp b/source/cppassist/source/cmdline/CommandLineProgram.cpp index 4e583ed..7db6ad6 100644 --- a/source/cppassist/source/cmdline/CommandLineProgram.cpp +++ b/source/cppassist/source/cmdline/CommandLineProgram.cpp @@ -5,8 +5,10 @@ #include <cppassist/logging/logging.h> #include <cppassist/string/manipulation.h> -#include <cppassist/cmdline/CommandLineAction.h> +#include <cppassist/cmdline/CommandLineProgram.h> +#include <cppassist/cmdline/CommandLineCommand.h> #include <cppassist/cmdline/CommandLineOption.h> +#include <cppassist/cmdline/CommandLineParameter.h> #include <cppassist/cmdline/CommandLineSwitch.h> @@ -19,6 +21,9 @@ CommandLineProgram::CommandLineProgram(const std::string & name, const std::stri , m_shortDesc(shortDesc) , m_description(description) , m_selectedAction(nullptr) +, m_optionalParametersAllowed(false) +, m_optionalParameterName("arg") +, m_activated(false) { } @@ -48,6 +53,11 @@ void CommandLineProgram::setShortDesc(const std::string & shortDesc) const std::string & CommandLineProgram::description() const { + if (m_description.empty()) + { + return shortDesc(); + } + return m_description; } @@ -56,12 +66,12 @@ void CommandLineProgram::setDescription(const std::string & description) m_description = description; } -const std::vector<CommandLineAction *> & CommandLineProgram::actions() const +const std::vector<CommandLineProgram *> & CommandLineProgram::actions() const { return m_actions; } -CommandLineAction * CommandLineProgram::getAction(const std::string & name) const +CommandLineProgram * CommandLineProgram::getAction(const std::string & name) const { for (auto * action : m_actions) { @@ -74,12 +84,178 @@ CommandLineAction * CommandLineProgram::getAction(const std::string & name) cons return nullptr; } -void CommandLineProgram::add(CommandLineAction * action) +void CommandLineProgram::add(CommandLineProgram * action) { m_actions.push_back(action); } -std::string CommandLineProgram::help(CommandLineAction * forAction) const +const std::vector<CommandLineCommand *> & CommandLineProgram::commands() const +{ + return m_commands; +} + +CommandLineCommand * CommandLineProgram::getCommand(const std::string & name) const +{ + for (auto * command : m_commands) + { + if (command->name() == name) + { + return command; + } + } + + return nullptr; +} + +void CommandLineProgram::add(CommandLineCommand * command) +{ + m_commands.push_back(command); +} + +const std::vector<CommandLineOption *> & CommandLineProgram::options() const +{ + return m_options; +} + +CommandLineOption * CommandLineProgram::getOption(const std::string & name) const +{ + for (auto * option : m_options) + { + if (option->shortName() == name || option->longName() == name) + { + return option; + } + } + + return nullptr; +} + +void CommandLineProgram::add(CommandLineOption * option) +{ + m_options.push_back(option); +} + +const std::vector<CommandLineSwitch *> & CommandLineProgram::switches() const +{ + return m_switches; +} + +CommandLineSwitch * CommandLineProgram::getSwitch(const std::string & name) const +{ + for (auto * sw : m_switches) + { + if (sw->shortName() == name || sw->longName() == name) + { + return sw; + } + } + + return nullptr; +} + +void CommandLineProgram::add(CommandLineSwitch * sw) +{ + m_switches.push_back(sw); +} + +const std::vector<CommandLineParameter *> & CommandLineProgram::parameters() const +{ + return m_parameters; +} + +CommandLineParameter * CommandLineProgram::getParameter(const std::string & name) const +{ + for (auto * parameter : m_parameters) + { + if (parameter->name() == name) + { + return parameter; + } + } + + return nullptr; +} + +CommandLineParameter * CommandLineProgram::getParameter(size_t index) const +{ + if (index < m_parameters.size()) + { + return m_parameters[index]; + } + + return nullptr; +} + +bool CommandLineProgram::optionalParametersAllowed() const +{ + return m_optionalParametersAllowed; +} + +void CommandLineProgram::setOptionalParametersAllowed(bool allowed) +{ + m_optionalParametersAllowed = allowed; +} + +const std::string & CommandLineProgram::optionalParameterName() const +{ + return m_optionalParameterName; +} + +void CommandLineProgram::setOptionalParameterName(const std::string & name) +{ + m_optionalParameterName = name; +} + +void CommandLineProgram::add(CommandLineParameter * parameter) +{ + m_parameters.push_back(parameter); +} + +std::string CommandLineProgram::usage() const +{ + std::string usage; + + for (auto * command : m_commands) + { + if (usage.size() > 0) usage += " "; + + usage += command->name(); + } + + for (auto * sw : m_switches) + { + if (usage.size() > 0) usage += " "; + if (sw->isOptional()) usage += "["; + usage += sw->name(); + if (sw->isOptional()) usage += "]"; + } + + for (auto * option : m_options) + { + if (usage.size() > 0) usage += " "; + if (option->isOptional()) usage += "["; + usage += option->name() + " <" + option->valueName() + ">"; + if (option->isOptional()) usage += "]"; + } + + for (auto * param : m_parameters) + { + if (usage.size() > 0) usage += " "; + if (param->isOptional()) usage += "["; + usage += "<" + param->name() + ">"; + if (param->isOptional()) usage += "]"; + } + + if (m_optionalParametersAllowed) + { + if (usage.size() > 0) usage += " "; + usage += "[<" + m_optionalParameterName + ">]*"; + } + + return usage; +} + +std::string CommandLineProgram::help(CommandLineProgram * forAction) const { std::string msg = ""; @@ -112,7 +288,7 @@ std::string CommandLineProgram::help(CommandLineAction * forAction) const } // Choose action(s) to display - std::vector<CommandLineAction *> actions; + std::vector<CommandLineProgram *> actions; if (forAction) actions.push_back(forAction); else actions = m_actions; @@ -181,6 +357,32 @@ void CommandLineProgram::reset() } m_selectedAction = nullptr; + + m_activated = false; + + m_unknownArgs.clear(); + m_errors.clear(); + + for (auto * command : m_commands) + { + command->setActivated(false); + } + + for (auto * sw : m_switches) + { + sw->setActivated(false); + sw->setCount(0); + } + + for (auto * option : m_options) + { + option->setValue(""); + } + + for (auto * parameter : m_parameters) + { + parameter->setValue(""); + } } int CommandLineProgram::execute(int argc, char * argv[]) @@ -189,20 +391,20 @@ int CommandLineProgram::execute(int argc, char * argv[]) parse(argc, argv); // Check if a valid action has been selected - if (selectedAction() && !hasErrors()) + if (!hasErrors()) { // Execute action - return executeAction(selectedAction()); + return execute(); } // Print help print(help(selectedAction())); // Print errors - if (hasErrors() && selectedAction()) + if (hasErrors()) { // Print error message - std::string error = selectedAction()->errors()[0]; + std::string error = errors()[0]; print("Error: " + error); // Indicate error condition @@ -213,7 +415,7 @@ int CommandLineProgram::execute(int argc, char * argv[]) return 0; } -int CommandLineProgram::executeAction(CommandLineAction * action) +int CommandLineProgram::executeAction(CommandLineProgram * action) { if (action) { @@ -230,30 +432,253 @@ void CommandLineProgram::parse(int argc, char * argv[]) m_name = argv[0]; } + // List arguments + std::vector<std::string> args; + + for (int i=1; i<argc; i++) + { + std::string arg = argv[i]; + + // Expand grouped options (e.g., "-abc" -> "-a -b -c") + if (!string::hasPrefix(arg, "--") && string::hasPrefix(arg, "-") && arg.size() > 2) + { + for (size_t j=0; j<arg.size()-1; j++) + { + std::string opt = std::string("-") + arg[j+1]; + args.push_back(opt); + } + } + else + { + args.push_back(arg); + } + } + + // Parse command line + tryParse(args); + + // Check if this action is activated + m_activated = checkActivated(); +} + +bool CommandLineProgram::hasErrors() const +{ + if (m_selectedAction) + { + return m_selectedAction->hasErrors(); + } + + return m_errors.size() > 0; +} + +const std::vector<std::string> & CommandLineProgram::errors() const +{ + if (m_selectedAction) + { + return m_selectedAction->errors(); + } + + return m_errors; +} + +bool CommandLineProgram::activated() const +{ + return m_activated; +} + +const std::vector<std::string> & CommandLineProgram::unknownArguments() const +{ + if (m_selectedAction) + { + return m_selectedAction->unknownArguments(); + } + + return m_unknownArgs; +} + +int CommandLineProgram::execute() +{ + // To be implement in derived classes + + return executeAction(m_selectedAction); +} + +bool CommandLineProgram::tryParse(std::vector<std::string> & args) +{ // Reset status reset(); - // Find invoked action - for (auto * action : m_actions) + // Parse command line + size_t numParameters = 0; + for (size_t i=0; i<args.size(); i++) { - // Parse command line - action->parse(argc, argv); + // Get current and next argument + std::string arg = args[i]; + std::string next = (i+1 < args.size() ? args[i+1] : ""); + + // If item starts with "--" or "-", it may be an option or a switch + if (string::hasPrefix(arg, "-")) + { + CommandLineOption * option = getOption(arg); + CommandLineSwitch * sw = getSwitch(arg); + + // Option + if (option) + { + // Check that next item is a valid value (and not empty or an option) + if (!next.empty() && !string::hasPrefix(next, "-")) + { + option->setValue(next); + i++; + } + else + { + // Error: Value expected + m_errors.push_back("Expected value for '" + arg + "'"); + } + } + + // Switch + else if (sw) + { + sw->setActivated(true); + sw->setCount(sw->count() + 1); + } + + // Error: Unknown option + else + { + m_unknownArgs.push_back(arg); + } + } - // Action found? - if (action->activated()) + // Otherwise, it may be a command or a parameter + else + { + CommandLineCommand * command = getCommand(arg); + + // Command + if (command) + { + command->setActivated(true); + } + + // Parameter + else + { + auto * parameter = getParameter(numParameters); + + // Parameter found + if (parameter) + { + parameter->setValue(arg); + numParameters++; + } + + // Unknown parameter + else + { + m_unknownArgs.push_back(arg); + } + } + } + } + + for (auto action : m_actions) + { + if (action->tryParse(m_unknownArgs)) { m_selectedAction = action; - return; + break; } } + + checkErrors(); + + m_activated = checkActivated(); + + return m_activated; } -bool CommandLineProgram::hasErrors() const +bool CommandLineProgram::checkActivated() { - return (m_selectedAction && m_selectedAction->hasErrors()); + // Check if this action has been activated + for (auto * command : m_commands) + { + if (!command->activated()) + { + return false; + } + } + + for (auto * sw : m_switches) + { + if (sw->optional() == CommandLineSwitch::NonOptional && !sw->activated()) + { + return false; + } + } + + for (auto * option : m_options) + { + if (option->optional() == CommandLineOption::NonOptional && option->value().empty()) + { + return false; + } + } + + return true; +} + +void CommandLineProgram::checkErrors() +{ + // Look for options that miss a value + for (auto * option : m_options) + { + if (option->optional() == CommandLineOption::NonOptional && option->value().empty()) + { + m_errors.push_back("Expected value for '" + option->name() + "'"); + } + } + + // Look for parameters that have not been specified + for (auto * parameter : m_parameters) + { + if (parameter->optional() == CommandLineParameter::NonOptional && parameter->value().empty()) + { + m_errors.push_back("Expected <" + parameter->name() + ">"); + } + } + + // Look for commands that have not been activated + for (auto * command : m_commands) + { + if (!command->activated()) + { + m_errors.push_back("Expected '" + command->name() + "'"); + } + } + + // Look for required switches that have not been activated + for (auto * cmdSwitch : m_switches) + { + if (cmdSwitch->optional() == CommandLineSwitch::NonOptional && !cmdSwitch->activated()) + { + m_errors.push_back("Expected '" + cmdSwitch->name() + "'"); + } + } + + // Look for unrecognized parameters + if (!m_optionalParametersAllowed) + { + for (auto parameter : unknownArguments()) + { + m_errors.push_back("Unknown parameter '" + parameter + "'"); + } + } } -CommandLineAction * CommandLineProgram::selectedAction() const +CommandLineProgram * CommandLineProgram::selectedAction() const { return m_selectedAction; } diff --git a/source/examples/cmdline/ActionCopy.cpp b/source/examples/cmdline/ActionCopy.cpp index 4b2c635..f43ff20 100644 --- a/source/examples/cmdline/ActionCopy.cpp +++ b/source/examples/cmdline/ActionCopy.cpp @@ -10,7 +10,7 @@ using namespace cppassist; ActionCopy::ActionCopy(Program & program) -: CommandLineAction("cp", "Copy files") +: CommandLineProgram("cp", "Copy files") , m_program(program) , m_commandCopy("cp") , m_paramSrc("path", CommandLineParameter::NonOptional) @@ -34,7 +34,7 @@ int ActionCopy::execute() info() << "Let me copy that for you ..."; info() << "- " << m_paramSrc.value(); - for (auto arg : optionalParameters()) + for (auto arg : unknownArguments()) { info() << "- " << arg; } diff --git a/source/examples/cmdline/ActionCopy.h b/source/examples/cmdline/ActionCopy.h index a0f5b80..6178df1 100644 --- a/source/examples/cmdline/ActionCopy.h +++ b/source/examples/cmdline/ActionCopy.h @@ -2,7 +2,7 @@ #pragma once -#include <cppassist/cmdline/CommandLineAction.h> +#include <cppassist/cmdline/CommandLineProgram.h> #include <cppassist/cmdline/CommandLineCommand.h> #include <cppassist/cmdline/CommandLineParameter.h> @@ -14,7 +14,7 @@ class Program; * @brief * Command 'cp' */ -class ActionCopy : public cppassist::CommandLineAction +class ActionCopy : public cppassist::CommandLineProgram { public: /** @@ -32,7 +32,7 @@ class ActionCopy : public cppassist::CommandLineAction */ ~ActionCopy(); - // Virtual cppassist::CommandLineAction functions + // Virtual cppassist::CommandLineProgram functions virtual int execute() override; diff --git a/source/examples/cmdline/ActionCount.cpp b/source/examples/cmdline/ActionCount.cpp index 9f37a8b..79026c0 100644 --- a/source/examples/cmdline/ActionCount.cpp +++ b/source/examples/cmdline/ActionCount.cpp @@ -10,7 +10,7 @@ using namespace cppassist; ActionCount::ActionCount(Program & program) -: CommandLineAction("count", "Count from one number to another") +: CommandLineProgram("count", "Count from one number to another") , m_program(program) , m_commandCount("count") , m_optionStep("--increment-by", "-i", "step", "Number that is added per iteration", CommandLineOption::Optional) diff --git a/source/examples/cmdline/ActionCount.h b/source/examples/cmdline/ActionCount.h index e3403a8..329a543 100644 --- a/source/examples/cmdline/ActionCount.h +++ b/source/examples/cmdline/ActionCount.h @@ -2,7 +2,7 @@ #pragma once -#include <cppassist/cmdline/CommandLineAction.h> +#include <cppassist/cmdline/CommandLineProgram.h> #include <cppassist/cmdline/CommandLineCommand.h> #include <cppassist/cmdline/CommandLineOption.h> #include <cppassist/cmdline/CommandLineParameter.h> @@ -15,7 +15,7 @@ class Program; * @brief * Command 'count' */ -class ActionCount : public cppassist::CommandLineAction +class ActionCount : public cppassist::CommandLineProgram { public: /** @@ -33,7 +33,7 @@ class ActionCount : public cppassist::CommandLineAction */ ~ActionCount(); - // Virtual cppassist::CommandLineAction functions + // Virtual cppassist::CommandLineProgram functions virtual int execute() override; diff --git a/source/examples/cmdline/ActionHelp.cpp b/source/examples/cmdline/ActionHelp.cpp index 380088f..eaba814 100644 --- a/source/examples/cmdline/ActionHelp.cpp +++ b/source/examples/cmdline/ActionHelp.cpp @@ -10,7 +10,7 @@ using namespace cppassist; ActionHelp::ActionHelp(Program & program) -: CommandLineAction("help", "Print help text") +: CommandLineProgram("help", "Print help text") , m_program(program) , m_switchHelp("--help", "-h", "Print help text", CommandLineSwitch::NonOptional) , m_paramCommand("command", CommandLineParameter::Optional) @@ -27,7 +27,7 @@ ActionHelp::~ActionHelp() int ActionHelp::execute() { - CommandLineAction * forAction = nullptr; + CommandLineProgram * forAction = nullptr; if (!m_paramCommand.value().empty()) { diff --git a/source/examples/cmdline/ActionHelp.h b/source/examples/cmdline/ActionHelp.h index b87f46e..8b0e9f7 100644 --- a/source/examples/cmdline/ActionHelp.h +++ b/source/examples/cmdline/ActionHelp.h @@ -2,7 +2,7 @@ #pragma once -#include <cppassist/cmdline/CommandLineAction.h> +#include <cppassist/cmdline/CommandLineProgram.h> #include <cppassist/cmdline/CommandLineSwitch.h> #include <cppassist/cmdline/CommandLineParameter.h> @@ -14,7 +14,7 @@ class Program; * @brief * Command 'help' */ -class ActionHelp : public cppassist::CommandLineAction +class ActionHelp : public cppassist::CommandLineProgram { public: /** @@ -32,7 +32,7 @@ class ActionHelp : public cppassist::CommandLineAction */ ~ActionHelp(); - // Virtual cppassist::CommandLineAction functions + // Virtual cppassist::CommandLineProgram functions virtual int execute() override; diff --git a/source/examples/cmdline/Program.cpp b/source/examples/cmdline/Program.cpp index ad65501..148b286 100644 --- a/source/examples/cmdline/Program.cpp +++ b/source/examples/cmdline/Program.cpp @@ -28,12 +28,12 @@ Program::~Program() { } -void Program::addDefaultOptionsTo(CommandLineAction & action) +void Program::addDefaultOptionsTo(CommandLineProgram & action) { action.add(&m_switchVerbose); } -int Program::executeAction(CommandLineAction * action) +int Program::executeAction(CommandLineProgram * action) { // Set log level int logLevel = LogMessage::Info; @@ -54,5 +54,7 @@ int Program::executeAction(CommandLineAction * action) return action->execute(); } + print(help()); + return 0; } diff --git a/source/examples/cmdline/Program.h b/source/examples/cmdline/Program.h index c64f3b5..be7209e 100644 --- a/source/examples/cmdline/Program.h +++ b/source/examples/cmdline/Program.h @@ -36,10 +36,10 @@ class Program : public cppassist::CommandLineProgram * @param[in] action * Command line action */ - void addDefaultOptionsTo(cppassist::CommandLineAction & action); + void addDefaultOptionsTo(cppassist::CommandLineProgram & action); // Virtual cppassist::CommandLineProgram functions - virtual int executeAction(cppassist::CommandLineAction * action) override; + virtual int executeAction(cppassist::CommandLineProgram * action) override; public: diff --git a/source/examples/cmdline2/main.cpp b/source/examples/cmdline2/main.cpp index 6b7e596..fc0fa3d 100644 --- a/source/examples/cmdline2/main.cpp +++ b/source/examples/cmdline2/main.cpp @@ -4,7 +4,6 @@ #include <cppassist/logging/logging.h> #include <cppassist/cmdline/ArgumentParser.h> #include <cppassist/cmdline/CommandLineProgram.h> -#include <cppassist/cmdline/CommandLineAction.h> #include <cppassist/cmdline/CommandLineCommand.h> #include <cppassist/cmdline/CommandLineOption.h> #include <cppassist/cmdline/CommandLineSwitch.h> @@ -27,7 +26,7 @@ int main(int argc, char * argv[]) CommandLineSwitch swVerbose("--verbose", "-v", "Make output more verbose"); // Action: 'help' - CommandLineAction actionHelp("help", "Print help text"); + CommandLineProgram actionHelp("help", "Print help text"); program.add(&actionHelp); actionHelp.add(&swVerbose); @@ -39,7 +38,7 @@ int main(int argc, char * argv[]) actionHelp.add(¶mCommand); // Action: 'count' - CommandLineAction actionCount("count", "Count from one number to another"); + CommandLineProgram actionCount("count", "Count from one number to another"); program.add(&actionCount); actionCount.add(&swVerbose); @@ -57,7 +56,7 @@ int main(int argc, char * argv[]) actionCount.add(¶mTo); // Action: 'cp' - CommandLineAction actionCopy("cp", "Copy files"); + CommandLineProgram actionCopy("cp", "Copy files"); program.add(&actionCopy); actionCopy.add(&swVerbose); @@ -83,7 +82,7 @@ int main(int argc, char * argv[]) // Execute 'help' if (program.selectedAction() == &actionHelp) { - CommandLineAction * forAction = nullptr; + CommandLineProgram * forAction = nullptr; if (!paramCommand.value().empty()) { @@ -105,7 +104,7 @@ int main(int argc, char * argv[]) info() << "Let me copy that for you ..."; info() << "- " << actionCopy.getParameter("path")->value(); - for (auto arg : actionCopy.optionalParameters()) + for (auto arg : actionCopy.unknownArguments()) { info() << "- " << arg; } @@ -119,10 +118,10 @@ int main(int argc, char * argv[]) program.print(program.help(program.selectedAction())); // Print errors - if (program.hasErrors() && program.selectedAction()) + if (program.hasErrors()) { // Print error message - std::string error = program.selectedAction()->errors()[0]; + std::string error = program.errors()[0]; program.print("Error: " + error); return 1;