![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Вернулся я вчера к своей модели "кривого зеркала" и начал добавлять туда анализатор аргументов командной строки.
Вспомнил, что это дело у меня всегда вызывало головную боль: добавление/удаление/изменение параметров требовало пристального внимания (т.к. нужно было и строку с короткими опциями подправить, и структуру с длинными, и в справке поковыряться, и в switch добавить пунктик). И решил я это дело себе упростить.
За полтора дня не очень усердной работы я сделал вот такой парсер опций командной строки. По ссылке — архив с примером, под катом — как работает.
Итак, как оно работает.
А работает очень просто. Я расширил стандартную структуру struct option, чтобы она содержала еще и такие поля, как:
- type
- тип аргумента, который будет изменяться в случае, если пользователь ввел данный ключ
- argptr
- указатель на этот аргумент
- help
- подсказка по этому аргументу
Ну, а первые четыре поля в моей структуре myoption — те же самые, что и в struct option:
typedef struct{
// these are from struct option:
const char *name; // long option's name
int has_arg; // 0 - no args, 1 - nesessary arg, 2 - optionally arg
int *flag; // NULL to return val, pointer to int - to set its value of val (function returns 0)
int val; // short opt name (if flag == NULL) or flag's value
// and these are mine:
argtype type; // type of argument
void *argptr; // pointer to variable to assign optarg value or function `void (*fn)(char *optarg)`
char *help; // help string which would be shown in function `showhelp` or NULL
} myoption;
Для того, чтобы "пережевать" все введенные пользователем ключи, служит функция
void parceargs(int *argc, char ***argv, myoption *options);
Первые ее два аргумента — адреса параметров argc и argv функции main. Моя "парсилка" эти значения изменяет, возвращая то, что осталось "нераспарсенным", поэтому если нужно иметь возможность использовать их дальше в неизменном виде, следует сохранить эти переменные куда-нибудь.
Третий аргумент — массив структур myoption, в котором содержатся все опции. Завершается этот массив нулями (или константой end_option).
А работа у этой функции довольно-таки простая: "собрать" из массива options аргументы для функции getopt_long и "пережевать" ею все введенные пользователем ключи, вернув в первых двух аргументах то, что осталось "непережеванным".
Помимо этой функции я добавил еще функции
void showhelp(int oindex, myoption *options); void change_helpstring(char *s);
Вторая нужна, чтобы поменять стандартный заголовок справки по опциям (по умолчанию там просто имя программы). Его формат простой: это обычная строка (в т.ч. с переводами строк и прочими символами), которая не должна содержать никаких модификаторов "%x" кроме единственного дозволенного "%s". Вместо "%s" будет подставляться имя программы.
Первая же занимается тем, что из options собирает все ключи и справки, выводя красивое сообщение об использовании опций командной строки. Ее первый аргумент должен быть негативным, если нужна полная справка. Если же нужна справка только по N-му элементу массива options, надо присвоить oindex = N.
Ну и напоследок — рабочий пример. Как видите, необходимость ручного ввода сильно сокращается, что неизбежно приводит к уменьшению ошибок:
#include <stdio.h>
#include <limits.h>
#include "parceargs.h"
bool printval(void* arg){
printf("\n***\nCalled option to run this function with");
if(*((char*)arg) != '1') printf(" argument %s", (char*)arg);
else printf("out argument");
printf("\n***\n");
return TRUE;
}
int main(int argc, char **argv){
int i, hlp = 0, ibpar1 = 0, ibpar2 = 0, ipar = 0;
long long lpar = 0;
double dpar = 0;
char *chpar = NULL;
change_helpstring("Usage: %s [args]\n\nargs are:");
myoption cmdlnopts[] = {
// name has_arg flag val type argptr help
{"help", 0, NULL, 'h', arg_int, APTR(&hlp), "show this help"},
{"double", 1, NULL, 'd', arg_double, APTR(&dpar), "set double value"},
{"long-long",1, NULL, 'l', arg_longlong,APTR(&lpar), "set long long value"},
{"string", 1, NULL, 's', arg_string, APTR(&chpar), "set string value"},
{"integer", 1, NULL, 'i', arg_int, APTR(&ipar), "set integer value"},
{"boolean1",0, &ibpar1,1, arg_none, NULL, "set boolean one"},
{"boolean2",0, NULL, 'b', arg_int, APTR(&ibpar2), "set boolean two"},
{"func1", 0, NULL, '1', arg_function, APTR(printval),"run function without argument"},
{"func2", 1, NULL, '2', arg_function, APTR(printval),"run function with argument"},
{"func3", 2, NULL, '3', arg_function, APTR(printval),"run function with optional argument"},
end_option
};
parceargs(&argc, &argv, cmdlnopts);
if(hlp) showhelp(-1, cmdlnopts);
if(argc > 0){
printf("\nIgnore argument[s]:\n");
for (i = 0; i < argc; i++)
printf("\t%s\n", argv[i]);
}
printf("\nValues of parameters [default - zero]:\n");
printf("\tdouble = %g\n", dpar);
printf("\tlong long = %lld\n", lpar);
printf("\tinteger = %d\n", ipar);
printf("\tstring = %s\n", chpar);
printf("\tboolean1 = %s\n", ibpar1 ? "true" : "false");
printf("\tboolean2 = %s\n", ibpar2 ? "true" : "false");
printf("\n\n");
return 0;
}
А вот — короткое видео (1.7МБ), на котором я гоняю эту парсилку (в т.ч. демонстрирую ошибочное поведение и поведение с опциональными аргументами).
UPD: после комментария на фрихабре я задумался, что ведь и правда бывают "накапливаемые опции" (когда одна и та же опция вызывается несколько раз, а ее аргументы должны "накаприваться"). Обновил.
./testopts -1 -2 newar -d 4.65e3 -2 "another var" -s "A string with parameters" -2 "and one more" -3 --func2="last parameter"
***
Called option 1 (func1) to run this function without argument
***
***
Called option 2 (func2) to run this function with argument newar
***
***
Called option 2 (func2) to run this function with argument another var
***
***
Called option 2 (func2) to run this function with argument and one more
***
***
Called option 3 (func3) to run this function without argument
***
***
Called option 2 (func2) to run this function with argument last parameter
***
Values of parameters [default - zero]:
double = 4650
long long = 0
integer = 0
string = A string with parameters
boolean1 = false
boolean2 = false
Option '--func2' was called at least one time with argument[s]:
newar
another var
and one more
last parameter
Достаточно было лишь добавить еще один аргумент к вызываемой функции.
no subject
Date: 2013-04-08 05:33 am (UTC)no subject
Date: 2013-04-08 06:06 am (UTC)no subject
Date: 2013-04-08 06:14 am (UTC)no subject
Date: 2013-04-08 06:24 am (UTC)no subject
Date: 2013-04-08 06:25 am (UTC)Но ООП на С придётся осваивать, для того же умного дома. Чтобы единообразно общаться с датчиками.