eddy_em: (hram nauki)
[personal profile] eddy_em

Вернулся я вчера к своей модели "кривого зеркала" и начал добавлять туда анализатор аргументов командной строки.
Вспомнил, что это дело у меня всегда вызывало головную боль: добавление/удаление/изменение параметров требовало пристального внимания (т.к. нужно было и строку с короткими опциями подправить, и структуру с длинными, и в справке поковыряться, и в 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


Достаточно было лишь добавить еще один аргумент к вызываемой функции.


Date: 2013-04-08 05:33 am (UTC)
From: [identity profile] dru4.livejournal.com
надо будет структуры поучить, интересные фишки на них делать можно, оказывается.

Date: 2013-04-08 06:06 am (UTC)
From: [identity profile] eddy-em.livejournal.com
Ну так в glib вообще объектное ориентирование реализовано на структурах.
Edited Date: 2013-04-08 06:07 am (UTC)

Date: 2013-04-08 06:14 am (UTC)
From: [identity profile] dru4.livejournal.com
я ООП не очень хорошо перевариваю. Я и на рнр пишу в основном с использованием функций.

Date: 2013-04-08 06:24 am (UTC)
From: [identity profile] eddy-em.livejournal.com
Я — тоже. У меня что-то задач, которым ООП нужен, и нет-то.

Date: 2013-04-08 06:25 am (UTC)
From: [identity profile] dru4.livejournal.com
Во-во. Чем проще- тем лучше. Правда, поддерживать потом, спустя лет эдак 5, тяжело.

Но ООП на С придётся осваивать, для того же умного дома. Чтобы единообразно общаться с датчиками.

June 2025

S M T W T F S
123 4567
891011121314
15161718192021
22232425262728
2930     

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 6th, 2025 06:03 pm
Powered by Dreamwidth Studios