Home | History | Annotate | Download | only in lib
      1 /* argmatch.c -- find a match for a string in an array
      2 
      3    Copyright (C) 1990, 1998-1999, 2001-2007, 2009-2012 Free Software
      4    Foundation, Inc.
      5 
      6    This program is free software: you can redistribute it and/or modify
      7    it under the terms of the GNU General Public License as published by
      8    the Free Software Foundation; either version 3 of the License, or
      9    (at your option) any later version.
     10 
     11    This program is distributed in the hope that it will be useful,
     12    but WITHOUT ANY WARRANTY; without even the implied warranty of
     13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14    GNU General Public License for more details.
     15 
     16    You should have received a copy of the GNU General Public License
     17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
     18 
     19 /* Written by David MacKenzie <djm (at) ai.mit.edu>
     20    Modified by Akim Demaille <demaille (at) inf.enst.fr> */
     21 
     22 #include <config.h>
     23 
     24 /* Specification.  */
     25 #include "argmatch.h"
     26 
     27 #include <stdbool.h>
     28 #include <stdio.h>
     29 #include <stdlib.h>
     30 #include <string.h>
     31 
     32 #include "gettext.h"
     33 #define _(msgid) gettext (msgid)
     34 
     35 #include "error.h"
     36 #include "quotearg.h"
     37 #include "quote.h"
     38 
     39 #if USE_UNLOCKED_IO
     40 # include "unlocked-io.h"
     41 #endif
     42 
     43 /* When reporting an invalid argument, show nonprinting characters
     44    by using the quoting style ARGMATCH_QUOTING_STYLE.  Do not use
     45    literal_quoting_style.  */
     46 #ifndef ARGMATCH_QUOTING_STYLE
     47 # define ARGMATCH_QUOTING_STYLE locale_quoting_style
     48 #endif
     49 
     50 /* Non failing version of argmatch call this function after failing. */
     51 #ifndef ARGMATCH_DIE
     52 # include "exitfail.h"
     53 # define ARGMATCH_DIE exit (exit_failure)
     54 #endif
     55 
     56 #ifdef ARGMATCH_DIE_DECL
     57 ARGMATCH_DIE_DECL;
     58 #endif
     59 
     60 static void
     61 __argmatch_die (void)
     62 {
     63   ARGMATCH_DIE;
     64 }
     65 
     66 /* Used by XARGMATCH and XARGCASEMATCH.  See description in argmatch.h.
     67    Default to __argmatch_die, but allow caller to change this at run-time. */
     68 argmatch_exit_fn argmatch_die = __argmatch_die;
     69 
     70 
     71 /* If ARG is an unambiguous match for an element of the
     73    NULL-terminated array ARGLIST, return the index in ARGLIST
     74    of the matched element, else -1 if it does not match any element
     75    or -2 if it is ambiguous (is a prefix of more than one element).
     76 
     77    If VALLIST is none null, use it to resolve ambiguities limited to
     78    synonyms, i.e., for
     79      "yes", "yop" -> 0
     80      "no", "nope" -> 1
     81    "y" is a valid argument, for 0, and "n" for 1.  */
     82 
     83 ptrdiff_t
     84 argmatch (const char *arg, const char *const *arglist,
     85           const char *vallist, size_t valsize)
     86 {
     87   size_t i;                     /* Temporary index in ARGLIST.  */
     88   size_t arglen;                /* Length of ARG.  */
     89   ptrdiff_t matchind = -1;      /* Index of first nonexact match.  */
     90   bool ambiguous = false;       /* If true, multiple nonexact match(es).  */
     91 
     92   arglen = strlen (arg);
     93 
     94   /* Test all elements for either exact match or abbreviated matches.  */
     95   for (i = 0; arglist[i]; i++)
     96     {
     97       if (!strncmp (arglist[i], arg, arglen))
     98         {
     99           if (strlen (arglist[i]) == arglen)
    100             /* Exact match found.  */
    101             return i;
    102           else if (matchind == -1)
    103             /* First nonexact match found.  */
    104             matchind = i;
    105           else
    106             {
    107               /* Second nonexact match found.  */
    108               if (vallist == NULL
    109                   || memcmp (vallist + valsize * matchind,
    110                              vallist + valsize * i, valsize))
    111                 {
    112                   /* There is a real ambiguity, or we could not
    113                      disambiguate. */
    114                   ambiguous = true;
    115                 }
    116             }
    117         }
    118     }
    119   if (ambiguous)
    120     return -2;
    121   else
    122     return matchind;
    123 }
    124 
    125 /* Error reporting for argmatch.
    126    CONTEXT is a description of the type of entity that was being matched.
    127    VALUE is the invalid value that was given.
    128    PROBLEM is the return value from argmatch.  */
    129 
    130 void
    131 argmatch_invalid (const char *context, const char *value, ptrdiff_t problem)
    132 {
    133   char const *format = (problem == -1
    134                         ? _("invalid argument %s for %s")
    135                         : _("ambiguous argument %s for %s"));
    136 
    137   error (0, 0, format, quotearg_n_style (0, ARGMATCH_QUOTING_STYLE, value),
    138          quote_n (1, context));
    139 }
    140 
    141 /* List the valid arguments for argmatch.
    142    ARGLIST is the same as in argmatch.
    143    VALLIST is a pointer to an array of values.
    144    VALSIZE is the size of the elements of VALLIST */
    145 void
    146 argmatch_valid (const char *const *arglist,
    147                 const char *vallist, size_t valsize)
    148 {
    149   size_t i;
    150   const char *last_val = NULL;
    151 
    152   /* We try to put synonyms on the same line.  The assumption is that
    153      synonyms follow each other */
    154   fputs (_("Valid arguments are:"), stderr);
    155   for (i = 0; arglist[i]; i++)
    156     if ((i == 0)
    157         || memcmp (last_val, vallist + valsize * i, valsize))
    158       {
    159         fprintf (stderr, "\n  - %s", quote (arglist[i]));
    160         last_val = vallist + valsize * i;
    161       }
    162     else
    163       {
    164         fprintf (stderr, ", %s", quote (arglist[i]));
    165       }
    166   putc ('\n', stderr);
    167 }
    168 
    169 /* Never failing versions of the previous functions.
    170 
    171    CONTEXT is the context for which argmatch is called (e.g.,
    172    "--version-control", or "$VERSION_CONTROL" etc.).  Upon failure,
    173    calls the (supposed never to return) function EXIT_FN. */
    174 
    175 ptrdiff_t
    176 __xargmatch_internal (const char *context,
    177                       const char *arg, const char *const *arglist,
    178                       const char *vallist, size_t valsize,
    179                       argmatch_exit_fn exit_fn)
    180 {
    181   ptrdiff_t res = argmatch (arg, arglist, vallist, valsize);
    182   if (res >= 0)
    183     /* Success. */
    184     return res;
    185 
    186   /* We failed.  Explain why. */
    187   argmatch_invalid (context, arg, res);
    188   argmatch_valid (arglist, vallist, valsize);
    189   (*exit_fn) ();
    190 
    191   return -1; /* To please the compilers. */
    192 }
    193 
    194 /* Look for VALUE in VALLIST, an array of objects of size VALSIZE and
    195    return the first corresponding argument in ARGLIST */
    196 const char *
    197 argmatch_to_argument (const char *value,
    198                       const char *const *arglist,
    199                       const char *vallist, size_t valsize)
    200 {
    201   size_t i;
    202 
    203   for (i = 0; arglist[i]; i++)
    204     if (!memcmp (value, vallist + valsize * i, valsize))
    205       return arglist[i];
    206   return NULL;
    207 }
    208 
    209 #ifdef TEST
    210 /*
    211  * Based on "getversion.c" by David MacKenzie <djm (at) gnu.ai.mit.edu>
    212  */
    213 char *program_name;
    214 
    215 /* When to make backup files.  */
    216 enum backup_type
    217 {
    218   /* Never make backups.  */
    219   no_backups,
    220 
    221   /* Make simple backups of every file.  */
    222   simple_backups,
    223 
    224   /* Make numbered backups of files that already have numbered backups,
    225      and simple backups of the others.  */
    226   numbered_existing_backups,
    227 
    228   /* Make numbered backups of every file.  */
    229   numbered_backups
    230 };
    231 
    232 /* Two tables describing arguments (keys) and their corresponding
    233    values */
    234 static const char *const backup_args[] =
    235 {
    236   "no", "none", "off",
    237   "simple", "never",
    238   "existing", "nil",
    239   "numbered", "t",
    240   0
    241 };
    242 
    243 static const enum backup_type backup_vals[] =
    244 {
    245   no_backups, no_backups, no_backups,
    246   simple_backups, simple_backups,
    247   numbered_existing_backups, numbered_existing_backups,
    248   numbered_backups, numbered_backups
    249 };
    250 
    251 int
    252 main (int argc, const char *const *argv)
    253 {
    254   const char *cp;
    255   enum backup_type backup_type = no_backups;
    256 
    257   program_name = (char *) argv[0];
    258 
    259   if (argc > 2)
    260     {
    261       fprintf (stderr, "Usage: %s [VERSION_CONTROL]\n", program_name);
    262       exit (1);
    263     }
    264 
    265   if ((cp = getenv ("VERSION_CONTROL")))
    266     backup_type = XARGMATCH ("$VERSION_CONTROL", cp,
    267                              backup_args, backup_vals);
    268 
    269   if (argc == 2)
    270     backup_type = XARGMATCH (program_name, argv[1],
    271                              backup_args, backup_vals);
    272 
    273   printf ("The version control is '%s'\n",
    274           ARGMATCH_TO_ARGUMENT (backup_type, backup_args, backup_vals));
    275 
    276   return 0;
    277 }
    278 #endif
    279