diff --git a/src/specmicp_common/cli/parser.cpp b/src/specmicp_common/cli/parser.cpp index 1e7b597..e88b946 100644 --- a/src/specmicp_common/cli/parser.cpp +++ b/src/specmicp_common/cli/parser.cpp @@ -1,999 +1,1126 @@ -/* ============================================================================= +/* ============================================================================= Copyright (c) 2014 - 2016 F. Georget Princeton University All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ============================================================================= */ #include "parser.hpp" #include "specmicp_common/compat.hpp" #include #include #include #include namespace specmicp { namespace cli { +template +struct ValueTypeStr{}; + +template <> +struct ValueTypeStr +{ + constexpr static const char* str = "boolean"; +}; + +template <> +struct ValueTypeStr +{ + constexpr static const char* str = "integer"; +}; + +template <> +struct ValueTypeStr +{ + constexpr static const char* str = "double"; +}; + +template <> +struct ValueTypeStr +{ + constexpr static const char* str = "string"; +}; + +std::string value_type_str(ValueType val_type) +{ + if (val_type == ValueType::boolean) { + return "boolean"; + } else if (val_type == ValueType::integer) { + return "integer"; + } else if (val_type == ValueType::floating) { + return "double"; + } else if (val_type == ValueType::string) { + return "string"; + } else { + throw std::runtime_error("Invalid type !"); + } +} + +struct SPECMICP_DLL_LOCAL OptionValue +{ + union numerical { + bool boolean; + int integer; + double floating; + }; + numerical num; + std::string str; + + // getter + + template + T get() const {} + + template + auto get() const -> typename ValueTypeConversion::type + { + return get::type>(); + } + + + // setter + template + void set(T value) {} + + + + template + void set(typename ValueTypeConversion::type value) { + set(value); + } + + void set_from_string(const std::string& value, ValueType value_type) { + switch (value_type) { + case ValueType::boolean: + if (value == "True" or + value == "true" or + value == "ON" or + value == "on") + num.boolean = true; + else + num.boolean = false; + break; + case ValueType::integer: + num.integer = std::stoi(value); + break; + case ValueType::floating: + num.floating = std::stod(value); + break; + case ValueType::string: + str = value; + break; + } + } + +}; + +template <> +bool OptionValue::get() const { + return num.boolean; +} + +template <> +int OptionValue::get() const { + return num.integer; +} + +template <> +double OptionValue::get() const { + return num.floating; +} + +template <> +std::string OptionValue::get() const { + return str; +} + +template <> +void OptionValue::set(bool value) { + num.boolean = value; +} + +template <> +void OptionValue::set(int value) { + num.integer = value; +} + +template<> +void OptionValue::set(double value) { + num.floating = value; +} + +template<> +void OptionValue::set(std::string value) { + str = value; +} + //! \internal //! \brief The different types that a value can solve //! //! To allow a straigthforward use of the union, only a pointer //! to the string is stored, the actual string must be stored elsewhere -union SPECMICP_DLL_LOCAL OptionValue -{ - bool boolean; - int integer; - double floating_number; - std::string* str; +//union SPECMICP_DLL_LOCAL OptionValue +//{ +// bool boolean; +// int integer; +// double floating_number; +// std::string* str; - OptionValue(): - str(nullptr) - {} -}; +// OptionValue(): +// str(nullptr) +// {} +//}; //! \internal //! \brief Base class for an option struct SPECMICP_DLL_LOCAL BaseOption { BaseOption( ValueType type_value, const std::string& help): is_required(true), value_type(type_value), help_message(help) {} BaseOption( ValueType type_value, const std::string& help, OptionValue default_value ): is_required(false), value_type(type_value), help_message(help), value(default_value) {} + template + auto get() -> typename ValueTypeConversion::type + { + if (T != value_type) { + throw std::runtime_error("Wrong type, got " + + value_type_str(value_type) + + " expected " + + ValueTypeStr::str); + } + return value.get(); + } + + bool is_required; bool is_set {false}; ValueType value_type; std::string help_message; OptionValue value; }; //! \internal //! \brief A positional argument struct SPECMICP_DLL_LOCAL PositionalArgument: public BaseOption { PositionalArgument(const std::string& arg_name, ValueType type_value, const std::string& help ): BaseOption(type_value, help), name(arg_name) {} std::string name; }; //! \internal //! \brief An option //! //! An option value is precedented by a flag (starting with '--' pr '-') //! In case of a boolean, only the flag is necessary, the value is implicetely //! true //! struct SPECMICP_DLL_LOCAL SwitchArgument: public BaseOption { SwitchArgument(char the_short_flag, const std::string& the_long_flag, ValueType value_type, OptionValue default_value, const std::string& help_message ): BaseOption(value_type, help_message, default_value), short_flag(the_short_flag), long_flag(the_long_flag) {} SwitchArgument(char the_short_flag, const std::string& the_long_flag, ValueType value_type, const std::string& help_message ): BaseOption(value_type, help_message), short_flag(the_short_flag), long_flag(the_long_flag) {} char short_flag; std::string long_flag; }; //! \internal //! \brief Implementation details for the Command line parser struct SPECMICP_DLL_LOCAL CommandLineParser::CommandLineParserImpl { //! \brief Option : using str_option = std::pair; //! \brief Type of the list of options using list_switch_option = std::vector; //! \brief Type of the list of positional argument using list_pos_argument = std::vector; //! \brief Type of the index in a list of argument using size_type = list_pos_argument::size_type; //! \brief Type of a list of unparsed options using raw_list_option = std::vector; CommandLineParserImpl() { - //FIXME : if vector gets reallocated pointers are wrong ! - m_option_string_values.reserve(20); } //! \brief Set the values of the options void set_list_str_option(const raw_list_option& options); //! \brief Check the values of the options/arguments bool check_values(); //! \brief Check the values of the positional arguments bool check_pos_arguments(); //! \brief Check the values of the options bool check_options(); //! \brief Return the index of a short option //! //! Return -1 if the option does not exist int get_short_option_index(char flag) noexcept; //! \brief Return the index of a long option //! //! Return -1 if the option does not exist int get_long_option_index(const std::string& flag) noexcept; //! \brief Return the index of a long option //! //! \throw runtime_error if the option does not exist size_t get_safe_long_option_index(const std::string& flag); //! \brief Return the index of a positional argument //! //! \throw runtime error if the option does not exist size_t get_safe_pos_argument_index(const std::string& name); //! \brief Parse a boolean option activated by its short flag size_t analyse_boolean_short_option(size_t ind, char opts); //! \brief Parse an option activated by its short flag size_t analyse_short_option(size_t ind, const raw_list_option& options, char opts); //! \brief Parse an option activated by its long flag size_t analyse_long_option(size_t ind, const raw_list_option& options, const std::string& opts); //! \brief Parse a positional argument size_t analyse_pos_argument(size_t ind, const std::string& arg_value); //! \brief Return the index of the next free positional argument //! //! Return -1 if no such argument exists int next_free_pos_arg() noexcept; //! \brief Return a value that can be stored inside an option //! //! \tparam T true C++ type of the type //! \param value the value to set //! \param value_type the type of the option - template - OptionValue set_value(const T& value, ValueType value_type); + OptionValue set_value(bool val, ValueType value_type) { + OptionValue value; + if (value_type != ValueType::boolean) { + throw std::runtime_error("Wrong type : expected " + + value_type_str(value_type) + + " got boolean"); + } + value.set(val); + return value; + } + + + OptionValue set_value(int val, ValueType value_type) { + OptionValue value; + if (value_type != ValueType::integer) { + throw std::runtime_error("Wrong type : expected " + + value_type_str(value_type) + + " got integer"); + } + value.set(val); + return value; + } + + OptionValue set_value(double val, ValueType value_type) { + OptionValue value; + if (value_type != ValueType::floating) { + throw std::runtime_error("Wrong type : expected " + + value_type_str(value_type) + + " got integer"); + } + value.set(val); + return value; + } + + OptionValue set_value(const std::string& val, ValueType value_type) { + OptionValue value; + if (value_type != ValueType::string) { + throw std::runtime_error("Wrong type : expected " + + value_type_str(value_type) + + " got string"); + } + value.set(val); + return value; + } + + //! \brief Set the value of the option - template - void set_option(BaseOption& arg, const T& value); + void set_option(BaseOption& arg, const std::string& value) { + arg.value.set_from_string(value, arg.value_type); + arg.is_set = true; + } + //! \brief Set the value of the option + void set_option(BaseOption& arg, bool value) { + if (arg.value_type != ValueType::boolean) { + throw std::runtime_error("Wrong type : expected " + + value_type_str(arg.value_type) + + " got boolean"); + } + arg.value.set(value); + arg.is_set = true; + } + //! \brief Set the value of the option + void set_option(BaseOption& arg, int value) { + if (arg.value_type != ValueType::integer) { + throw std::runtime_error("Wrong type : expected " + + value_type_str(arg.value_type) + + " got integer"); + } + arg.value.set(value); + arg.is_set = true; + } + + //! \brief Set the value of the option + void set_option(BaseOption& arg, double value) { + if (arg.value_type != ValueType::floating) { + throw std::runtime_error("Wrong type : expected " + + value_type_str(arg.value_type) + + " got double"); + } + arg.value.set(value); + arg.is_set = true; + + } + + template + auto get_option( + const std::string& long_flag + ) -> typename ValueTypeConversion::type + { + const size_t ind = get_safe_long_option_index(long_flag); + return m_list_opt[ind].get(); + } + + template + auto get_pos_argument( + const std::string& name + ) -> typename ValueTypeConversion::type + { + const size_t ind = get_safe_pos_argument_index(name); + return m_list_args[ind].get(); + } //! \print the help message void print_help_message(); std::string m_name {""}; //!< Name of the program std::string m_help_msg {""}; //! Global help message list_switch_option m_list_opt; //!< List of options list_pos_argument m_list_args; //!< List of positional argument - //! \brief Store the values of the string options - //! - //! This list will store the values of the options - std::vector m_option_string_values; }; //! \name simple_predicator //! \brief simple functions to chech the type of a string in argv //! //! \internal //! @{ inline bool is_non_null(const std::string& option) { return (option.length() > 0); } inline bool is_flag(const std::string& option) { return (is_non_null(option) && option[0] == '-'); } inline bool is_value(const std::string& option) { return (is_non_null(option) && option[0] != '-'); } inline bool is_short_flag(const std::string& option) { return (is_flag(option) && option.length() >= 2 and option[1] != '-'); } inline bool is_unique_short_flag(const std::string& option) { return (is_flag(option) && option.length() == 2 and option[1] != '-'); } inline bool is_same_short_flag(const std::string& option, char flag) { return (is_unique_short_flag(option) and option['1'] == flag); } inline bool is_multiple_short_flags(const std::string& option) { return (is_short_flag(option) and option.length() > 2); } inline bool is_long_flag(const std::string& option) { return (is_flag(option) and option.length() > 2 and option[1] == '-'); } inline bool is_same_long_flag(const std::string& option, const std::string& flag) { return (is_long_flag(option) and (option.length()-2) == flag.length() and option.substr(2, option.length()-2) == flag ); } //! @} //! \brief A pair of for an option using option_value_t = std::pair; //! \brief Split a long option in a pair (if = sign in it) option_value_t SPECMICP_DLL_LOCAL split_long_option(const std::string& opts); //! \brief Split a short option std::vector SPECMICP_DLL_LOCAL split_short_options(const std::string& opts); // Definition of main functions CommandLineParser::CommandLineParser(): m_impl(make_unique()) { } CommandLineParser::~CommandLineParser() {} // Getter / Setter // ============== // /!\ templated code : needs to be in that order to // avoid instanciation of a specialization before we define it // CommandLineParser::get_option // ----------------------------- + + template <> auto CommandLineParser::get_option( const std::string& long_flag - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_long_option_index(long_flag); - auto& opt = m_impl->m_list_opt[ind]; - if (opt.value_type != ValueType::boolean) { - throw std::runtime_error("Not the right type ! Expected boolean."); - } - return (opt.value.boolean); + return m_impl->get_option(long_flag); } template <> auto CommandLineParser::get_option( const std::string& long_flag - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_long_option_index(long_flag); - auto& opt = m_impl->m_list_opt[ind]; - if (opt.value_type != ValueType::integer) { - throw std::runtime_error("Not the right type ! Expected integer."); - } - return (opt.value.integer); + return m_impl->get_option(long_flag); } template <> auto CommandLineParser::get_option( const std::string& long_flag - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_long_option_index(long_flag); - auto& opt = m_impl->m_list_opt[ind]; - if (opt.value_type != ValueType::floating) { - throw std::runtime_error("Not the right type ! Expected floating number."); - } - return (opt.value.floating_number); + return m_impl->get_option(long_flag); } template <> auto CommandLineParser::get_option( const std::string& long_flag - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_long_option_index(long_flag); - auto& opt = m_impl->m_list_opt[ind]; - if (opt.value_type != ValueType::string) { - throw std::runtime_error("Not the right type ! Expected string."); - } - std::string* str_ptr = opt.value.str; - if (str_ptr == nullptr) { - throw std::runtime_error("Value not set !"); - } - return *(opt.value.str); + return m_impl->get_option(long_flag); } // CommandLineParser.get_pos_argument // ----------------------------------- template <> auto CommandLineParser::get_pos_argument( const std::string& name - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_pos_argument_index(name); - auto& opt = m_impl->m_list_args[ind]; - if (opt.value_type != ValueType::boolean) { - throw std::runtime_error("Not the right type ! Expected boolean."); - } - return (opt.value.boolean); + return m_impl->get_pos_argument(name); } template <> auto CommandLineParser::get_pos_argument( const std::string& name - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_pos_argument_index(name); - auto& opt = m_impl->m_list_args[ind]; - if (opt.value_type != ValueType::integer) { - throw std::runtime_error("Not the right type ! Expected integer."); - } - return (opt.value.integer); + return m_impl->get_pos_argument(name); } template <> auto CommandLineParser::get_pos_argument( const std::string& name - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_pos_argument_index(name); - auto& opt = m_impl->m_list_args[ind]; - if (opt.value_type != ValueType::floating) { - throw std::runtime_error("Not the right type ! Expected floating number."); - } - return (opt.value.floating_number); + return m_impl->get_pos_argument(name); } template <> auto CommandLineParser::get_pos_argument( const std::string& name - ) -> typename TrueValueType::type + ) -> typename ValueTypeConversion::type { - size_t ind = m_impl->get_safe_pos_argument_index(name); - auto& opt = m_impl->m_list_args[ind]; - if (opt.value_type != ValueType::string) { - throw std::runtime_error("Not the right type ! Expected string."); - } - std::string* str_ptr = opt.value.str; - if (str_ptr == nullptr) { - throw std::runtime_error("Value not set !"); - } - return *(opt.value.str); + return m_impl->get_pos_argument(name); } template T get_value(const OptionValue& u_value, ValueType value_type) { T value; switch (value_type) { case ValueType::boolean: - value = T(u_value.boolean); + value = u_value.get(); break; case ValueType::integer: - value = T(u_value.integer); + value = u_value.get(); break; case ValueType::floating: - value = T(u_value.floating_number); + value = u_value.get(); break; case ValueType::string: - value = T(*u_value.str); + value = u_value.get(); break; } return value; } -template -OptionValue -CommandLineParser::CommandLineParserImpl::set_value( - const T& value, - ValueType value_type - ) -{ - OptionValue u_value; - switch (value_type) { - case ValueType::boolean: - u_value.boolean = bool(value); - break; - case ValueType::integer: - u_value.integer = int(value); - break; - case ValueType::floating: - u_value.floating_number = double(value); - break; - case ValueType::string: - m_option_string_values.emplace_back(std::to_string(value)); - u_value.str = &m_option_string_values.back(); - break; - } - return u_value; -} - -template <> -OptionValue -CommandLineParser::CommandLineParserImpl::set_value( - const std::string& value, - ValueType value_type - ) -{ - OptionValue u_value; - switch (value_type) { - case ValueType::boolean: - if (value == "True" or - value == "true" or - value == "ON" or - value == "on") - u_value.boolean = true; - else - u_value.boolean = false; - break; - case ValueType::integer: - u_value.integer = std::stoi(value); - break; - case ValueType::floating: - u_value.floating_number = std::stod(value); - break; - case ValueType::string: - m_option_string_values.emplace_back(value); - u_value.str = &m_option_string_values.back(); - break; - } - return u_value; -} - -template -void -CommandLineParser::CommandLineParserImpl::set_option( - BaseOption& opt, - const T& value - ) -{ - opt.value = set_value(value, opt.value_type); - opt.is_set = true; -} - - // Add options and positional arguments // ==================================== void CommandLineParser::add_option( char short_flag, const std::string& long_flag, ValueType value_type, const std::string& help_message ) { m_impl->m_list_opt.emplace_back( short_flag, long_flag, value_type, help_message); } void CommandLineParser::add_option( char short_flag, const std::string& long_flag, int default_value, const std::string& help_message ) { m_impl->m_list_opt.emplace_back( short_flag, long_flag, ValueType::integer, - m_impl->set_value(default_value, ValueType::integer), + m_impl->set_value(default_value, ValueType::integer), help_message); } void CommandLineParser::add_option( char short_flag, const std::string& long_flag, double default_value, const std::string& help_message ) { m_impl->m_list_opt.emplace_back( short_flag, long_flag, ValueType::floating, - m_impl->set_value(default_value, ValueType::floating), + m_impl->set_value(default_value, ValueType::floating), help_message ); } void CommandLineParser::add_option( char short_flag, const std::string& long_flag, bool default_value, const std::string& help_message ) { m_impl->m_list_opt.emplace_back( short_flag, long_flag, ValueType::boolean, - m_impl->set_value(default_value, ValueType::boolean), + m_impl->set_value(default_value, ValueType::boolean), help_message ); } void CommandLineParser::add_option( char short_flag, const std::string& long_flag, const std::string& default_value, const std::string& help_message ) { m_impl->m_list_opt.emplace_back( short_flag, long_flag, ValueType::string, m_impl->set_value(default_value, ValueType::string), help_message ); } void CommandLineParser::add_pos_argument( const std::string& name, ValueType value_type, const std::string& help_message ) { m_impl->m_list_args.emplace_back(name, value_type, help_message); } // Parsing // ======= int CommandLineParser::parse(const std::vector& opts) { add_option('h', "help", false, "Print the help message"); m_impl->set_list_str_option(opts); if (get_option("help")) { // if help we bypass m_impl->print_help_message(); return 1; } else { m_impl->check_values(); return 0; } } int CommandLineParser::parse(int argc, char* argv[]) { // register program name register_program_name(std::string(argv[0])); // first build the list of options std::vector opts; opts.reserve(argc); for (int i=1; im_name = name; } void CommandLineParser::set_help_message(std::string&& help_msg) { m_impl->m_help_msg = help_msg; } void CommandLineParser::CommandLineParserImpl::print_help_message() { std::stringstream msg; msg << "Usage : " << m_name << " [Options]"; for (auto& arg: m_list_args) { msg << " <" + arg.name + ">"; } msg << "\n\n" << m_help_msg; msg << "\n\n Positional arguments :\n-----------------------\n"; for (auto& arg: m_list_args) { msg << "\t" << arg.name << " : " << arg.help_message << "\n"; } msg << "\n Options :\n----------\n"; for (auto& opt: m_list_opt) { if (opt.value_type == ValueType::boolean) { msg << "\t -" << opt.short_flag << ", --" << opt.long_flag << " : " << opt.help_message << "\n"; } else { msg << "\t -" << opt.short_flag << " " << ", --" << opt.long_flag << "=" << " : " << opt.help_message << "\n"; } } std::cout << msg.str(); } // Check values and options // ======================== bool CommandLineParser::CommandLineParserImpl::check_values() { bool retcode = check_pos_arguments(); retcode = retcode and check_options(); return retcode; } bool CommandLineParser::CommandLineParserImpl::check_pos_arguments() { size_t expected = m_list_args.size(); size_t provided = 0; for (auto& arg: m_list_args) { if (arg.is_set) ++provided; } if (provided != expected) { throw std::runtime_error("Missing (a) positional argument(s) :" "expected : " + std::to_string(expected) + " , provided : " + std::to_string(provided) + "." ); } return true; } bool CommandLineParser::CommandLineParserImpl::check_options() { for (auto& opt: m_list_opt) { if ((opt.is_required) and (not opt.is_set)) { throw std::runtime_error("Missing required option : '" + opt.long_flag + "'."); } } return true; } // Parse the list of argument // ========================== void CommandLineParser::CommandLineParserImpl::set_list_str_option( const std::vector& options ) { if (options.size() < 1) return; m_list_opt.reserve(options.size()); for (size_type ind=0; ind(inda)]; set_option(the_option, arg_value); return ++ind; } size_t CommandLineParser::CommandLineParserImpl::analyse_short_option( size_t ind, const raw_list_option& options, char opt) { int option_index = get_short_option_index(opt); if (option_index < 0) { std::string msg = "Error : unknown option '"; msg.push_back(opt); msg += "'."; throw std::runtime_error(msg); } SwitchArgument& the_option = m_list_opt[option_index]; ValueType vtype = the_option.value_type; if (vtype == ValueType::boolean) { set_option(the_option, true); ++ind; } else { if (ind > 0 and ind >= options.size()-1) { throw std::runtime_error( "Missing an argument for option : " + the_option.long_flag ); } std::string next_value = std::string(options[ind+1]); if (not is_value(next_value)) { throw std::runtime_error ("Missing an argument for option : " + the_option.long_flag ); } ind += 2; set_option(the_option, next_value); } return ind; } size_t CommandLineParser::CommandLineParserImpl::analyse_long_option( size_t ind, const raw_list_option& option, const std::string& opt) { option_value_t pair = split_long_option(opt); int option_index = get_long_option_index(pair.first); SwitchArgument& the_option = m_list_opt[option_index]; ValueType vtype = the_option.value_type; if (pair.second.size() > 0) { set_option(the_option, pair.second); ++ind; return ind; } if (vtype == ValueType::boolean) { if (pair.second.size() > 0) set_option(the_option, pair.second); else set_option(the_option, true); ++ind; } else { if (ind >= option.size()) { throw std::runtime_error( "Missing an argument for option : " + the_option.long_flag ); } std::string next_value = std::string(option[ind+1]); if (not is_value(next_value)) { throw std::runtime_error( "Missing an argument for option : " + the_option.long_flag ); } set_option(the_option, next_value); ind = ind+2; } return ind; } size_t CommandLineParser::CommandLineParserImpl::analyse_boolean_short_option( size_t ind, char option) { int option_index = get_short_option_index(option); if (option_index < 0) { std::string msg = "Error : unknown option '"; msg.push_back(option); msg += "'."; throw std::runtime_error(msg); } SwitchArgument& the_option = m_list_opt[option_index]; ValueType vtype = the_option.value_type; if (vtype != ValueType::boolean) { throw std::runtime_error( "Error, expected boolean for option " + the_option.long_flag ); } set_option(the_option, true); return ind; } // Indexes of options // ================== int CommandLineParser::CommandLineParserImpl::get_short_option_index(char flag) noexcept { int ind = -1; for (auto it=m_list_opt.begin();it!=m_list_opt.end();++it) { if (it->short_flag == flag) { ind = it - m_list_opt.begin(); break; } } return ind; } int CommandLineParser::CommandLineParserImpl::get_long_option_index( const std::string& flag) noexcept { int ind = -1; for (auto it=m_list_opt.begin();it!=m_list_opt.end();++it) { if (it->long_flag == flag) { ind = it - m_list_opt.begin(); break; } } return ind; } size_t CommandLineParser::CommandLineParserImpl::get_safe_long_option_index( const std::string& long_flag) { int ind = get_long_option_index(long_flag); if (ind == -1) { throw std::runtime_error("No such option : " + long_flag); } return static_cast(ind); } int CommandLineParser::CommandLineParserImpl::next_free_pos_arg() noexcept { int ind = -1; for (auto it=m_list_args.begin(); it!=m_list_args.end(); ++it) { if (! (*it).is_set) { ind = it - m_list_args.begin(); break; } } return ind; } size_t CommandLineParser::CommandLineParserImpl::get_safe_pos_argument_index( const std::string& name ) { int ind = -1; for (auto it=m_list_args.begin(); it!=m_list_args.end(); ++it) { if (name == (*it).name) { ind = it - m_list_args.begin(); break; } } if (ind < 0) { throw std::runtime_error("Unknow positional argument : " + name); } return static_cast(ind); } // Split options // ============= // remove '-' for short and '--' for long options // also split long options if they are in the form 'opt=value' option_value_t split_long_option(const std::string& opts) { option_value_t val; auto it = std::find(opts.begin(), opts.end(), '='); // no equal sign if (it == opts.end()) { val.first = opts.substr(2, std::string::npos); } // equal sign else { std::string::size_type len_before = it - opts.begin(); val.first = opts.substr(2, len_before-2); if (opts.size() > len_before) { // if there is something after val.second = opts.substr(len_before+1, std::string::npos); } } return val; } std::vector split_short_options(const std::string& opts) { std::vector shortopts; shortopts.reserve(opts.size()-1); for (size_t i=1; i Princeton University All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ============================================================================= */ #ifndef SPECMICP_CLI_PARSER_HPP #define SPECMICP_CLI_PARSER_HPP //! \file cli/parser.hpp //! \brief The option parser #include "specmicp_common/macros.hpp" #include #include #include namespace specmicp { //! \namespace specmicp::cli //! \brief Namespace for the option parsing module namespace cli { //! \enum ValueType //! \brief Type of the options enum class ValueType { boolean, //!< Boolean string, //!< String of characters integer, //!< Integer floating //!< Floating point number }; //! \brief Type writer to obtain the correct underlying type //! -//! \tparam val_type type of the option -template -struct TrueValueType {}; +//! \tparam value_type type of the option +template +struct ValueTypeConversion{}; + template <> -struct TrueValueType +struct ValueTypeConversion { - using type=bool; + using type = bool; }; template <> -struct TrueValueType +struct ValueTypeConversion { - using type=std::string; + using type = int; }; template <> -struct TrueValueType +struct ValueTypeConversion { - using type=int; + using type = double; }; template <> -struct TrueValueType +struct ValueTypeConversion { - using type=double; + using type = std::string; }; /*! \brief The command line parser This class is the interface to the command line parser. It works in 3 steps : - Add options - Parse command line arguments - Obtain value for the options */ class SPECMICP_DLL_PUBLIC CommandLineParser { public: CommandLineParser(); ~CommandLineParser(); //! \brief Add a required option of 'value_type' //! //! The option would need to be provided by the user void add_option( char short_flag, const std::string& long_flag, ValueType value_type, const std::string& help_message ); //! \brief Add an optional integer option //! //! 'default_value' will be used as the default value void add_option( char short_flag, const std::string& long_flag, int default_value, const std::string& help_message ); //! \brief Add an optional floating number option //! //! 'default_value' will be used as the default value void add_option( char short_flag, const std::string& long_flag, double default_value, const std::string& help_message ); //! \brief Add an optional boolean option //! //! 'default_value' will be used as the default value void add_option( char short_flag, const std::string& long_flag, bool default_value, const std::string& help_message ); //! \brief Add an optional string option //! //! 'default_value' will be used as the default value void add_option( char short_flag, const std::string& long_flag, const std::string& default_value, const std::string& help_message ); //! \brief Add a positional argument void add_pos_argument( const std::string& name, ValueType value_type, const std::string& help_message ); //! \brief Register the name of the program void register_program_name(std::string&& name); //! \brief Set the help message of the program void set_help_message(std::string&& help_msg); //! \brief Parse the options int parse(const std::vector& opts); //! \brief Parse the options provided in standard format int parse(int argc, char* argv[]); //! \brief Return the value of an option //! //! \tparam val_type type of the option //! \param long_flag long flag used for the option template auto get_option(const std::string& long_flag) - -> typename TrueValueType::type; + -> typename ValueTypeConversion::type; //! \brief Return the value of a positional argument //! //! \tparam val_type type of the argument //! \param name name of the argument template auto get_pos_argument(const std::string& name) - -> typename TrueValueType::type; + -> typename ValueTypeConversion::type; private: struct SPECMICP_DLL_LOCAL CommandLineParserImpl; //! \brief Implementation details std::unique_ptr m_impl; }; // The following macros define the only template specialization // allowed for the get_option and get_pos_argument values #define spc_def_cli_get_option(x) \ template <> \ auto CommandLineParser::get_option( \ const std::string& long_flag \ - ) -> typename TrueValueType::type; + ) -> typename ValueTypeConversion::type; spc_def_cli_get_option(ValueType::boolean) spc_def_cli_get_option(ValueType::floating) spc_def_cli_get_option(ValueType::integer) spc_def_cli_get_option(ValueType::string) #undef spc_def_get_option #define spc_def_cli_get_pos_argument(x) \ template <> \ auto CommandLineParser::get_pos_argument( \ const std::string& long_flag \ - ) -> typename TrueValueType::type; + ) -> typename ValueTypeConversion::type; spc_def_cli_get_pos_argument(ValueType::boolean) spc_def_cli_get_pos_argument(ValueType::floating) spc_def_cli_get_pos_argument(ValueType::integer) spc_def_cli_get_pos_argument(ValueType::string) #undef spc_def_get_pos_argument } //end namespace cli } //end namespace specmicp #endif // SPECMICP_CLI_PARSER_HPP