#pragma once #include namespace po = boost::program_options; #include #include #include #include #include #include "options.h" #include "options_types.h" #include "vw_exception.h" // Boost Program Options requires that all types that have a default option are ostreamable namespace std { template std::ostream& operator<<(std::ostream& os, const std::vector& vec) { for (auto const& item : vec) { os << item << ", "; } return os; } } // namespace std template std::ostream& std::operator<<(std::ostream&, const std::vector&); template std::ostream& std::operator<<(std::ostream&, const std::vector&); template std::ostream& std::operator<<(std::ostream&, const std::vector&); template std::ostream& std::operator<<(std::ostream&, const std::vector&); template std::ostream& std::operator<<(std::ostream&, const std::vector&); namespace VW { namespace config { struct options_boost_po : public options_i { options_boost_po(int argc, char** argv) : options_boost_po(std::vector(argv + 1, argv + argc)) {} options_boost_po(std::vector args) : m_command_line(args) {} options_boost_po(options_boost_po&) = delete; options_boost_po& operator=(options_boost_po&) = delete; virtual void add_and_parse(const option_group_definition& group) override; virtual bool was_supplied(const std::string& key) override; virtual std::string help() override; virtual void check_unregistered() override; virtual std::vector> get_all_options() override; virtual std::shared_ptr get_option(const std::string& key) override; virtual void insert(const std::string& key, const std::string& value) override { m_command_line.push_back("--" + key); if (value != "") { m_command_line.push_back(value); } } // Note: does not work for vector options. virtual void replace(const std::string& key, const std::string& value) override { auto full_key = "--" + key; auto it = std::find(m_command_line.begin(), m_command_line.end(), full_key); // Not found, insert instead. if (it == m_command_line.end()) { insert(key, value); return; } // Check if it is the final option or the next option is not a value. if (it + 1 == m_command_line.end() || (*(it + 1)).find("--") != std::string::npos) { THROW(key + " option does not have a value."); } // Actually replace the value. *(it + 1) = value; } // key must reference an option previously defined. bool try_get_positional_option_token(const std::string& key, std::string& token, int position) { po::positional_options_description p; p.add(key.c_str(), position); po::parsed_options pos = po::command_line_parser(m_command_line) .style(po::command_line_style::default_style ^ po::command_line_style::allow_guessing) .options(master_description) .allow_unregistered() .positional(p) .run(); auto it = std::find_if(pos.options.begin(), pos.options.end(), [&key](boost::program_options::option& option) { return option.string_key == key; }); if (it != pos.options.end() && (*it).value.size() > 0) { token = (*it).value.at(0); return true; } return false; } private: template typename po::typed_value>* get_base_boost_value(std::shared_ptr>& opt); template po::typed_value>* get_base_boost_value(std::shared_ptr>>& opt); template po::typed_value>* convert_to_boost_value(std::shared_ptr>& opt); template po::typed_value>* convert_to_boost_value(std::shared_ptr>>& opt); template po::typed_value>* add_notifier( std::shared_ptr>& opt, po::typed_value>* po_value); template po::typed_value>* add_notifier( std::shared_ptr>>& opt, po::typed_value>* po_value); template bool add_if_t(std::shared_ptr opt, po::options_description& options_description); void add_to_description(std::shared_ptr opt, po::options_description& options_description); template void add_to_description_impl(std::shared_ptr opt, po::options_description& options_description) { if (add_if_t(opt, options_description)) { return; } add_to_description_impl(opt, options_description); } template void add_to_description(std::shared_ptr> opt, po::options_description& options_description); private: std::map> m_options; std::vector m_command_line; std::stringstream m_help_stringstream; // All options that were supplied on the command line. std::set m_supplied_options; // Used the ignore values that get incorrectly interpreted as options. std::set m_ignore_supplied; po::options_description master_description; // All options that a description was provided for. std::set m_defined_options; }; template po::typed_value>* options_boost_po::get_base_boost_value(std::shared_ptr>& opt) { auto value = po::value>(); if (opt->default_value_supplied()) { value->default_value({opt->default_value()}); } return add_notifier(opt, value)->composing(); } template po::typed_value>* options_boost_po::get_base_boost_value( std::shared_ptr>>& opt) { auto value = po::value>(); if (opt->default_value_supplied()) { value->default_value(opt->default_value()); } return add_notifier(opt, value)->composing(); } template po::typed_value>* options_boost_po::convert_to_boost_value(std::shared_ptr>& opt) { return get_base_boost_value(opt); } template po::typed_value>* options_boost_po::convert_to_boost_value( std::shared_ptr>>& opt) { return get_base_boost_value(opt)->multitoken(); } template <> po::typed_value>* options_boost_po::convert_to_boost_value(std::shared_ptr>& opt); template po::typed_value>* options_boost_po::add_notifier( std::shared_ptr>& opt, po::typed_value>* po_value) { return po_value->notifier([opt](std::vector final_arguments) { T first = final_arguments[0]; for (auto const& item : final_arguments) { if (item != first) { std::stringstream ss; ss << "Disagreeing option values for '" << opt->m_name << "': '" << first << "' vs '" << item << "'"; THROW_EX(VW::vw_argument_disagreement_exception, ss.str()); } } // Set the value for the listening location. opt->m_location = first; opt->value(first); }); } template po::typed_value>* options_boost_po::add_notifier( std::shared_ptr>>& opt, po::typed_value>* po_value) { return po_value->notifier([opt](std::vector final_arguments) { // Set the value for the listening location. opt->m_location = final_arguments; opt->value(final_arguments); }); } template bool options_boost_po::add_if_t(std::shared_ptr opt, po::options_description& options_description) { if (opt->m_type_hash == typeid(T).hash_code()) { auto typed = std::dynamic_pointer_cast>(opt); add_to_description(typed, options_description); return true; } return false; } template <> void options_boost_po::add_to_description_impl>( std::shared_ptr opt, po::options_description& options_description); template void options_boost_po::add_to_description( std::shared_ptr> opt, po::options_description& options_description) { std::string boost_option_name = opt->m_name; if (opt->m_short_name != "") { boost_option_name += ","; boost_option_name += opt->m_short_name; } options_description.add_options()(boost_option_name.c_str(), convert_to_boost_value(opt), opt->m_help.c_str()); if (m_defined_options.count(opt->m_name) == 0) { // TODO may need to add noop notifier here. master_description.add_options()(boost_option_name.c_str(), convert_to_boost_value(opt), ""); } } } // namespace config } // namespace VW