Boost Program Options: разбор командной строки в стиле ООП
Определения
Рассмотрим команду:
prog -ab -n 10 --style=awesome file1 file2
Здесь:
prog— имя программы;a,b,n,style— опции;style— длинная опция;-ab— сгруппированные опции;10иawesome— аргументы опций;file1иfile2— параметры.
Неявным значением аргумента будем называть значение, которое использует программа вместо аргумента опции, если этот аргумент не задан (то есть, указана опция без аргумента).
Значением аргумента по умолчанию будем называть значение, которое использует программа вместо аргумента опции, если опция не задана в командной строке.
Возможно, вы видели и другие варианты названий, неважно — я буду пользоваться этими.
Требования
Мы хотим от парсера командной строки поддержки следующего:
- коротких (
-f) и длинных (--input-file) опций; - опций с аргументами и без;
- опций с обязательными и необязательными аргументами;
- возможности задания значения аргумента по умолчанию и/или неявного значения аргумента;
- распознавания сгруппированных опций:
-abcдолжно быть эквивалентно-a -b -c; - взаимоисключающих опций;
- параметров;
- распознавания лексемы «
--», разделяющей опции и параметры; - возможности выполнить произвольную функцию, если задана некоторая опция;
- строгой типизации аргументов опций;
- автоматического формирования справки по опциям.
Также мы хотим, чтобы парсер уведомлял о
- неизвестной опции;
- отсутствии обязательного аргумента;
- задании нескольких взаимоисключающих опций;
- прочих ошибках.
Реализация
В качестве эксперимента возможности библиотеки, удовлетворяющие перечисленные требования, будут продемонстрированы на простой программе.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | #include <iostream> #include <vector> #include <string> #include <iterator> #include <boost/program_options.hpp> namespace po = boost::program_options; // чтобы было короче using namespace std; // Вспомогательная функция - выводит содержимое вектора строк. void print_vector(const vector<string> &v, const string &name) { cout << name << ": "; for (vector<string>::const_iterator i = v.begin(); i != v.end(); ++i) cout << *i << "/"; cout << "\n"; } // Функция уведомления, автоматически вызываемая при задании опции -v. // В аргументе функции передается аргумент опции. void value_notifier(int v) { cout << "Value notifier, argument is " << v << "\n"; } int main(int argc, char *argv[]) { // Группа опций под названием "Visible". Эти опции будем включать в справку. po::options_description desc_visible("Visible options"); // Добавляем опции в группу. Обратите внимание на удобный синтаксис. Функция add_options() // возвращает объект-функтор, создающий объекты опций. desc_visible.add_options() // Обычная опция без аргумента. Длинное и короткое имя указываются через запятую. ("help,h", "produce help message") // Опция с обязательным аргументом типа int. При указании опции в командной строке // вызывается функция value_notifier(), которой передается значение аргумента. ("value,v", po::value<int>()->notifier(value_notifier), "A value. Argument is mandatory.") // Опция-флаг. С ней автоматически связывается аргумент булевского типа, который // равен true, если опция указана в командной строке, и false в противном случае. ("aflag,a", po::bool_switch(), "Flag A") // Полностью эквивалентно предыдущей записи, но выражено более многословно. // zero_tokens() не дает задать аргумент опции, default_value() задает значение аргумента // по умолчанию (когда опция не задана), а implicit_value() задает неявное значение аргумента // (когда опция задана без аргумента). ("bflag,b", po::value<bool>()->zero_tokens()->default_value(false)->implicit_value(true), "Flag B") // Для закрепления: если опция указана, у нее может быть целый аргумент. Если аргумент // опции не указан, считается, что он равен 10. Вызов implicit_value() делает указание // аргумента необязательным. ("implicit,i", po::value<int>()->implicit_value(10), "Implicit value option") // Задание опции, аргументом которой является список. Такое нечасто встретишь. // Функция multitoken() позволяет комбинировать аргументы несколько раз заданной опции. // zero_tokens() позволяет задать список нулевой длины. Тип аргумента vector<string> позволяет // включать в значение аргумента несколько следующих за именем опции лексем, не // являющихся именем опции. // Опция может быть задана так: // 1. --list // пустой список // 2. --list a b c // список содержит a, b, c // 3. --list a -i 10 --list b c // список содержит a, b, c ("list", po::value< vector<string> >()->multitoken()->zero_tokens(), "list of some values"); // Группа опций, которую мы не будем показывать пользователю. Зачем эта группа, описано ниже. po::options_description desc_hidden("Hidden options"); desc_hidden.add_options() // Опция с аргументом-списком. Обратите внимание на задание значения по умолчанию. Если // для типа значения аргумента не определен оператор << для ostream, должно быть явно задано // текстовое представление указываемого значения (в данном случае - пустого вектора). ("pos2", po::value< vector<string> >()->default_value(vector<string>(), ""), "First two positional arguments") // Такая же опция. ("posrem", po::value< vector<string> >()->default_value(vector<string>(), ""), "Remaining positional arguments"); // Группа опций, объединяющая две ранее определенные группы. po::options_description desc; desc.add(desc_visible).add(desc_hidden); // Описание параметров ("позиционных опций"). Теперь внимание. В значение аргумента опции // pos2 попадают первые два параметра, остальные параметры попадают в значение аргумента // опции posrem (на это указывает -1). po::positional_options_description p; p.add("pos2", 2).add("posrem", -1); // Здесь будет сохранен результат разбора. po::variables_map vm; try { // Предписываем использовать desc как множество распознаваемых опций и p как // множество "псевдоопций", предназначенных для "усыновления" параметров. po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm); // Вызвать функции уведомления (value_notifier() в данном случае). po::notify(vm); } // Обработаем исключения, генерируемые при некорректном синтаксисе. catch (const po::invalid_command_line_syntax &inv_syntax) { switch (inv_syntax.kind()) { case po::invalid_syntax::missing_parameter: cout << "Missing argument for option '" << inv_syntax.tokens() << "'.\n"; break; default: cout << "Syntax error, kind " << int(inv_syntax.kind()) << "\n"; break; }; return 1; } // Обработаем исключение, генерируемое при обнаружении неизвестной опции. Есть еще // несколько типов исключений, но в подавляющем большинстве случаев нужны только эти. catch (const po::unknown_option &unkn_opt) { cout << "Unknown option '" << unkn_opt.get_option_name() << "'\n"; return 1; } // Задана опция help - выводим справку по опциям группы "Visible". Справка по опциям pos2 и // posrem не выводится (зачем пользователю знать, как мы обрабатываем параметры?). if (vm.count("help")) { cout << desc_visible << "\n"; return 1; } // Примеры типизации значения аргумента. if (vm["aflag"].as<bool>() == true) cout << "Flag A is set\n"; if (vm["bflag"].as<bool>() == true) cout << "Flag B is set\n"; // Типизация векторов. if (vm.count("list")) { print_vector(vm["list"].as< vector<string> >(), "List"); } // Здесь не надо проверять vm.count, как у list, потому что мы задали значения по умолчанию. print_vector(vm["pos2"].as< vector<string> >(), "pos2"); print_vector(vm["posrem"].as< vector<string> >(), "posrem"); return 0; } |
При сборке нужно подключить библиотеку libboost_program_options.
Чего мне не хватает в Program Options:
- возможности задавать аргумент-список, перечисляя его члены через запятую (как в команде mount);
- встоенной поддержки взаимоисключающих аргументов (сейчас — только вручную);
- более широких возможностей по форматированию автоматически формируемой справки.
Конечно, в одном посте невозможно описать все возможности библиотеки — их гораздо больше, чем я описал. Но, во-первых, приведенный мной код покрывает 95% всех случаев, и, во-вторых, всегда можно обратиться к документации.
Также обязан заметить, что я представлял в качестве читателя человека, для которого синтаксис C++ не является сюрпризом.


