Емельянов Эдуард Владимирович (eddy_em) wrote,
Емельянов Эдуард Владимирович
eddy_em

  • Mood:

Упрощение вывода справки по параметрам команды и парсера параметров


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


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


Tags: c
Subscribe

  • Опять Подорванка смыла мост

    В четверг лило настолько, что, похоже, опять на подорванке забилась стремнина бревнами, а потом внезапно это все прорвало. МЧСовсцы перетаскивали…

  • Аж коробит…

    Уже который раз вижу (даже в прессе) выражение: "ставить прививку". Это что за странное такое словоблудие? Либо эпохи царской России, либо даже…

  • Упаковочка

    Получил сегодня пару посылок с алиэкспресса. Одна из них была с новым для меня способом доставки (на посылке не указывался адрес, а лишь "до…

promo eddy_em august 17, 2019 12:33 3
Buy for 10 tokens
Юра намедни напечатал корпус для хронометра. Для первого блина получилось неплохо: И еще немного фотографий:
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic
  • 5 comments