Files
boost_program_options_docs_ru/boost/1.89.0/how_to.md

13 KiB
Raw Blame History

Нестандартные случаи использования библиотеки

Нестандартный синтаксис

Иногда требуется поддержка нестандартного синтаксиса командных строк. Например, как в компиляторе 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)

Для обхода ограничений длины командной строки используются файлы ответов.

Шаги реализации:

  1. Определение опции для файла ответов:
("response-file", value<string>(), "can be specified with '@name', too")
  1. Дополнительный парсер для синтаксиса @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());
}
  1. Обработка найденного файла ответов:
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("")); // Настройка конвертации согласно локали пользователя

Тестирование поддержки локализаций

Проверка включает:

  1. Сборка теста test_convert
  2. Установка не-ASCII локали:
    export LC_CTYPE=ru_RU.KOI8-R
    
  3. Запуск теста с не-ASCII строкой

При успешной работе вы увидите список Unicode-кодов, что подтверждает корректную поддержку локализаций.

Такой подход позволяет:

  • Настраивать собственную логику валидации
  • Работать с Unicode-данными
  • Обеспечивать корректную конвертацию кодировок

Разрешение неизвестных опций

Стандартное поведение

По умолчанию библиотека генерирует исключение при обнаружении неизвестных опций. Однако это поведение можно изменить.

Разрешение неизвестных опций

Для разрешения неизвестных опций:

  1. Используйте basic_command_line_parser вместо parse_command_line
  2. Вызовите метод 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:

  • Меньше вероятность ошибок при переименовании опций
  • Более чистый код
  • Автоматическая обработка наличия значения

Такой подход рекомендуется использовать для проверки наличия опций в программе.