forked from enlightenment/efl
getopt: add positional argument handling.
positional arguments must appear at the end of the description array (after the last option) and should have a metavar set and not have shortname or longname. Simple, elegant and fit :-) There is a new function to parse the positional arguments, ecore_getopt_parse_positional() because we may want to not try to parse them in the case of a quit-option such as --help, --license, --copyright, --version or some user-defined action. This avoids us producing errors of missing positional arguments when printing help and adds some flexibility as well. This should make Tasn happy :-)
This commit is contained in:
parent
1c1f9ea0e1
commit
0b86e5119f
|
@ -5,6 +5,17 @@
|
|||
#include <Ecore_Getopt.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* if defined will end the positional arguments with the special
|
||||
* action ECORE_GETOPT_ACTION_APPEND that will require at least one
|
||||
* trailing argument and will also consume the remaining arguments
|
||||
* until the end.
|
||||
*
|
||||
* if not defined unhandled positional arguments start at the index
|
||||
* returned by ecore_getopt_parse_positional(), that will be less or
|
||||
* equal to argc.
|
||||
*/
|
||||
#define END_WITH_POS_APPEND 1
|
||||
|
||||
static const char * available_choices[] = {
|
||||
"banana",
|
||||
"apple",
|
||||
|
@ -15,8 +26,8 @@ static const char * available_choices[] = {
|
|||
static const Ecore_Getopt options = {
|
||||
/* program name, usually a macro PACKAGE_NAME */
|
||||
"ecore_getopt_example",
|
||||
/* usage line */
|
||||
"%prog [options]",
|
||||
/* usage line, leave empty to generate one with positional arguments */
|
||||
NULL,
|
||||
/* program version, usually a macro PACKAGE_VERSION */
|
||||
"0.1",
|
||||
/* copyright string */
|
||||
|
@ -136,6 +147,25 @@ static const Ecore_Getopt options = {
|
|||
ECORE_GETOPT_LICENSE('L', "license"),
|
||||
ECORE_GETOPT_HELP('h', "help"),
|
||||
|
||||
/* positional arguments can be handled as well, add their
|
||||
* description after the last option was specified. They should
|
||||
* have empty short and long options, but have the metavar
|
||||
* defined.
|
||||
*/
|
||||
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, "Positional string.", "STRING"),
|
||||
ECORE_GETOPT_STORE_METAVAR_INT(0, NULL, "Positional integer.", "INT"),
|
||||
ECORE_GETOPT_CHOICE_METAVAR(0, NULL, "Positional choice.", "CHOICE",
|
||||
available_choices),
|
||||
|
||||
#ifdef END_WITH_POS_APPEND
|
||||
/* this will consume until the end of the command line, forcing
|
||||
* ecore_getopt_parse() to return args == argc on succes.
|
||||
* It will require at least one argument in the end of the command line.
|
||||
*/
|
||||
ECORE_GETOPT_APPEND_METAVAR(0, NULL, "Extra options.", "ARG",
|
||||
ECORE_GETOPT_TYPE_STR),
|
||||
#endif
|
||||
|
||||
/* the sentinel is required to notify end of descriptions */
|
||||
ECORE_GETOPT_SENTINEL
|
||||
}
|
||||
|
@ -172,6 +202,12 @@ main(int argc, char **argv)
|
|||
Eina_List *lst_ints = NULL;
|
||||
Eina_Bool break_given = EINA_FALSE;
|
||||
Eina_Bool quit_option = EINA_FALSE;
|
||||
char *pos_str = NULL;
|
||||
int pos_int = 0;
|
||||
char *pos_choice = NULL;
|
||||
#ifdef END_WITH_POS_APPEND
|
||||
Eina_List *pos_args = NULL;
|
||||
#endif
|
||||
Ecore_Getopt_Value values[] = {
|
||||
/* block of options that store a single value in a variable of type */
|
||||
ECORE_GETOPT_VALUE_STR(str_value),
|
||||
|
@ -218,6 +254,15 @@ main(int argc, char **argv)
|
|||
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
|
||||
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
|
||||
|
||||
/* example of positiona argument */
|
||||
ECORE_GETOPT_VALUE_STR(pos_str),
|
||||
ECORE_GETOPT_VALUE_INT(pos_int),
|
||||
ECORE_GETOPT_VALUE_STR(pos_choice),
|
||||
|
||||
#ifdef END_WITH_POS_APPEND
|
||||
ECORE_GETOPT_VALUE_LIST(pos_args),
|
||||
#endif
|
||||
|
||||
ECORE_GETOPT_VALUE_NONE /* sentinel */
|
||||
};
|
||||
int args, retval = EXIT_SUCCESS;
|
||||
|
@ -240,6 +285,14 @@ main(int argc, char **argv)
|
|||
/* options that set 'quit_option' to true requires us to exit. */
|
||||
if (quit_option) goto end;
|
||||
|
||||
args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
|
||||
if (args < 0)
|
||||
{
|
||||
fputs("ERROR: Could not parse positional arguments.\n", stderr);
|
||||
retval = EXIT_FAILURE;
|
||||
goto end;
|
||||
}
|
||||
|
||||
printf("given values:\n"
|
||||
"string = %s\n"
|
||||
"bool = %s\n"
|
||||
|
@ -269,6 +322,10 @@ main(int argc, char **argv)
|
|||
"choice = %s\n"
|
||||
"\n"
|
||||
"--break = %s\n"
|
||||
"\nDeclared Positional:\n"
|
||||
"STRING = %s\n"
|
||||
"INT = %d\n"
|
||||
"CHOICE = %s\n"
|
||||
"\n",
|
||||
str_value,
|
||||
bool_value ? "true" : "false",
|
||||
|
@ -294,7 +351,10 @@ main(int argc, char **argv)
|
|||
use_z,
|
||||
count,
|
||||
choice,
|
||||
break_given ? "given" : "omitted");
|
||||
break_given ? "given" : "omitted",
|
||||
pos_str,
|
||||
pos_int,
|
||||
pos_choice);
|
||||
|
||||
if (!lst_strs)
|
||||
puts("no --append-string=VALUE was given.");
|
||||
|
@ -324,14 +384,30 @@ main(int argc, char **argv)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef END_WITH_POS_APPEND
|
||||
assert(pos_args != NULL);
|
||||
assert(args == argc);
|
||||
if (1)
|
||||
{
|
||||
char *str;
|
||||
printf("%u extra arguments:\n",
|
||||
eina_list_count(pos_args));
|
||||
EINA_LIST_FREE(pos_args, str)
|
||||
{
|
||||
printf("\t%s\n", str);
|
||||
free(str);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (args == argc)
|
||||
puts("no positional arguments were given.");
|
||||
puts("no extra positional arguments were given.");
|
||||
else
|
||||
{
|
||||
printf("%d positional arguments were given:\n", argc - args);
|
||||
for (; args < argc; args++)
|
||||
printf("\t%s\n", argv[args]);
|
||||
}
|
||||
#endif
|
||||
|
||||
end:
|
||||
ecore_shutdown();
|
||||
|
|
|
@ -409,6 +409,9 @@ EAPI void ecore_getopt_help(FILE *fp, const Ecore_Getopt *info);
|
|||
EAPI Eina_Bool ecore_getopt_parser_has_duplicates(const Ecore_Getopt *parser);
|
||||
EAPI int ecore_getopt_parse(const Ecore_Getopt *parser, Ecore_Getopt_Value *values, int argc, char **argv);
|
||||
|
||||
EAPI int ecore_getopt_parse_positional(const Ecore_Getopt *parser, Ecore_Getopt_Value *values, int argc, char **argv, int start);
|
||||
|
||||
|
||||
EAPI Eina_List *ecore_getopt_list_free(Eina_List *list);
|
||||
|
||||
/* helper functions to be used with ECORE_GETOPT_CALLBACK_*() */
|
||||
|
|
|
@ -43,6 +43,12 @@ static int _argc = 0;
|
|||
static int cols = 80;
|
||||
static int helpcol = 80 / 3;
|
||||
|
||||
|
||||
static Eina_Bool _ecore_getopt_desc_is_sentinel(const Ecore_Getopt_Desc *desc);
|
||||
static Ecore_Getopt_Desc_Arg_Requirement _ecore_getopt_desc_arg_requirement(const Ecore_Getopt_Desc *desc);
|
||||
static void _ecore_getopt_help_desc_setup_metavar(const Ecore_Getopt_Desc *desc, char *metavar, int *metavarlen, int maxsize);
|
||||
|
||||
|
||||
static void
|
||||
_ecore_getopt_help_print_replace_program(FILE *fp,
|
||||
const Ecore_Getopt *parser EINA_UNUSED,
|
||||
|
@ -98,7 +104,42 @@ _ecore_getopt_help_usage(FILE *fp,
|
|||
|
||||
if (!parser->usage)
|
||||
{
|
||||
fprintf(fp, _("%s [options]\n"), prog);
|
||||
const Ecore_Getopt_Desc *d;
|
||||
|
||||
fprintf(fp, _("%s [options]"), prog);
|
||||
|
||||
for (d = parser->descs; !_ecore_getopt_desc_is_sentinel(d); d++);
|
||||
|
||||
if (d->metavar)
|
||||
{
|
||||
for (; d->metavar != NULL; d++)
|
||||
{
|
||||
Ecore_Getopt_Desc_Arg_Requirement ar;
|
||||
char metavar[32];
|
||||
int metavarlen = 0;
|
||||
|
||||
ar = _ecore_getopt_desc_arg_requirement(d);
|
||||
_ecore_getopt_help_desc_setup_metavar(d, metavar,
|
||||
&metavarlen,
|
||||
sizeof(metavar));
|
||||
|
||||
fputc(' ', fp);
|
||||
if (ar != ECORE_GETOPT_DESC_ARG_REQUIREMENT_YES)
|
||||
fputc('[', fp);
|
||||
fputs(metavar, fp);
|
||||
|
||||
if (ar != ECORE_GETOPT_DESC_ARG_REQUIREMENT_YES)
|
||||
fputc(']', fp);
|
||||
|
||||
if (d->action == ECORE_GETOPT_ACTION_APPEND)
|
||||
{
|
||||
fprintf(fp, " [%s] ...", metavar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fputc('\n', fp);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -358,7 +399,8 @@ static int
|
|||
_ecore_getopt_help_desc_show_arg(FILE *fp,
|
||||
Ecore_Getopt_Desc_Arg_Requirement requirement,
|
||||
const char *metavar,
|
||||
int metavarlen)
|
||||
int metavarlen,
|
||||
Eina_Bool show_attr)
|
||||
{
|
||||
int used;
|
||||
|
||||
|
@ -375,9 +417,13 @@ _ecore_getopt_help_desc_show_arg(FILE *fp,
|
|||
|
||||
if (requirement != ECORE_GETOPT_DESC_ARG_REQUIREMENT_NO)
|
||||
{
|
||||
fputc('=', fp);
|
||||
if (show_attr)
|
||||
{
|
||||
fputc('=', fp);
|
||||
used++;
|
||||
}
|
||||
fputs(metavar, fp);
|
||||
used += metavarlen + 1;
|
||||
used += metavarlen;
|
||||
}
|
||||
|
||||
if (requirement == ECORE_GETOPT_DESC_ARG_REQUIREMENT_OPTIONAL)
|
||||
|
@ -597,7 +643,7 @@ _ecore_getopt_help_desc(FILE *fp,
|
|||
fputc(desc->shortname, fp);
|
||||
used += 2;
|
||||
used += _ecore_getopt_help_desc_show_arg
|
||||
(fp, arg_req, metavar, metavarlen);
|
||||
(fp, arg_req, metavar, metavarlen, EINA_TRUE);
|
||||
}
|
||||
|
||||
if (desc->shortname && desc->longname)
|
||||
|
@ -614,7 +660,12 @@ _ecore_getopt_help_desc(FILE *fp,
|
|||
fputs(desc->longname, fp);
|
||||
used += 2 + namelen;
|
||||
used += _ecore_getopt_help_desc_show_arg
|
||||
(fp, arg_req, metavar, metavarlen);
|
||||
(fp, arg_req, metavar, metavarlen, EINA_TRUE);
|
||||
}
|
||||
else if ((!desc->shortname) && (desc->metavar))
|
||||
{
|
||||
used += _ecore_getopt_help_desc_show_arg
|
||||
(fp, arg_req, metavar, metavarlen, EINA_FALSE);
|
||||
}
|
||||
|
||||
if (!desc->help)
|
||||
|
@ -668,6 +719,14 @@ _ecore_getopt_help_options(FILE *fp,
|
|||
_ecore_getopt_help_desc(fp, desc);
|
||||
|
||||
fputc('\n', fp);
|
||||
|
||||
if (!desc->metavar) return;
|
||||
|
||||
fputs(_("Positional arguments:\n"), fp);
|
||||
for (; desc->metavar != NULL; desc++)
|
||||
_ecore_getopt_help_desc(fp, desc);
|
||||
|
||||
fputc('\n', fp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -877,6 +936,14 @@ _ecore_getopt_desc_print_error(const Ecore_Getopt_Desc *desc,
|
|||
fputs("--", stderr);
|
||||
fputs(desc->longname, stderr);
|
||||
}
|
||||
else if ((!desc->shortname) && (desc->metavar))
|
||||
{
|
||||
char metavar[32];
|
||||
int metavarlen = 0;
|
||||
_ecore_getopt_help_desc_setup_metavar(desc, metavar, &metavarlen,
|
||||
sizeof(metavar));
|
||||
fputs(metavar, stderr);
|
||||
}
|
||||
|
||||
fputs(": ", stderr);
|
||||
|
||||
|
@ -1667,6 +1734,98 @@ _ecore_getopt_parse_arg(const Ecore_Getopt *parser,
|
|||
return _ecore_getopt_parse_arg_short(parser, values, argc, argv, idx, nonargs, arg + 1);
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_ecore_getopt_parse_pos(const Ecore_Getopt *parser,
|
||||
const Ecore_Getopt_Desc **p_desc,
|
||||
Ecore_Getopt_Value *values,
|
||||
int argc,
|
||||
char **argv,
|
||||
int *idx,
|
||||
int *nonargs)
|
||||
{
|
||||
const Ecore_Getopt_Desc *desc = *p_desc;
|
||||
Ecore_Getopt_Desc_Arg_Requirement arg_req;
|
||||
char metavar[32];
|
||||
int metavarlen = 0;
|
||||
const char *arg_val;
|
||||
int desc_idx;
|
||||
Ecore_Getopt_Value *value;
|
||||
Eina_Bool ret;
|
||||
|
||||
_ecore_getopt_help_desc_setup_metavar
|
||||
(desc, metavar, &metavarlen, sizeof(metavar));
|
||||
|
||||
desc_idx = desc - parser->descs;
|
||||
value = values + desc_idx;
|
||||
|
||||
arg_req = _ecore_getopt_desc_arg_requirement(desc);
|
||||
if (*idx >= argc)
|
||||
{
|
||||
(*p_desc)++;
|
||||
if (arg_req == ECORE_GETOPT_DESC_ARG_REQUIREMENT_YES)
|
||||
{
|
||||
/* TODO: should we consider callback here as well? */
|
||||
if ((desc->action == ECORE_GETOPT_ACTION_APPEND) &&
|
||||
(value->listp) && (*value->listp))
|
||||
{
|
||||
printf("append desc: %s (%d), value: %p\n",
|
||||
desc->metavar, desc_idx, value->listp);
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
_("ERROR: missing required positional argument %s.\n"),
|
||||
metavar);
|
||||
return EINA_FALSE;
|
||||
}
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
arg_val = argv[*idx];
|
||||
|
||||
switch (desc->action)
|
||||
{
|
||||
case ECORE_GETOPT_ACTION_STORE:
|
||||
ret = _ecore_getopt_parse_store(parser, desc, value, arg_val);
|
||||
(*p_desc)++;
|
||||
break;
|
||||
|
||||
case ECORE_GETOPT_ACTION_CHOICE:
|
||||
ret = _ecore_getopt_parse_choice(parser, desc, value, arg_val);
|
||||
(*p_desc)++;
|
||||
break;
|
||||
|
||||
case ECORE_GETOPT_ACTION_APPEND:
|
||||
ret = _ecore_getopt_parse_append(parser, desc, value, arg_val);
|
||||
/* no changes to p_desc, we keep appending until the end */
|
||||
break;
|
||||
|
||||
case ECORE_GETOPT_ACTION_CALLBACK:
|
||||
ret = _ecore_getopt_parse_callback(parser, desc, value, arg_val);
|
||||
(*p_desc)++;
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, _("ERROR: unsupported action type %d "
|
||||
"for positional argument %s\n"),
|
||||
desc->action, metavar);
|
||||
(*p_desc)++;
|
||||
ret = EINA_FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
{
|
||||
(*idx)++;
|
||||
(*nonargs)++;
|
||||
}
|
||||
|
||||
if ((!ret) && parser->strict)
|
||||
return EINA_FALSE;
|
||||
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static const Ecore_Getopt_Desc *
|
||||
_ecore_getopt_parse_find_short_other(const Ecore_Getopt *parser,
|
||||
const Ecore_Getopt_Desc *orig)
|
||||
|
@ -1803,6 +1962,15 @@ _ecore_getopt_find_help(const Ecore_Getopt *parser)
|
|||
* and copyright may be translated, standard/global gettext() call
|
||||
* will be applied on them if ecore was compiled with such support.
|
||||
*
|
||||
* This function will @b not parse positional arguments! If these are
|
||||
* declared (metavar is defined with both shortname and longname being
|
||||
* empty), then you must call ecore_getopt_parse_positional() with the
|
||||
* last argument (@c start) being the result of this function. This is
|
||||
* done so you can have "quit options", those that once called you
|
||||
* want to exit without doing further parsing, as is the case with
|
||||
* help, license, copyright, version and eventually others you may
|
||||
* define.
|
||||
*
|
||||
* @param parser description of how to work.
|
||||
* @param values where to store values, it is assumed that this is a vector
|
||||
* of the same size as @c parser->descs. Values should be previously
|
||||
|
@ -1812,6 +1980,8 @@ _ecore_getopt_find_help(const Ecore_Getopt *parser)
|
|||
* @param argv command line parameters.
|
||||
*
|
||||
* @return index of first non-option parameter or -1 on error.
|
||||
*
|
||||
* @see ecore_getopt_parse_positional()
|
||||
*/
|
||||
EAPI int
|
||||
ecore_getopt_parse(const Ecore_Getopt *parser,
|
||||
|
@ -1877,6 +2047,123 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse command line positional parameters.
|
||||
*
|
||||
* Walks the command line positional parameters (those that do not
|
||||
* start with "-" or "--") and parse them based on @a parser
|
||||
* description, doing actions based on @c parser->descs->action, like
|
||||
* storing values of some type.
|
||||
*
|
||||
* It is expected that @a values is of the same size than @c
|
||||
* parser->descs, same as with ecore_getopt_parse().
|
||||
*
|
||||
* All values are expected to be initialized before use.
|
||||
*
|
||||
* Unlike the ecore_getopt_parse(), only the following options are
|
||||
* supported:
|
||||
* - @c ECORE_GETOPT_ACTION_STORE
|
||||
* - @c ECORE_GETOPT_ACTION_CHOICE
|
||||
* - @c ECORE_GETOPT_ACTION_APPEND
|
||||
* - @c ECORE_GETOPT_ACTION_CALLBACK
|
||||
*
|
||||
* There is a special case for @c ECORE_GETOPT_ACTION_APPEND as it
|
||||
* will consume all remaining elements. It is also special in the
|
||||
* sense that it will allocate memory and thus need to be freed. For
|
||||
* consistency between all of appended subtypes, @c eina_list->data
|
||||
* will contain an allocated memory with the value, that is, for @c
|
||||
* ECORE_GETOPT_TYPE_STR it will contain a copy of the argument, @c
|
||||
* ECORE_GETOPT_TYPE_INT a pointer to an allocated integer and so on.
|
||||
*
|
||||
* If parser is in strict mode (see @c Ecore_Getopt->strict), then any
|
||||
* error will abort parsing and @c -1 is returned. Otherwise it will try
|
||||
* to continue as far as possible.
|
||||
*
|
||||
* Translation of help strings (description) and metavar may be done,
|
||||
* standard/global gettext() call will be applied on them if ecore was
|
||||
* compiled with such support.
|
||||
*
|
||||
* @param parser description of how to work.
|
||||
* @param values where to store values, it is assumed that this is a vector
|
||||
* of the same size as @c parser->descs. Values should be previously
|
||||
* initialized.
|
||||
* @param argc how many elements in @a argv. If not provided it will be
|
||||
* retrieved with ecore_app_args_get().
|
||||
* @param argv command line parameters.
|
||||
* @param start the initial position argument to look at, usually the
|
||||
* return of ecore_getopt_parse(). If less than 1, will try to
|
||||
* find it automatically.
|
||||
*
|
||||
* @return index of first non-option parameter or -1 on error. If the
|
||||
* last positional argument is of action @c
|
||||
* ECORE_GETOPT_ACTION_APPEND then it will be the same as @a argc.
|
||||
*/
|
||||
EAPI int
|
||||
ecore_getopt_parse_positional(const Ecore_Getopt *parser,
|
||||
Ecore_Getopt_Value *values,
|
||||
int argc,
|
||||
char **argv,
|
||||
int start)
|
||||
{
|
||||
const Ecore_Getopt_Desc *desc;
|
||||
int nonargs;
|
||||
|
||||
if (!parser)
|
||||
{
|
||||
fputs(_("ERROR: no parser provided.\n"), stderr);
|
||||
return -1;
|
||||
}
|
||||
if (!values)
|
||||
{
|
||||
fputs(_("ERROR: no values provided.\n"), stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((argc < 1) || (!argv))
|
||||
ecore_app_args_get(&argc, &argv);
|
||||
|
||||
if (argc < 1)
|
||||
{
|
||||
fputs(_("ERROR: no arguments provided.\n"), stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[0])
|
||||
prog = argv[0];
|
||||
else
|
||||
prog = parser->prog;
|
||||
|
||||
if (start > argc)
|
||||
start = argc;
|
||||
else if (start < 1)
|
||||
start = _ecore_getopt_parse_find_nonargs_base(parser, argc, argv);
|
||||
|
||||
nonargs = start;
|
||||
for (desc = parser->descs; !_ecore_getopt_desc_is_sentinel(desc); desc++);
|
||||
while (desc->metavar)
|
||||
if (!_ecore_getopt_parse_pos
|
||||
(parser, &desc, values, argc, argv, &start, &nonargs))
|
||||
goto error;
|
||||
|
||||
return nonargs;
|
||||
|
||||
error:
|
||||
{
|
||||
const Ecore_Getopt_Desc *help;
|
||||
fputs(_("ERROR: invalid positional arguments found."), stderr);
|
||||
|
||||
help = _ecore_getopt_find_help(parser);
|
||||
if (!help)
|
||||
fputc('\n', stderr);
|
||||
else if (help->longname)
|
||||
fprintf(stderr, _(" See --%s.\n"), help->longname);
|
||||
else
|
||||
fprintf(stderr, _(" See -%c.\n"), help->shortname);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to free list and nodes allocated by @a ECORE_GETOPT_ACTION_APPEND.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue