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