Argtable это открытая ANSI C библиотека, которая может парсить командную строку и её опции. Она упрощает обработку командной строки путем предоставления API в декларативном стиле, где Вы можете указать синтаксис команд, какой захотите. Библиотека argtable автоматически генерирует полноценную логику обработки ошибок и текстовых описаний синтаксиса команд, которые важны, но весьма утомительны для реализации надежного CLI-интерфейса программы (Command Line Interface).
Ниже показан список причин, по которым Вы можете добавить argtable в свой тулбокс C/C++:
• Знакомый GNU-стиль синтаксиса реализуемой командной строки: использование стандартного и кроссплатформенного стиля выражений в командах. • Декларативное API: устраняется сложная логика парсинга. Программисту нужно всего лишь указать что надо делать программе для обработки команд, а не как это надо делать. • Встроенная обработка ошибок: генерируется полная логика обработки ошибок. • Встроенные сообщения помощи (help): автоматически генерируются описания синтаксиса командной строки. • Библиотека написана на чистом ANSI C: упрощается её интеграция с другими языками. • Удобочитаемый исходный код, снабженный подробными комментариями, со 100% покрытием тестами. • Весь код находится в одном файле: отсутствуют запутанные скрипты сборки. Просто киньте один исходник в свои проекты. • Самодостаточность: отсутствуют внешние зависимости. • Кроссплатформенность: доступна на большинстве систем семейства UNIX, на Windows, и для встраиваемых систем. • Лицензия BSD: можно использовать библиотеку для любых целей, включая коммерческие программы.
Если Вы хотите начать использовать argtable3, то начните с официального руководства [1]. Если предпочитаете научиться на примерах, то можете просмотреть список программ в репозитории [2]. И если Вы найдете любую проблему документации или кода, то можете опубликовать соответствующий пост (issue) на страничке Github проекта [2].
Примечание: сайт [1] описывает последнюю версию argtable (v3), которая эволюционировала из библиотеки argtable v2, созданной Stewart Heitmann. Библиотека argtable3 не совместима с этой библиотекой. Таким образом, если Вы предпочитаете использовать argtable2 API, то посетите её сайт [3] и скачайте оттуда исходный код, доступный на странице проекта Sourceforge.net.
[Введение в argtable3]
Argtable3 использует NetBSD getopt для выполнения актуального парсинга в соответствии с принципами утилит команд POSIX [4], которым следует большинство программ UNIX и некоторые программы Windows. Argtable3 поддерживает как короткий вариант опций (такие как -abc и -o myfile), так и длинные опции (такие как –-scalar=7 и –-verbose), а также и аргументы без тегов (такие как < file> [< file>]). Не поддерживается non-POSIX синтаксис команд, такой как стиль опций /X /Y /Z многих программ Windows.
Быстрый старт. Argtable3 состоит из единственного файла на языке ANSI-C. Все что нужно сделать - просто добавить модуль argtable3.c в свой проект, и подключить заголовок argtable3.h в исходном коде.
Например, создание CLI-программы может выглядеть так:
$> util.exe --help
Usage: util.exe [-v] [--help] [--version] [--level=< n>] [-o myfile] < file> [< file>]...
Это демонстрация парсинга командной строки с помощью argtable3.
--help отобразит этот текст и завершит программу
--version отобразит версию и завершит программу
--level=< n> передача в программу значения
-v, --verbose включение подробного вывода
-o myfile выходной файл
< file> входные файлы
Вы можете реализовать такой командный интерфейс программы вот в таком несложном коде:
#include "argtable3.h"
/* Глобальные структуры arg_xxx */
struct arg_lit *verb, *help, *version;
struct arg_int *level;
struct arg_file *o, *file;
struct arg_end *end;
intmain(int argc, char*argv[])
{
/* Глобальные структуры arg_xxx, инициализируемые внутри argtable */void*argtable[] = {
help = arg_litn(NULL, "help", 0, 1, "отобразит этот текст и завершит программу"),
version = arg_litn(NULL, "version", 0, 1, "отобразит версию и завершит программу"),
level = arg_intn(NULL, "level", "< n>", 0, 1, "передача в программу значения"),
verb = arg_litn("v", "verbose", 0, 1, "включение подробного вывода"),
o = arg_filen("o", NULL, "myfile", 0, 1, "выходной файл"),
file = arg_filen(NULL, NULL, "< file>", 1, 100, "входные файлы"),
end = arg_end(20),
};
int exitcode =0;
char progname[] ="util.exe";
int nerrors;
nerrors = arg_parse(argc,argv,argtable);
/* Специальный случай: '--help' имеет приоритет над сообщениями об ошибках */if (help->count >0)
{
printf("Usage: %s", progname);
arg_print_syntax(stdout, argtable, "\n");
printf("Это демонстрация парсинга командной строки с помощью argtable3.\n\n");
arg_print_glossary(stdout, argtable, " %-25s %s\n");
exitcode =0;
goto exit;
}
/* Если парсер вернет любые ошибки, то они будут отображены,
и программа завершится */if (nerrors >0)
{
/* Отображение подробной информации об ошибке, содержащейся
в структуре arg_end.*/
arg_print_errors(stdout, end, progname);
printf("Попробуйте '%s --help' для дополнительной информации.\n", progname);
exitcode =1;
goto exit;
}
exit:/* Освобождение памяти для всех не null элементов в argtable[] */
arg_freetable(argtable, sizeof(argtable) /sizeof(argtable[0]));
return exitcode;
}
Чтобы выполнить сборку программы в среде Microsoft Visual C++, Вы можете открыть окно приглашения командной строки Visual Studio Developer Command Prompt, и выполнить следующую команду:
C:\> cl.exe util.c argtable3.c
Чтобы собрать программу с помощью GCC, MinGW или Cygwin, откройте окно шелла и введите следующую команду:
$ gcc util.c argtable3.c
Если сборка прошла успешно, и Вы запустите util.exe --help, то увидите сообщение подсказки по командам. Это значит, что Вы научились интегрировать argtable3 в свою программу. В следующих секциях будет описано, как использовать каждый тип опций, как генерировать сообщения help, и как обрабатывать ошибки.
[Как работает argtable3]
Библиотека предоставляет набор структур arg_xxx, по одной для каждого типа аргумента (литерал, целое число int, число double, строка, имя файла и т. д.), который она поддерживает. Каждая такая структура может поддерживать несколько вхождений аргумента в командой строке. Кроме того, для каждой опции может существовать альтернативное представление: короткая опция с одним дефисом (-c) или длинная опция (--scalar), и эти формы опций можно использовать взаимозаменяемо. Фактически каждая опция может даже принимать несколько альтернативных коротких или длинных вариантов, или то, или другое. Опции также можно определить без тега (< file>) в случаях, когда они идентифицируются по позиции в командной строке (опции с тегами -, -- могут находиться в любом месте командной строки).
Чтобы определить опции командной строки, Вы должны создать структуру arg_xxx для каждого необходимого типа аргумента, и составить из этих структур массив, который разработчики назвали таблицей аргументов (argument table). Порядок следования структур в таблице аргументов определяет порядок, в каком они ожидаются в командной строке, хотя порядок парсинга на самом деле важен только для опций без тегов (т. е. без - или --). Сама таблица аргументов это просто массив указателей на void, и по соглашению каждая структура имеет в своем составе первый элемент в известном формате структуры arg_hdr, что позволяет библиотеке идентифицировать структуры каждого типа.
Для примера предположим, что существует структура arg_int, используемая для опций командной строки, принимающих целочисленный аргумент в виде -–scalar=7.
struct arg_int
{
struct arg_hdr hdr;
int count;
int*ival;
};
Первый член структуры hdr содержит приватные данные, которые используются функциями библиотеки argtable3. В них содержатся такие вещи, как строка тега аргумента, и т. д. К этим данным открыт прямой доступ, но это редко используется. Поле ival указывает на массив целых чисел int, извлеченный из командной строки, и count дает количество значений в этом массиве. Хранилище для массива ival выделяется при создании arg_int. Это должно быть сделано функцией конструктора arg_int:
struct arg_int *arg_intn (constchar* shortopts,
constchar* longopts,
constchar*datatype,
int mincount,
int maxcount,
constchar*glossary);
Функции конструктора для всех типов аргумента работают одинаково: они выделяют блок памяти, который содержит структуру arg_xxx с её заголовком, за которым идет хранилище для локальных данных этой структуры. Нашем примере содержимое хранилища это массив ival. По этой причине Вы никогда не должны вручную инициировать структуру arg_xxx. Всегда используйте функции конструктора, предоставленные для выделения структур и их освобождения, когда работа с ними завершена.
Продолжим описание нашего примера arg_int. Следующий кусок кода конструирует опцию для целочисленного типа в форме --scalar=< n>, которая должна появляется в командной строке от 3 до 5 раз включительно.
После выполнения этого кода s будет указывать на блок памяти, содержащий структуру arg_int, за которой идет массив ival из 5 элементов.
[]
Как показано на диаграмме выше, структура заголовка s->hdr хранит в себе, вместе с другими вещами, ссылки на строковые параметры функции конструктора. Переменная s->count инициализируется в 0, поскольку она показывает количество доступных аргументов, сохраненных массив s->ival после парсинга командной строки. Это показывает реальное количество данных в массиве s->ival.
В примере выше мы опустили определение короткой опции путем передачи NULL в функцию конструктора. Вместо этого можно передать короткий вариант опции, например "k":
s = arg_intn("k", "scalar", "< n>", 3, 5, "значение foo");
В результате получится такая же структура, однако теперь опция может приниматься как длинном (-–scalar=< n>), так и сокращенном варианте (-k< n>), и оба этих варианта эквивалентны. Действительно, мы можем пойти еще дальше, и определить несколько альтернатив для коротких и длинных вариантов опций. Короткие опции формируются строкой одиночных символов, в то время как длинные опции представляются как строка, где варианты отделены друг от друга запятой. Пример:
s = arg_intn("kKx", "scalar,foo", "< n>", 3, 5, "значение foo");
Этот пример будет принимать любые из следующих альтернативных форм командной строки: -k< n> -K< n> -x< n> --scalar=< n> --foo=< n>.
Кроме arg_int, интересны другие структуры arg_xxx:
// Для опций, которые не передают значения:
struct arg_lit
{
struct arg_hdr hdr;
int count;
};
// Для передачи значений с плавающей запятой:
struct arg_dbl
{
struct arg_hdr hdr;
int count;
double*dval;
};
// Для передачи строк:
struct arg_str
{
struct arg_hdr hdr;
int count;
constchar**sval;
};
// Для передачи регулярных выражений:
struct arg_rex
{
struct arg_hdr hdr;
int count;
constchar**sval;
};
void*argtable[] = {a, b, c, scal, verb, o, file, end};
Опции -a, -b, -c и -v|--verbose не принимают значений, для них используется структура arg_lit. Мы указали mincount 0 и maxcount 1, потому что эти отдельные опции появляются в командной строке только один раз, или не появляются вообще.
Опция --scalar=< n> принимает аргумент int, поэтому для неё используется структура arg_int. Она также может появляться в командной строке один раз, или отсутствовать, поэтому mincount 0 и maxcount 1.
Опции -o myfile и < file> обе относятся к именам файлов, поэтому для них используется структура arg_file. Обратите внимание, что это опция без дефисов (untagged option) и у неё нет строк ни для короткой, ни для длинной формы.
Структура arg_end имеет специальное назначение, она не представляет опцию командной строки. Она помечает конец массива argtable, но кроме этого также хранит в себе парсер ошибок, которые встретились при обработке аргументов командной строки. Целочисленный параметр, передаваемый в конструктор arg_end, задает максимальное сохраняемое количество ошибок (в нашем случае 20), и любые ошибки сверх этого количества будут отбрасываться с сообщением "too many errors".
Скоро мы увидим, как использовать arg_end в сообщениях об ошибках, но сначала мы должны убедиться, что все элементы таблицы были успешно выделены их функциями конструктора. Если выделения не произошло, то тогда в массиве argtable будут находиться записи NULL, которые укажут на наличие проблемы. Мы можем использовать функцию arg_nullcheck, чтобы проверить argtable на отсутствие записей NULL. Эта функция вернет ненулевое значение, если в таблице команд до её завершения, обозначенного маркером структуры arg_end, обнаружатся любые элементы, равные NULL.
if (arg_nullcheck(argtable) !=0)
printf("error: недостаточно памяти\n");
Если все прошло хорошо, то мы можем теперь инициализировать любые значения по умолчанию для наших опциональных аргументов. Мы можем просто присвоить желаемые значения напрямую в структуры arg_xxx, и будем знать, что argtable перезапишет их, если обработает соответствующие значения опций в командной строке. Здесь для примера мы установим значения 3 и - аргументов repeat и outfile соответственно.
repeat->ival[0] =3;
outfile->filename[0] ="-";
Argtable3 не требует, чтобы мы инициализировали любые значения по умолчанию, это просто более удобный вариант, чтобы наша программа изначально имела рабочие умолчания до обработки командной строки вместо того, чтобы присвоить недостающие значения после. Однако Вы можете выбрать и такой вариант.
[Парсинг командной строки]
Теперь наша таблица аргументов заполнена, и её можно использовать для парсинга аргументов командной строки. Мы используем для этого функцию arg_parse, она возвратит количество ошибок парсинга, если они имели место.
nerrors = arg_parse(argc, argv, argtable);
Если ошибок не было (при nerrors == 0), то мы успешно обработали строку команды, и можем перейти к выполнению основной задачи обработки, используя значения, найденные в наших структурах arg_xxx.
if (nerrors ==0)
{
int i;
printf("-a = %d\n", a->count);
printf("-b = %d\n", b->count);
printf("-c = %d\n", c->count);
printf("--verbose = %d\n", verb->count);
if (scal->count >0)
printf("--scalar=%d\n", scal->ival[0]);
if (o->count >0)
printf("-o %s\n", o->filename[0]);
for (i =0; i < file->count; i++)
printf("file[%d]=%s\n", i, file->filename[i]);
};
[Обработка ошибок]
Если функция arg_parse сообщила об ошибках (вернула ненулевое значение), то нам нужно отобразить причины ошибок, потому что сама функция arg_parse этого не делает. Как упоминалось раньше, arg_parse сохраняет ошибки, если они встретились в командной строке, в структуре arg_end таблицы аргументов. Нам не нужно знать подробности структуры arg_end, просто надо вызвать функцию arg_print_errors, чтобы вывести эти ошибки в том порядке, в котором они произошли.
В функцию мы передаем указатель на структуру arg_end таблицы параметров, а также имя программы, которое предшествует каждому сообщению об ошибке. Имя программы может быть NULL, если его выводить не нужно.
If (nerrors >0)
arg_print_errors(stdout, end, "myprog");
Этот пример показывает результаты запуска нашего примера с некорректными опциями командной строки:
Причина, по которой функция не печатает ошибки, состоит в том, что программа может вызвать её несколько раз, чтобы обработать командную строку по разным таблицам аргументов, без преждевременного отображения сообщений об ошибках. Таким образом, мы можем определить несколько таблиц аргументов для тех программ, у которых существуют взаимоисключающие наборы опций командной строки, чтобы программа пробовала применить эти таблицы к командной строке по очереди, пока не найдет подходящую, с которой не было ошибок. Если все таблицы аргументов не удовлетворяют командной строке, то мы можем напечатать ошибки по каждой из них, или можно напечатать форму ошибок для самой подходящей таблицы. В любом случае мы управляем, какие сообщения будут отображаться.
[Отображение синтаксиса опции]
Если Вы хотите, чтобы программа отображала встроенный help, то можете использовать функцию arg_print_syntax. Она показывает полный синтаксис по командной строке, сгенерированный из таблицы аргументов. Существует 2 формы этой функции:
Последняя функция, с буквой v в конце, отобразит более подробную форму помощи. Обе функции отобразят подсказку по всей таблице аргументов. Аргумент syntax предназначен для удобного добавления символов новой строки или любых других строк, завершающих вывод подсказки. В подробной варианте вывода помощи (arg_print_syntaxv) каждый элемент таблицы аргументов будет отображен в своей короткой и длинной форме, которые будут разделены символом |, за которыми идет строка типа данных аргумента. Например:
Эта опция будет показана в подробной форме как [-k|-K|-x|--scalar|--foo=< n>]. В то же время стандартная форма выведет сокращенный вариант опции для каждого элемента таблицы, такой как [-k < n>]. Стандартная форма также соединит все короткие варианты опции в одну строку, следуя стандарту GNU-стиля (например: -a -b -c будут отображены как -abc). Таблица аргументов из нашего предыдущего примера в стандартной форме будет отображена так:
Обратите внимание, что опциональные (не обязательные) элементы командной строки автоматически обрамляются квадратными скобками, в то время как обязательные угловыми. Кроме того, те аргументы, которые могут появляться несколько раз, отображаются один раз для каждого экземпляра, как в " []". Это происходит максимум до 3 экземпляров, после чего повторения заменяются многоточием, как в "[]...".
Функции arg_print_syntax безопасно игнорируют NULL-строки коротких и длинных опций, в то время как строка NULL для datatype автоматически заменяется типом данных по умолчанию для такой структуры arg_xxx. Тип по умолчанию datatype может быть подавлен использованием пустой строки datatype вместо NULL.
[Отображение глоссария опций]
Отдельные элементы в таблице аргументов могут быть отображены в виде словарика с помощью функции arg_print_glossary. Будет выведен полный синтаксис по каждому элементу таблицы аргументов, за котором будет идти описание опции (строка glossary). Это строка, которая находится в последнем параметре вызова функций конструктора arg_xxx. Элементы таблицы, у которых строки glossary установлены в NULL, не будут отображены.
Строка format, переданная в функцию arg_print_glossary, фактически строка в стиле формата printf. В ней должно находиться ровно 2 параметра формата %s, первый из них используется для управления форматом printf строки синтаксиса опции, и второй для строки аргумента glossary. Типичная строка format будет " %-25s %s\n". Строка format позволяет тонко управлять отображением, однако требует детализации, поскольку любые неожиданные параметры могут привести к непредсказуемым результатам. Ниже показан результат вызова arg_print_glossary для нашего примера таблицы аргументов:
-a опция -a
-b опция -b
-c опция -c
--scalar=< n> значение foo
-v, --verbose подробный вывод сообщений
-o myfile выходной файл
< file> входные файлы
Иногда вы захотите добавить дополнительные строки текста в глоссарий или даже поместить свой собственный текст в синтаксическую строку, созданную arg_print_syntax. При желании в строки таблицы аргументов можно добавлять символы новой строки, но вскоре она становится уродливой. Лучший способ - добавить arg_rem структуры в таблицу аргументов. Они являются фиктивными записями таблицы аргументов в том смысле, что они не изменяют синтаксический анализ аргументов, но их строки datatype и glossary появляются в выходных данных, генерируемых функциями arg_print_syntax и arg_print_glossary. Название arg_rem предназначено для "пометок" и вдохновлено инструкцией REM, используемой в языке BASIC.
[Очистка]
При завершении программы нам понадобится освободить память, выделенную под каждую из структур arg_xxx. Это можно было бы сделать, освобождая каждую структуру по отдельности, но функция arg_freetable позволяет реализовать очистку более удобно.
Она просматривает таблицу аргументов, и вызывает free для каждого элемента. Второй параметр sizeof(argtable)/sizeof(argtable[0]) просто представляет количество элементов а нашем массиве argtable. По завершению этой функции все элементы массива argtable будут установлены в NULL.
ANSI C не позволяет функциям конструктора arg_xxx размещаться в глобальном пространстве имен. Поэтому если Вы захотите сделать свои структуры arg_xxx глобальными, то должны инициализировать их в другом месте. Здесь показан программный трюк для использования глобальных структур arg_xxx, все еще объявляя argtable статически.
#include < argtable3.h>
/* Глобальные структуры arg_xxx */
struct arg_lit *a, *b, *c, *verb;
struct arg_int *scal;
struct arg_file *o, *file;
struct arg_end *end;
intmain(int argc, char**argv)
{
/* Глобальные структуры arg_xxx, инициализируемые внутри argtable */void*argtable[] = {
a = arg_lit0("a", NULL, "опция -a"),
b = arg_lit0("b", NULL, "опция -b"),
c = arg_lit0("c", NULL, "опция -c"),
scal = arg_int0(NULL, "scalar","< n>", "значение foo"),
verb = arg_lit0("v", "verbose", "подробный вывод сообщений"),
o = arg_file0("o", NULL,"myfile", "выходной файл"),
file = arg_filen(NULL,NULL,"< file>",1,2, "входные файлы"),
end = arg_end(20),
};
...
return0;
};
См. программу ls.c, включенную в дистрибутив argtable3 для получения примера использования такого стиля декларации.
[Примеры программ]
Дистрибутив argtable3 поставляется с примерами программ, которые реализуют полные опции в стандарте POSIX для нескольких команд UNIX. См. директорию argtable-3.x/example/, где находится исходный код следующих программ: