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