13 KiB
Нестандартные случаи использования библиотеки
Нестандартный синтаксис
Иногда требуется поддержка нестандартного синтаксиса командных строк. Например, как в компиляторе 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);
Файлы ответов (Response Files)
Для обхода ограничений длины командной строки используются файлы ответов.
Шаги реализации:
- Определение опции для файла ответов:
("response-file", value<string>(), "can be specified with '@name', too")
- Дополнительный парсер для синтаксиса
@file:
pair<string, string> at_option_parser(string const&s) {
if ('@' == s[0])
return make_pair(string("response-file"), s.substr(1));
else
return make_pair(string(), string());
}
- Обработка найденного файла ответов:
if (vm.count("response-file")) {
ifstream ifs(vm["response-file"].as<string>().c_str());
if (!ifs) {
cout << "Could not open the response file\n";
return 1;
}
stringstream ss;
ss << ifs.rdbuf();
char_separator<char> sep(" \n\r");
string ResponsefileContents(ss.str());
tokenizer<char_separator<char>> tok(ResponsefileContents, sep);
vector<string> args;
copy(tok.begin(), tok.end(), back_inserter(args));
store(command_line_parser(args).options(desc).run(), vm);
}
Другие возможности
- Winmain Command Line — поддержка специфичного синтаксиса Windows
- Группы опций и скрытые опции — организация опций в логические группы
- Пользовательские валидаторы — создание собственных правил проверки значений
- Поддержка Unicode — работа с Unicode-строками
- Неизвестные опции — возможность разрешать неизвестные опции
- Проверка наличия опций — механизмы проверки присутствия опций
Эти возможности позволяют гибко настраивать поведение библиотеки под конкретные требования проекта.
Работа с командной строкой в Windows
Особенности Winmain
В Windows GUI-приложения получают командную строку как единую строку, а не как массив элементов. Для решения этой проблемы библиотека предоставляет функцию split_winmain.
Пример использования:
vector<string> args = split_winmain(lpCmdLine);
store(command_line_parser(args).options(desc).run(), vm);
Функция split_winmain имеет перегрузку для строк wchar_t, что позволяет использовать её в Unicode-приложениях.
Группы опций и скрытые опции
Проблемы единого описания опций
При использовании единого экземпляра options_description возникают сложности:
- Некоторые опции имеют смысл только для определённых источников
- Требуется структурированная справка
- Некоторые опции не должны отображаться в справке
Решение через группы опций
Библиотека позволяет создавать несколько экземпляров options_description и объединять их по необходимости.
Пример группировки:
// Общая группа опций
options_description general("Общие опции");
general.add_options()
("help", "показать справку")
("help-module", value<string>(), "показать справку по модулю")
("version", "показать версию");
// Группа опций GUI
options_description gui("Опции GUI");
gui.add_options()
("display", value<string>(), "используемый дисплей");
// Группа опций бэкенда
options_description backend("Опции бэкенда");
backend.add_options()
("num-threads", value<int>(), "начальное число потоков");
Объединение групп
Создаём два объединения:
- Полное для парсинга
- Видимое для справки
// Все опции для парсинга
options_description all("Разрешённые опции");
all.add(general).add(gui).add(backend);
// Видимые опции для справки
options_description visible("Разрешённые опции");
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 << "Неизвестный модуль '" << s << "'\n";
return 1;
}
return 0;
}
Такой подход позволяет:
- Структурировать опции по группам
- Контролировать видимость опций в справке
- Гибко настраивать поведение программы
Пользовательские валидаторы
Настройка конвертации значений
По умолчанию конвертация значений опций выполняется через iostreams. Библиотека позволяет настроить собственную конвертацию для конкретных классов.
Пример создания пользовательского валидатора:
struct magic_number {
public:
magic_number(int n) : n(n) {}
int n;
};
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;
// Проверка на множественные присваивания
validators::check_first_occurrence(v);
// Получение единственной строки
const string& s = validators::get_single_string(values);
// Проверка через регулярное выражение
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);
}
}
Параметры функции валидации:
- v — хранилище для значения
- values — список строк
- target_type — указатель на целевой тип
- Дополнительный параметр для совместимости с компиляторами
Поддержка Unicode
Для работы с Unicode необходимо:
- Использовать Unicode-совместимые парсеры
- Указывать поддержку Unicode для нужных опций
Особенности работы:
- Большинство парсеров имеют Unicode-версии
- Для создания Unicode-опций используется wvalue вместо value
- Автоматическое преобразование кодировок между Unicode и локальными
Настройка локализаций
Для корректной работы с Unicode:
locale::global(locale("")); // Настройка конвертации согласно локали пользователя
Тестирование поддержки локализаций
Проверка включает:
- Сборка теста test_convert
- Установка не-ASCII локали:
export LC_CTYPE=ru_RU.KOI8-R - Запуск теста с не-ASCII строкой
При успешной работе вы увидите список Unicode-кодов, что подтверждает корректную поддержку локализаций.
Такой подход позволяет:
- Настраивать собственную логику валидации
- Работать с Unicode-данными
- Обеспечивать корректную конвертацию кодировок
Разрешение неизвестных опций
Стандартное поведение
По умолчанию библиотека генерирует исключение при обнаружении неизвестных опций. Однако это поведение можно изменить.
Разрешение неизвестных опций
Для разрешения неизвестных опций:
- Используйте 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 — флаг неизвестной опции
- original_tokens — исходные токены
Передача неизвестных опций
Для сбора неизвестных опций используйте функцию collect_unrecognized:
vector<string> to_pass_further = collect_unrecognized(
parsed.options,
include_positional
);
Проверка наличия опций
Традиционный подход
Ранее проверка осуществлялась через метод count:
if (vm.count("compression")) {
cout << "Уровень сжатия установлен: " << vm["compression"].as<int>() << endl;
}
Улучшенный подход с boost::optional
Использование boost::optional позволяет:
- Автоматически инициализировать переменную при наличии опции
- Упростить проверку наличия опции
Пример:
boost::optional<int> compression;
desc.add_options()
("compression", po::value(&compression), "уровень сжатия");
// После парсинга и уведомления
if (compression) {
cout << "Уровень сжатия установлен: " << *compression << endl;
} else {
cout << "Уровень сжатия не установлен" << endl;
}
Преимущества подхода с boost::optional:
- Меньше вероятность ошибок при переименовании опций
- Более чистый код
- Автоматическая обработка наличия значения
Такой подход рекомендуется использовать для проверки наличия опций в программе.