В этом разделе описывается, как библиотека может использоваться в определенных ситуациях.
Иногда стандартных синтаксисов командной строки недостаточно. Например, компилятор gcc имеет опции «-frtti» и «-fno-rtti», и этот синтаксис не поддерживается напрямую.
Для таких случаев библиотека позволяет пользователю предоставить дополнительный парсер— функцию, которая будет вызываться на каждом элементе командной строки перед любой обработкой библиотекой. Если дополнительный парсер распознает синтаксис, он возвращает имя и значение опции, которые используются непосредственно. Вышеприведенный пример может быть обработан следующим кодом:
pair<string, string> reg_foo(const string& s)
{
if (s.find("-f") == 0) {
if (s.substr(2, 3) == "no-")
return make_pair(s.substr(5), string("false"));
else
return make_pair(s.substr(2), string("true"));
} else {
return make_pair(string(), string());
}
}
Вот определение дополнительного парсера. При разборе командной строки мы проходим дополнительный парсер:
store(command_line_parser(ac, av).options(desc).extra_parser(reg_foo)
.run(), vm);
Полный пример можно найти в файле "example/custom_syntax.cpp".
Некоторые операционные системы имеют очень низкие пределы длины командной строки. Обычным способом обойти эти ограничения является использование файлов ответов.. Файл ответа — это просто файл конфигурации, который использует тот же синтаксис, что и командная строка. Если командная строка указывает имя файла ответа для использования, он загружается и анализируется в дополнение к командной строке. Библиотека не предоставляет прямой поддержки файлов ответов, поэтому вам нужно будет написать дополнительный код.
Для начала нужно определить опцию для файла ответа:
("response-file", value<string>(),
"can be specified with '@name', too")
Во-вторых, вам понадобится дополнительный парсер для поддержки стандартного синтаксиса для указания файлов ответа: «@file»:
pair<string, string> at_option_parser(string const&s)
{
if ('@' == s[0])
return std::make_pair(string("response-file"), s.substr(1));
else
return pair<string, string>();
}
Наконец, когда будет найдена опция «ответ-файл», вам придется загрузить этот файл и передать его в парсер командной строки. Эта часть самая сложная. Будем использовать буст. Библиотека Токенайзера, которая работает, но имеет некоторые ограничения. Вы также можете рассмотреть Boost. СтрингАлго. Код такой:
if (vm.count("response-file")) {
// Load the file and tokenize it
ifstream ifs(vm["response-file"].as<string>().c_str());
if (!ifs) {
cout << "Could not open the response file\n";
return 1;
}
// Read the whole file into a string
stringstream ss;
ss << ifs.rdbuf();
// Split the file content
char_separator<char> sep(" \n\r");
std::string ResponsefileContents( ss.str() );
tokenizer<char_separator<char> > tok(ResponsefileContents, sep);
vector<string> args;
copy(tok.begin(), tok.end(), back_inserter(args));
// Parse the file and store the options
store(command_line_parser(args).options(desc).run(), vm);
}
Полный пример можно найти в файле "example/response_file.cpp".
В операционной системе Windows приложения GUI получают командную строку как единую строку, не разделенную на элементы. По этой причине парсер командной строки не может использоваться напрямую. По крайней мере, на некоторых компиляторах можно получить разделенную командную строку, но неясно, поддерживают ли все компиляторы один и тот же механизм на всех версиях операционной системы. Функция<split_winmain
>является переносным механизмом, предоставляемым библиотекой.
Вот пример использования:
vector<string> args = split_winmain(lpCmdLine);
store(command_line_parser(args).options(desc).run(), vm);
Функция<split_winmain
>перегружена для<wchar_t
>строк, поэтому также может использоваться в приложениях Unicode.
Option Groups and Hidden Options
Наличие одного экземпляра класса<options_description
>со всеми вариантами программы может быть проблематичным:
Некоторые опции имеют смысл только для конкретного источника, например, конфигурационных файлов.
Пользователь предпочёл бы некоторую структуру в генерируемом сообщении справки.
Некоторые опции вообще не должны появляться в генерируемом сообщении о помощи.
Для решения вышеуказанных задач библиотека позволяет программисту создать несколько экземпляров класса<options_description
>, которые могут быть объединены в разные комбинации. В следующем примере будут определены три группы опций: специфичная командная строка и две группы опций для конкретных программных модулей, только одна из которых показана в генерируемом сообщении справки.
Каждая группа определяется с использованием стандартного синтаксиса. Однако для каждого случая следует использовать разумные имена<options_description
>:
options_description general("General options");
general.add_options()
("help", "produce a help message")
("help-module", value<string>(),
"produce a help for a given module")
("version", "output the version number")
;
options_description gui("GUI options");
gui.add_options()
("display", value<string>(), "display to use")
;
options_description backend("Backend options");
backend.add_options()
("num-threads", value<int>(), "the initial number of threads")
;
После объявления групп опционов мы объединяем их в две комбинации. Первый будет включать все варианты и использоваться для разбора. Второй вариант будет использоваться для «помощи».
// Declare an options description instance which will include
// all the options
options_description all("Allowed options");
all.add(general).add(gui).add(backend);
// Declare an options description instance which will be shown
// to the user
options_description visible("Allowed options");
visible.add(general).add(gui);
Остается только разобрать и обработать варианты:
variables_map vm;
store(parse_command_line(ac, av, all), vm);
if (vm.count("help"))
{
cout << visible;
return 0;
}
if (vm.count("help-module")) {
const string& s = vm["help-module"].as<string>();
if (s == "gui") {
cout << gui;
} else if (s == "backend") {
cout << backend;
} else {
cout << "Unknown module '"
<< s << "' in the --help-module option\n";
return 1;
}
return 0;
}
if (vm.count("num-threads")) {
cout << "The 'num-threads' options was set to "
<< vm["num-threads"].as<int>() << "\n";
}
При разборе командной строки допускаются все варианты. Сообщение «--помощь», однако, не включает группу «Backend options» — варианты в этой группе скрыты. Пользователь может явно форсировать отображение этой группы опций, передав опцию «--модуля поддержки backend». Полный пример можно найти в файле "example/option_groups.cpp".
По умолчанию преобразование значения опции из строки в тип C++ осуществляется с помощью iostreams, что иногда не удобно. Библиотека позволяет пользователю настроить конверсию для конкретных классов. Для этого пользователь должен обеспечить соответствующую перегрузку функции<validate
>.
Для начала определим простой класс:
struct magic_number {
public:
magic_number(int n) : n(n) {}
int n;
};
А затем перегрузить<validate
>функцию:
void validate(boost::any& v,
const std::vector<std::string>& values,
magic_number* target_type, int)
{
static regex r("\\d\\d\\d-(\\d\\d\\d)");
using namespace boost::program_options;
// Make sure no previous assignment to 'a' was made.
validators::check_first_occurrence(v);
// Extract the first string from 'values'. If there is more than
// one string, it's an error, and exception will be thrown.
const string& s = validators::get_single_string(values);
// Do regex match and convert the interesting part to
// int.
smatch match;
if (regex_match(s, match, r)) {
v = any(magic_number(lexical_cast<int>(match[1])));
} else {
throw validation_error(validation_error::invalid_option_value);
}
}
Функция имеет четыре параметра. Первое — это хранилище для значения, и в этом случае оно либо пусто, либо содержит экземпляр класса<magic_number
>. Второй - список строк, найденных в следующем случае опции. Оставшиеся два параметра необходимы для обхода отсутствия частичной специализации шаблона и частичного заказа шаблона функции на некоторых компиляторах.
Функция сначала проверяет, что мы не пытаемся назначить одну и ту же опцию дважды. Затем он проверяет, что была пропущена только одна строка. Затем струна проверяется с помощью Boost. Библиотека Regex. Если этот тест пройден, парсинговое значение сохраняется в переменной<v
>.
Полный пример можно найти в файле "example/regex.cpp".
Чтобы использовать библиотеку с Unicode, вам нужно:
Используйте Unicode-aware парсеры для ввода Unicode
Требуется поддержка Unicode для опций, которые в ней нуждаются
Большинство парсеров имеют версии Unicode. Например, функция<parse_command_line
>имеет перегрузку, которая принимает<wchar_t
>строк, вместо обычной<char
>.
Даже если некоторые из парсеров знакомы с Unicode, это не означает, что вам нужно изменить определение всех вариантов. На самом деле, для многих вариантов, таких как целые, это не имеет смысла. Для использования Unicode вам понадобитсяУникальные варианты. Они отличаются от обычных опций тем, что принимают<wstring
>вход и обрабатывают его с помощью широких потоков символов. Создать опцию Unicode-aware легко: просто используйте функцию<wvalue
>вместо обычной<value
>.
Когда парсер асции передает данные опции асции, или парсер Unicode передает данные опции Unicode, данные не изменяются вообще. Таким образом, опция ascii будет видеть строку в локальном 8-битном кодировании, а опция Unicode будет видеть любую строку, пропущенную в качестве входа Unicode.
Что происходит, когда данные Unicode передаются в асции, и наоборот? Библиотека автоматически выполняет преобразование из Unicode в локальное 8-битное кодирование. Например, если командная строка находится в ascii, но вы используете<wstring
>опции, то вход ascii будет преобразован в Unicode.
Чтобы выполнить преобразование, библиотека использует грань<codecvt<wchar_t,
char>
>от глобальной локации. Если вы хотите работать со строками, которые используют локальное 8-битное кодирование (в отличие от 7-битного подмножества), ваше приложение должно начинаться с:
locale::global(locale(""));
который настраивает грань конверсии в соответствии с выбранным пользователем местоположением.
Тем не менее, целесообразно проверить статус поддержки локализации C++ в вашей реализации. Быстрый тест включает три шага:
Перейдите в каталог «test» и создайте двоичный файл «test_convert».
Установите некоторое неасциальное место в окружающей среде. На Linux можно запускать, например:
<
$ export LC_CTYPE=ru_RU.KOI8-R
>
Запустите двоичную строку «test_convert» с любой неасциальной строкой в выбранной кодировке в качестве ее параметра. Если вы видите список кодовых точек Unicode, все в порядке. В противном случае локальная поддержка вашей системы может быть нарушена.
Обычно библиотека выкладывает исключение на неизвестные имена вариантов. Такое поведение можно изменить. Например, только некоторая часть вашего приложения используетProgram_options, и вы хотите передать непризнанные варианты другой части программы или даже другому приложению.
Чтобы разрешить незарегистрированные опции в командной строке, вам нужно использовать класс<basic_command_line_parser
>для разбора (не<parse_command_line
>) и вызвать метод<allow_unregistered
>этого класса:
parsed_options parsed =
command_line_parser(argc, argv).options(desc).allow_unregistered().run();
Для каждого токена, который выглядит как опция, но не имеет известного имени, в результат будет добавлен экземпляр<basic_option
>. Поля<string_key
>и<value
>экземпляра будут содержать результаты синтаксического разбора токена, поле<unregistered
>будет установлено на<true
>, а поле<original_tokens
>будет содержать токен, как он появился в командной строке.
Если вы хотите пройти непризнанные опции дальше, можно использовать функцию<collect_unrecognized
>. Функция будет собирать оригинальные токены для всех непризнанных значений и, необязательно, для всех найденных позиционных опций. Скажем, если ваш код обрабатывает несколько опций, но не обрабатывает позиционные опции вообще, вы можете использовать такую функцию:
vector<string> to_pass_further = collect_unrecognized(parsed.options, include_positional);