Search code examples
c++boostboost-program-options

Using '[' and ']' with boost::program_options


I am using boost add_options() like this way:


("setclient", po::value<std::string>(), "<awsipaddress>[:awsport[:mystring]] Change the settings.")

This works well if I use:
./mycli —setclient 192.68.1.1:8888:ui

I want to allow special character like [ and ]. The usage will be like

./mycli --setclient [192.68.1.1]:8888:ui

Try out: I changed this

("setclient", po::value<std::string>(), "<awsipaddress>[:awsport[:mystring]] Change the settings.”)

to

("setclient", po::value<std::string>(), "<[awsipaddress]:>[:awsport[:mystring]] Change the settings.")

But doesn’t work for me

Mentioned in the problem statement my try out


Solution

  • The edited code is a descriptive text only, just intended to be printed as usage instructions. It doesn't encode a grammar or anything. So any changes to it don't matter.

    The commenter has a good point that might get you half-way: the [] characters will often be special to the shell you're using. Wrap them in quotes e.g.:

    ./mycli --setclient '[192.68.1.1]:8888:ui'
    

    or

    ./mycli --setclient "[192.68.1.1]:8888:ui"
    

    Now, to actually support the additional characters you need to have a parser that expects the [ and ] characters added. If that's not yet the case, please update with a self-contained example (that correctly handles the old syntax) and we can tell you what is missing.

    Live Demo

    With the simplest form:

    Live On Coliru

    #include <boost/program_options.hpp>
    #include <iostream>
    
    namespace po = boost::program_options;
    
    int main(int argc, char** argv) {
        po::options_description desc;
        desc.add_options() //
            ("setclient", po::value<std::string>(),
             "<awsipaddress>[:awsport[:mystring]] Change the settings.") //
            ;                                                            //
    
    
        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
    
        std::cout << "Got: '" << vm["setclient"].as<std::string>() <<"'\n";
    }
    

    Prints

    ./a.out --setclient 192.68.1.1:8888:ui
    Got: '192.68.1.1:8888:ui'
    ./a.out --setclient [192.68.1.1]:8888:ui
    Got: '[192.68.1.1]:8888:ui'
    

    BONUS: Ipv6?

    Guessing that you MIGHT require [] to allow ipv6 addresses (as they contain : characters), have a look at What is the nicest way to parse this in C++?, and a new live demo integrating that in your program-options:

    Live On Coliru

    #include <boost/asio/ip/address.hpp>
    #include <boost/program_options.hpp>
    #include <iomanip>
    #include <iostream>
    
    namespace po = boost::program_options;
    using boost::asio::ip::address;
    
    struct Endpoint {
        address     address_;
        uint16_t    port_;
        std::string name_;
    
        static Endpoint parse_endpoint(std::string_view);
    };
    
    int main(int argc, char** argv) {
        po::options_description desc;
        std::string setclient;
        desc.add_options() //
            ("setclient", po::value<std::string>(&setclient),
             "<awsipaddress>[:awsport[:mystring]] Change the settings.");
    
        po::variables_map vm;
        store(po::parse_command_line(argc, argv, desc), vm);
        notify(vm);
    
        auto ep = Endpoint::parse_endpoint(setclient);
        std::cout << std::left << "\t"
                  << "Ip address: " << std::setw(12) << ep.address_ //
                  << "Port: " << std::setw(12) << ep.port_          //
                  << "Name: " << std::setw(12) << ep.name_          //
                  << "\n";
    }
    
    #include <boost/spirit/home/x3.hpp>
    #include <boost/fusion/adapted/struct.hpp>
    namespace x3 = boost::spirit::x3;
    BOOST_FUSION_ADAPT_STRUCT(Endpoint, address_, port_, name_)
    
    Endpoint Endpoint::parse_endpoint(std::string_view text) { //
        auto to_address = [](auto const& ctx) {
            auto& r   = _attr(ctx);
            _val(ctx) = address::from_string({r.begin(), r.end()});
        };
    
        auto octet = x3::uint8;
        auto ipv46 = x3::rule<void, address>{} = x3::raw[+~x3::char_("]")][to_address];
        auto ipv4                              = x3::rule<void, address>{} =
            x3::raw[octet >> '.' >> octet >> '.' >> octet >> '.' >> octet][to_address];
        auto port  = (':' >> x3::uint16) | x3::attr(443);
        auto name  = (':' >> +x3::char_) | x3::attr(std::string("ui"));
        auto spec  = ('[' >> ipv46 >> ']' >> port >> name | //
                     ipv4 >> port >> name)                 //
            >> x3::eoi;
    
        Endpoint ep;
        parse(begin(text), end(text), x3::expect[spec], ep);
        return ep;
    }
    

    Printing for various test inputs:

    + ./a.out --setclient 192.68.1.1
        Ip address: 192.68.1.1  Port: 443         Name: ui          
    + ./a.out --setclient 192.68.1.1:backend
        Ip address: 192.68.1.1  Port: 443         Name: backend     
    + ./a.out --setclient 192.68.1.1:8443
        Ip address: 192.68.1.1  Port: 8443        Name: ui          
    + ./a.out --setclient 192.68.1.1:8443:backend
        Ip address: 192.68.1.1  Port: 8443        Name: backend     
    + ./a.out --setclient '[::1]'
        Ip address: ::1         Port: 443         Name: ui          
    + ./a.out --setclient '[::1]:backend'
        Ip address: ::1         Port: 443         Name: backend     
    + ./a.out --setclient '[::1]:8443'
        Ip address: ::1         Port: 8443        Name: ui          
    + ./a.out --setclient '[::1]:8443:backend'
        Ip address: ::1         Port: 8443        Name: backend     
    + ./a.out --setclient 127.0.0.1
        Ip address: 127.0.0.1   Port: 443         Name: ui          
    + ./a.out --setclient 127.0.0.1:backend
        Ip address: 127.0.0.1   Port: 443         Name: backend     
    + ./a.out --setclient 127.0.0.1:8443
        Ip address: 127.0.0.1   Port: 8443        Name: ui          
    + ./a.out --setclient 127.0.0.1:8443:backend
        Ip address: 127.0.0.1   Port: 8443        Name: backend     
    

    BONUS 2: Converting Inside Program-Options

    Live On Coliru

    struct Endpoint {
        address     address_;
        uint16_t    port_;
        std::string name_;
    
        static Endpoint parse_endpoint(std::string_view);
    
        friend std::ostream& operator<<(std::ostream& os, Endpoint const& ep) {
            return os << "[" << ep.address_ << "]:" << ep.port_ << ":" << ep.name_;
        }
        friend void validate(boost::any& value, std::vector<std::string> const& tt, Endpoint*, int) {
            if (tt.size() != 1)
                throw po::invalid_option_value("too many tokens");
            value = parse_endpoint(tt.front());
        }
    };
    
    int main(int argc, char** argv) {
        po::options_description desc;
    
        Endpoint ep{boost::asio::ip::address_v4::loopback(), 443, "ui"};
        desc.add_options() //
            ("setclient", po::value<Endpoint>(&ep),
             "<awsipaddress>[:awsport[:mystring]] Change the settings.");
    
        po::variables_map vm;
        store(po::parse_command_line(argc, argv, desc), vm);
        notify(vm);
    
        std::cout << "\tparsed: " << ep << "\n";
    }