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