barbieri pushed a commit to branch master.

http://git.enlightenment.org/core/efl.git/commit/?id=0b86e5119f328b6fa979bcbff6391a6458c6efe8

commit 0b86e5119f328b6fa979bcbff6391a6458c6efe8
Author: Gustavo Sverzut Barbieri <barbi...@profusion.mobi>
Date:   Tue Dec 17 21:06:53 2013 -0200

    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 :-)
---
 src/examples/ecore/ecore_getopt_example.c |  84 ++++++++-
 src/lib/ecore/Ecore_Getopt.h              |   3 +
 src/lib/ecore/ecore_getopt.c              | 299 +++++++++++++++++++++++++++++-
 3 files changed, 376 insertions(+), 10 deletions(-)

diff --git a/src/examples/ecore/ecore_getopt_example.c 
b/src/examples/ecore/ecore_getopt_example.c
index dc91f0c..f299266 100644
--- a/src/examples/ecore/ecore_getopt_example.c
+++ b/src/examples/ecore/ecore_getopt_example.c
@@ -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();
diff --git a/src/lib/ecore/Ecore_Getopt.h b/src/lib/ecore/Ecore_Getopt.h
index 2cc73ad..64e3eed 100644
--- a/src/lib/ecore/Ecore_Getopt.h
+++ b/src/lib/ecore/Ecore_Getopt.h
@@ -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_*() */
diff --git a/src/lib/ecore/ecore_getopt.c b/src/lib/ecore/ecore_getopt.c
index 2a7850f..2af5d47 100644
--- a/src/lib/ecore/ecore_getopt.c
+++ b/src/lib/ecore/ecore_getopt.c
@@ -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,
@@ -1878,6 +2048,123 @@ error:
 }
 
 /**
+ * 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.
  *
  * @param list pointer to list to be freed.

-- 


Reply via email to