Đối số Bắt buộc và Tùy chọn Sử dụng Tùy chọn Chương trình Thư viện Tăng cường


83

Tôi đang sử dụng Thư viện tùy chọn chương trình Boost để phân tích cú pháp các đối số dòng lệnh.

Tôi có các yêu cầu sau:

  1. Sau khi "trợ giúp" được cung cấp, tất cả các tùy chọn khác là tùy chọn;
  2. Khi "trợ giúp" không được cung cấp, tất cả các tùy chọn khác là bắt buộc.

Làm thế nào tôi có thể đối phó với điều này? Đây là đoạn mã của tôi xử lý điều này, và tôi thấy nó rất thừa, và tôi nghĩ rằng phải có một cách dễ dàng để làm, phải không?

#include <boost/program_options.hpp>
#include <iostream>
#include <sstream>
namespace po = boost::program_options;

bool process_command_line(int argc, char** argv,
                          std::string& host,
                          std::string& port,
                          std::string& configDir)
{
    int iport;

    try
    {
        po::options_description desc("Program Usage", 1024, 512);
        desc.add_options()
          ("help",     "produce help message")
          ("host,h",   po::value<std::string>(&host),      "set the host server")
          ("port,p",   po::value<int>(&iport),             "set the server port")
          ("config,c", po::value<std::string>(&configDir), "set the config path")
        ;

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        // There must be an easy way to handle the relationship between the
        // option "help" and "host"-"port"-"config"
        if (vm.count("host"))
        {
            std::cout << "host:   " << vm["host"].as<std::string>() << "\n";
        }
        else
        {
            std::cout << "\"host\" is required!" << "\n";
            return false;
        }

        if (vm.count("port"))
        {
            std::cout << "port:   " << vm["port"].as<int>() << "\n";
        }
        else
        {
            std::cout << "\"port\" is required!" << "\n";
            return false;
        }

        if (vm.count("config"))
        {
            std::cout << "config: " << vm["config"].as<std::string>() << "\n";
        }
        else
        {
            std::cout << "\"config\" is required!" << "\n";
            return false;
        }
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }
    catch(...)
    {
        std::cerr << "Unknown error!" << "\n";
        return false;
    }

    std::stringstream ss;
    ss << iport;
    port = ss.str();

    return true;
}

int main(int argc, char** argv)
{
  std::string host;
  std::string port;
  std::string configDir;

  bool result = process_command_line(argc, argv, host, port, configDir);
  if (!result)
      return 1;

  // Do the main routine here
}

Câu trả lời:


103

Tôi đã tự mình gặp phải vấn đề này. Chìa khóa của một giải pháp là chức năng này po::stoređiền vào variables_mapđồng thời po::notifylàm tăng bất kỳ lỗi nào gặp phải, vì vậy vmcó thể được sử dụng trước khi bất kỳ thông báo nào được gửi đi.

Vì vậy, theo Tim , hãy đặt từng tùy chọn thành bắt buộc, như mong muốn, nhưng hãy chạy po::notify(vm) sau khi bạn đã xử lý tùy chọn trợ giúp. Bằng cách này, nó sẽ thoát mà không có bất kỳ ngoại lệ nào được ném ra. Bây giờ, với các tùy chọn được đặt thành bắt buộc, một tùy chọn bị thiếu sẽ gây ra một required_optionngoại lệ và sử dụng get_option_namephương pháp của nó, bạn có thể giảm mã lỗi của mình thành một catchkhối tương đối đơn giản .

Một lưu ý bổ sung, các biến tùy chọn của bạn được đặt trực tiếp thông qua po::value< -type- >( &var_name )cơ chế, vì vậy bạn không cần phải truy cập chúng thông qua vm["opt_name"].as< -type- >().

Một ví dụ mã được cung cấp trong câu trả lời của Peters


Cảm ơn vì đã trả lời. Tôi nghĩ rằng nó hoạt động như mong đợi. Tôi cũng đã đăng chương trình hoàn chỉnh bên dưới cho những người cần một ví dụ điển hình.
Peter Lee

5
Giải pháp tuyệt vời! Tài liệu chính thức nên làm rõ điều đó với một ví dụ.
nga

@rcollyer, bạn có thể cung cấp một ví dụ làm việc đầy đủ được không?
Jonas Stein

@JonasStein Tôi có thể, nhưng Peter dường như vẫn ổn. Hãy cho tôi biết nếu điều đó là không đủ.
rcollyer

1
@rcollyer Trang web sx không kết nối trực quan hai câu trả lời, vì vậy tôi đã bỏ lỡ điều đó. Tôi đã thêm một ghi chú. Vui lòng hoàn nguyên, nếu bạn không thoải mái với nó.
Jonas Stein

46

Đây là chương trình hoàn chỉnh theo rcollyer và Tim, người mà các khoản tín dụng được chuyển đến:

#include <boost/program_options.hpp>
#include <iostream>
#include <sstream>
namespace po = boost::program_options;

bool process_command_line(int argc, char** argv,
                          std::string& host,
                          std::string& port,
                          std::string& configDir)
{
    int iport;

    try
    {
        po::options_description desc("Program Usage", 1024, 512);
        desc.add_options()
          ("help",     "produce help message")
          ("host,h",   po::value<std::string>(&host)->required(),      "set the host server")
          ("port,p",   po::value<int>(&iport)->required(),             "set the server port")
          ("config,c", po::value<std::string>(&configDir)->required(), "set the config path")
        ;

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        // There must be an easy way to handle the relationship between the
        // option "help" and "host"-"port"-"config"
        // Yes, the magic is putting the po::notify after "help" option check
        po::notify(vm);
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }
    catch(...)
    {
        std::cerr << "Unknown error!" << "\n";
        return false;
    }

    std::stringstream ss;
    ss << iport;
    port = ss.str();

    return true;
}

int main(int argc, char** argv)
{
  std::string host;
  std::string port;
  std::string configDir;

  bool result = process_command_line(argc, argv, host, port, configDir);
  if (!result)
      return 1;

  // else
  std::cout << "host:\t"   << host      << "\n";
  std::cout << "port:\t"   << port      << "\n";
  std::cout << "config:\t" << configDir << "\n";

  // Do the main routine here
}

/* Sample output:

C:\Debug>boost.exe --help
Program Usage:
  --help                produce help message
  -h [ --host ] arg     set the host server
  -p [ --port ] arg     set the server port
  -c [ --config ] arg   set the config path


C:\Debug>boost.exe
Error: missing required option config

C:\Debug>boost.exe --host localhost
Error: missing required option config

C:\Debug>boost.exe --config .
Error: missing required option host

C:\Debug>boost.exe --config . --help
Program Usage:
  --help                produce help message
  -h [ --host ] arg     set the host server
  -p [ --port ] arg     set the server port
  -c [ --config ] arg   set the config path


C:\Debug>boost.exe --host 127.0.0.1 --port 31528 --config .
host:   127.0.0.1
port:   31528
config: .

C:\Debug>boost.exe -h 127.0.0.1 -p 31528 -c .
host:   127.0.0.1
port:   31528
config: .
*/

3
Bạn nên nắm bắt boost::program_options::required_optionđể có thể xử lý việc thiếu một tùy chọn bắt buộc trực tiếp thay vì để nó bị bắt std::exception.
rcollyer

Cổng phải là loại không có dấu.
g33kz0r

2
Bạn chỉ nên bắt lỗi boost :: program_options :: này.
CreativeMind

13

Bạn có thể chỉ định rằng một tùy chọn được yêu cầu đủ dễ dàng [ 1 ], ví dụ:

..., value<string>()->required(), ...

nhưng theo như tôi biết thì không có cách nào để biểu diễn mối quan hệ giữa các tùy chọn khác nhau với thư viện chương trình_options.

Một khả năng là phân tích cú pháp dòng lệnh nhiều lần với các bộ tùy chọn khác nhau, sau đó nếu bạn đã kiểm tra "trợ giúp", bạn có thể phân tích cú pháp lại với ba tùy chọn khác được đặt theo yêu cầu. Tuy nhiên, tôi không chắc mình sẽ coi đó là một cải tiến so với những gì bạn có.


2
vâng, tôi có thể nói đúng như vậy ->required(), nhưng sau đó người dùng không thể nhận được thông tin trợ giúp bằng cách --help(mà không cung cấp tất cả các tùy chọn bắt buộc khác), vì các tùy chọn khác là bắt buộc.
Peter Lee

@ Peter Bạn sẽ tìm kiếm chỉ giúp lần đầu tiên, các tùy chọn khác sẽ thậm chí không có trong danh sách. Sau đó, nếu họ không vượt qua tùy chọn trợ giúp, chỉ khi đó bạn mới chạy lại phân tích cú pháp, lần này sẽ chuyển qua ba tùy chọn khác, được đặt thành bắt buộc và không trợ giúp. Cách tiếp cận này có thể sẽ yêu cầu một bộ tùy chọn thứ ba, với tất cả chúng được kết hợp, để sử dụng cho việc in thông tin sử dụng. Tôi khá chắc rằng nó sẽ hoạt động, nhưng cách tiếp cận của rcollyer rõ ràng hơn.
Tim Sylvester

1
    std::string conn_mngr_id;
    std::string conn_mngr_channel;
    int32_t priority;
    int32_t timeout;

    boost::program_options::options_description p_opts_desc("Program options");
    boost::program_options::variables_map p_opts_vm;

    try {

        p_opts_desc.add_options()
            ("help,h", "produce help message")
            ("id,i", boost::program_options::value<std::string>(&conn_mngr_id)->required(), "Id used to connect to ConnectionManager")
            ("channel,c", boost::program_options::value<std::string>(&conn_mngr_channel)->required(), "Channel to attach with ConnectionManager")
            ("priority,p", boost::program_options::value<int>(&priority)->default_value(1), "Channel to attach with ConnectionManager")
            ("timeout,t", boost::program_options::value<int>(&timeout)->default_value(15000), "Channel to attach with ConnectionManager")
        ;

        boost::program_options::store(boost::program_options::parse_command_line(argc, argv, p_opts_desc), p_opts_vm);

        boost::program_options::notify(p_opts_vm);

        if (p_opts_vm.count("help")) {
            std::cout << p_opts_desc << std::endl;
            return 1;
        }

    } catch (const boost::program_options::required_option & e) {
        if (p_opts_vm.count("help")) {
            std::cout << p_opts_desc << std::endl;
            return 1;
        } else {
            throw e;
        }
    }

Đó chắc chắn là một sự thay thế thú vị. Tuy nhiên, nó buộc bạn phải lặp lại mã xử lý trợ giúp và mặc dù nhỏ nhưng tôi có xu hướng tránh nó.
rcollyer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.