В этом разделе мы рассмотрим наиболее распространенные сценарии использования библиотеки Program_options, начиная с самого простого. Примеры показывают только интересные части кода, но полные программы можно найти в каталоге «BOOST_ROOT/libs/program_options/example». На всех примерах мы предположим, что действует следующий псевдоним пространства имен:
namespace po = boost::program_options;
Первый пример самый простой: он обрабатывает только два варианта. Вот исходный код (полная программа находится в "пример/первый.cpp"):
// Declare the supported options.
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("compression", po::value<int>(), "set compression level")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);
if (vm.count("help")) {
cout << desc << "\n";
return 1;
}
if (vm.count("compression")) {
cout << "Compression level was set to "
<< vm["compression"].as<int>() << ".\n";
} else {
cout << "Compression level was not set.\n";
}
Мы начинаем с объявления всех разрешенных вариантов с использованием класса<options_description
>. Метод<add_options
>этого класса возвращает специальный прокси-объект, который определяет<operator()
>. Звонки этому оператору фактически объявляют опции. Параметрами являются имя опции, информация о значении и описание опции. В этом примере первый вариант не имеет значения, а второй имеет значение типа<int
>.
После этого объявляется объект класса<variables_map
>. Этот класс предназначен для хранения значений опционов и может хранить значения произвольных типов. Затем вызовы<store
>,<parse_command_line
>и<notify
>функций заставляют<vm
>содержать все опции, найденные в командной строке.
И теперь, наконец, мы можем использовать варианты, как нам нравится. Класс<variables_map
>может использоваться так же, как<std::map
>, за исключением того, что значения, хранящиеся там, должны быть извлечены с помощью метода<as
>, показанного выше. (Если тип, указанный в вызове к<as
>способу, отличается от фактически сохраненного типа, забрасывается исключение.)
Сейчас самое время попробовать составить код самостоятельно, но если вы еще не готовы, вот пример сессии:
$ bin/gcc/debug/first
Compression level was not set.
$ bin/gcc/debug/first --help
Allowed options:
--help : produce help message
--compression arg : set compression level
$ bin/gcc/debug/first --compression 10
Compression level was set to 10.
Значение опциона, безусловно, может иметь другие типы, чем<int
>, и может иметь другие интересные свойства, которые мы обсудим прямо сейчас. Полную версию кода можно найти в<example/options_description.cpp
>.
Представьте, что мы пишем компилятор. Он должен занимать уровень оптимизации, включать в себя несколько путей и несколько входных файлов и выполнять некоторые интересные работы. Давайте опишем варианты:
int opt;
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("optimization", po::value<int>(&opt)->default_value(10),
"optimization level")
("include-path,I", po::value< vector<string> >(),
"include path")
("input-file", po::value< vector<string> >(), "input file")
;
Вариант<"help"
>должен быть знаком из предыдущего примера. Это хорошая идея, чтобы иметь этот вариант во всех случаях.
Вариант<"optimization"
>показывает две новые функции. Во-первых, мы указываем адрес переменной<&opt
>. После хранения значений эта переменная будет иметь значение опции. Во-вторых, мы указываем значение 10 по умолчанию, которое будет использоваться, если пользователь не указал значение.
Вариант<"include-path"
>является примером единственного случая, когда интерфейс класса<options_description
>обслуживает только один источник — командную строку. Пользователи обычно любят использовать короткие имена опций для общих опций, а имя «include-path, I» указывает, что короткое имя опции — «I». Таким образом, можно использовать как «--включить-путь», так и «-я».
Отметим также, что тип<"include-path"
>опцииstd::vector. Библиотека обеспечивает специальную поддержку векторов — можно будет несколько раз указать опцию, и все заданные значения будут собраны в один вектор.
Опция «вход-файл» определяет список файлов для обработки. Это нормально для начала, но, конечно, написать что-то вроде:
compiler --input-file=a.cpp
Это немного нестандартно по сравнению с
compiler a.cpp
Мы обсудим это через минуту.
Токены командной строки, которые не имеют названия опции, как указано выше, называются этой библиотекой «позиционными опциями». С ними тоже можно справиться. С небольшой помощью пользователя библиотека может решить, что «a.cpp» на самом деле означает то же самое, что «--input-file=a.cpp». Вот дополнительный код, который нам нужен:
po::positional_options_description p;
p.add("input-file", -1);
po::variables_map vm;
po::store(po::command_line_parser(ac, av).
options(desc).positional(p).run(), vm);
po::notify(vm);
Первые две строки говорят, что все позиционные опции должны быть переведены в опции «вход-файл». Также обратите внимание, что мы используем класс<command_line_parser
>для разбора командной строки, а не функцию<parse_command_line
>. Последний является удобной оберткой для простых случаев, но сейчас нужно передать дополнительную информацию.
К настоящему времени все варианты описаны и разобраны. Мы избавим себя от проблем с реализацией остальной логики компилятора и распечатаем только опции:
if (vm.count("include-path"))
{
cout << "Include paths are: "
<< vm["include-path"].as< vector<string> >() << "\n";
}
if (vm.count("input-file"))
{
cout << "Input files are: "
<< vm["input-file"].as< vector<string> >() << "\n";
}
cout << "Optimization level is " << opt << "\n";
Вот пример сессии:
$ bin/gcc/debug/options_description --help
Usage: options_description [options]
Allowed options:
--help : produce help message
--optimization arg : optimization level
-I [ --include-path ] arg : include path
--input-file arg : input file
$ bin/gcc/debug/options_description
Optimization level is 10
$ bin/gcc/debug/options_description --optimization 4 -I foo a.cpp
Include paths are: foo
Input files are: a.cpp
Optimization level is 4
Упс, есть небольшая проблема. По-прежнему можно указать опцию «-вход-файл», и сообщение об использовании говорит об этом, что может сбить с толку пользователя. Было бы неплохо скрыть эту информацию, но давайте подождем следующего примера.
Вполне вероятно, что указание всех опций нашему компилятору в командной строке будет раздражать пользователей. Что делать, если пользователь устанавливает новую библиотеку и хочет всегда проходить дополнительный элемент командной строки? Что, если он сделал выбор, который должен быть применен на каждом бегу? Желательно создать файл конфигурации с общими настройками, который будет использоваться вместе с командной строкой.
Конечно, потребуется объединить значения из командной строки и файла конфигурирования. Например, уровень оптимизации, указанный в командной строке, должен переопределять значение из файла конфигурации. С другой стороны, следует объединить пути.
Теперь посмотрим код. Полная программа находится в "примеры/multiple_sources.cpp". Определение опции имеет две интересные детали. Во-первых, мы объявляем несколько экземпляров класса<options_description
>. Причина в том, что в целом не все варианты одинаковы. Некоторые опции, такие как «вход-файл» выше, не должны отображаться в автоматическом сообщении справки. Некоторые опции имеют смысл только в файле конфигурирования. Наконец, приятно иметь некоторую структуру в сообщении о помощи, а не просто длинный список вариантов. Объявим несколько групп вариантов:
// Declare a group of options that will be
// allowed only on command line
po::options_description generic("Generic options");
generic.add_options()
("version,v", "print version string")
("help", "produce help message")
;
// Declare a group of options that will be
// allowed both on command line and in
// config file
po::options_description config("Configuration");
config.add_options()
("optimization", po::value<int>(&opt)->default_value(10),
"optimization level")
("include-path,I",
po::value< vector<string> >()->composing(),
"include path")
;
// Hidden options, will be allowed both on command line and
// in config file, but will not be shown to the user.
po::options_description hidden("Hidden options");
hidden.add_options()
("input-file", po::value< vector<string> >(), "input file")
;
Обратите внимание на призыв к методу<composing
>в объявлении опции «включающий путь». Это говорит библиотеке, что ценности из разных источников должны быть составлены вместе, как мы скоро увидим.
Способ<add
>класса<options_description
>может быть использован для дальнейшей группировки вариантов:
po::options_description cmdline_options;
cmdline_options.add(generic).add(config).add(hidden);
po::options_description config_file_options;
config_file_options.add(config).add(hidden);
po::options_description visible("Allowed options");
visible.add(generic).add(config);
Разбор и хранение значений следует обычной схеме, за исключением того, что мы дополнительно называем<parse_config_file
>и называем функцию<store
>дважды. Но что происходит, если одно и то же значение указано как в командной строке, так и в файле конфигурирования? Обычно значение, сохраненное первым, является предпочтительным. Вот что происходит с опцией «оптимизация». Для «составляющих» опций, таких как «включающий файл», значения объединяются.
Вот пример сессии:
$ bin/gcc/debug/multiple_sources
Include paths are: /opt
Optimization level is 1
$ bin/gcc/debug/multiple_sources --help
Allows options:
Generic options:
-v [ --version ] : print version string
--help : produce help message
Configuration:
--optimization n : optimization level
-I [ --include-path ] path : include path
$ bin/gcc/debug/multiple_sources --optimization=4 -I foo a.cpp b.cpp
Include paths are: foo /opt
Input files are: a.cpp b.cpp
Optimization level is 4
Первый вызов использует значения из файла конфигурации. Второй вызов также использует значения из командной строки. Как мы видим, пути включения в командной строке и в файле конфигурации сливаются, а оптимизация берется из командной строки.