среда, 24 сентября 2008 г.

Разбор командной строки в boost

Долго ломал голову как сделать обработку командной строки в boost по хитрому. А по хитрому - это так: сперва указываются глобальные опции, потом некоторая команда, потом локальные опции, свойственные конкретной команде. Но program_option из boost, упрямо засасывает все опции сразу, и чтобы придумать способ - пришлось покопаться в исходниках.

И как всегда, после того как разобрался, решение оказывается простым и понятным, но где оно было раньше - не понятно. Всмысле это могло бы быть написано в докумментации.

Начнем с устройства program_options::command_line_parser. Устроен он следующим образом:

Имеется некоторый набор парсеров стиля (style_parser), которые по некоторым критериям выбирают из строки опции. набор стайл парсеров формируется на основе стиля командной строки, который может варьироваться в некоторых пределах - поддержка длинных имен, значения у опций, опции формата DOS и тд.

Каждый парсер возвращает список опций (vector<program_options::option>), обработанных им на данном этапе. Это может быть пустой список, одна или много опций, хотя парсить много опций за раз нет необходимости, там цикл и остатки командной строки обязательно снова пройдут через каждый парсер. Обработка заканчивается после того, как командная строка опустевает.

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

Это было введение. :)

Теперь предположим, что командная строка предположительно будет разбираться за два прохода. Первый проход выделяет глобальные опции (идущие перед командой) и команду соответственно. А второй проход получит список аргументов, объединяющий в себе локальные опции и все остальное.

Но как и в любом деле здесь главное вовремя остановиться, то есть хотелось бы чтобы boost не анализировал строку до конца в поисках опций. И тут нам на помощь приходит extra_style_parser. Дополниельный парсер стиля - это отдельная функция. Её задачей будет при обнаружении первого неопционального слова, предположительно команды, оставшуюся часть строки без разбора загнать в результат. Cлайды:

vector<program_options::option> stop_on_command (vector<string> &args)
{
vector<program_options::option> result;
const string &tok = args[0];
if (tok[0] != '-') {
BOOST_FOREACH (string &arg, args) {
program_options::option opt;
opt.value.push_back(arg);
result.push_back(opt);
}
args.clear();
}
return result;
}

Используем это при разборе строки:

program_options::command_line_parser parser (argc, argv);
parser.extra_style_parser (stop_on_command);

...

program_options::store(parser.run(), vm);

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

Вот собственно и все. Может пригодиться кому.

7 коммент.:

Анонимный комментирует...

Можно потребовать чтобы "команда" обязательно стояла 1-ым аргументом. Тогда, проанализировав первый аргумент командной строки, составить список опций (глобальные + локальные) и начать разбор со второго аргумента.

Андрей Валяев комментирует...

Не, это не наш путь... :)

Это называется будем делать так, как диктует бибиотека. Всмысле я хочу сказать, что не никогда не нужно делать так, как получается. Надо всегда делать так, как хочется. :)

И при генерации каких-то идей ни в коем случае не надо думать о том, как это может быть реализовано. :) Это сразу все портит.

Я предпочитаю обдумывать идеи без привязки к реализации, и потом делать как хочу, а не как получается. :) Только так я могу написать то, что мне действительно будет нравится. А не оправдываться потом, что дескать я не мог сделать лучше, потому что буст, сцуко, не дает. :)

Где-то так я мыслю.

Анонимный комментирует...

Мне понятна Ваша точка зрения. И я ее даже разделяю :). Непонятно мне другое, чем хуже та идея что я высказал выше? И не применительно к boost'у, а вообще, так сказать абстрактно, в отрыве от конкретных интсрументальных средств.

Андрей Валяев комментирует...

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

Кроме того среди глобальных и локальных опций могут попасться одинаковые. И тогда эта версия не сработает так как надо.

например
./prog -h
может выдавать общий хелп по командам, а
./prog command -h может выдавать хелп по конкретной команде.

есть разница всетаки.

Андрей Валяев комментирует...

Конечно можно интерпретировать (положение опции предположительно не играет роли)
./prog -h
для отображения глобального хелпа, а
./prog -h command
для локального, но это опять получится допущение. А если опции несут разную функцию (делать так кончено не следует, но вдруг захочется?)

И кроме того в основном разборе придется плодить условия типа
if (command == "cmd1") {
help1;
} else if (command == "cmd2")

и так далее
Хотя по правильному этим должны заниматься подобъекты.

Мы конечно можем сперва сконструировать объект команды а протом сказать
command->help().

пожалуй это будет лучше, но никак не решит проблему разного смысла одноименных опций.

Анонимный комментирует...

If you want to have something done, do it yourself (c) "Fifth Element"

Не быстрее самому написать свой класс/набор классов? А то обсуждать проблемы boost дольше :-) Я, конечно, понимаю, что я похож на того, который на форуме осеписателей говорит, а зачем нужна ось, уже написано много... но все же :-)

Андрей Валяев комментирует...

Нет, этот блог не посвящен бусту...

А что касается трудностей, без буста трудностей будет не меньше.

Буст экономит код, просто в данном случае небольшой изврат с командами, поэтому сложности. Но это не повод отказываться от boost. :)