1/*
2 * tkArgv.c --
3 *
4 *	This file contains a function that handles table-based argv-argc
5 *	parsing.
6 *
7 * Copyright (c) 1990-1994 The Regents of the University of California.
8 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
9 *
10 * See the file "license.terms" for information on usage and redistribution of
11 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id$
14 */
15
16#include "tkInt.h"
17
18/*
19 * Default table of argument descriptors. These are normally available in
20 * every application.
21 */
22
23static Tk_ArgvInfo defaultTable[] = {
24    {"-help",	TK_ARGV_HELP, NULL, NULL,
25	"Print summary of command-line options and abort"},
26    {NULL,	TK_ARGV_END, NULL, NULL, NULL}
27};
28
29/*
30 * Forward declarations for functions defined in this file:
31 */
32
33static void	PrintUsage(Tcl_Interp *interp, Tk_ArgvInfo *argTable,
34		    int flags);
35
36/*
37 *----------------------------------------------------------------------
38 *
39 * Tk_ParseArgv --
40 *
41 *	Process an argv array according to a table of expected command-line
42 *	options. See the manual page for more details.
43 *
44 * Results:
45 *	The return value is a standard Tcl return value. If an error occurs
46 *	then an error message is left in the interp's result. Under normal
47 *	conditions, both *argcPtr and *argv are modified to return the
48 *	arguments that couldn't be processed here (they didn't match the
49 *	option table, or followed an TK_ARGV_REST argument).
50 *
51 * Side effects:
52 *	Variables may be modified, resources may be entered for tkwin, or
53 *	functions may be called. It all depends on the arguments and their
54 *	entries in argTable. See the user documentation for details.
55 *
56 *----------------------------------------------------------------------
57 */
58
59int
60Tk_ParseArgv(
61    Tcl_Interp *interp,		/* Place to store error message. */
62    Tk_Window tkwin,		/* Window to use for setting Tk options. NULL
63				 * means ignore Tk option specs. */
64    int *argcPtr,		/* Number of arguments in argv. Modified to
65				 * hold # args left in argv at end. */
66    CONST char **argv,		/* Array of arguments. Modified to hold those
67				 * that couldn't be processed here. */
68    Tk_ArgvInfo *argTable,	/* Array of option descriptions */
69    int flags)			/* Or'ed combination of various flag bits,
70				 * such as TK_ARGV_NO_DEFAULTS. */
71{
72    register Tk_ArgvInfo *infoPtr;
73				/* Pointer to the current entry in the table
74				 * of argument descriptions. */
75    Tk_ArgvInfo *matchPtr;	/* Descriptor that matches current argument. */
76    CONST char *curArg;		/* Current argument */
77    register char c;		/* Second character of current arg (used for
78				 * quick check for matching; use 2nd char.
79				 * because first char. will almost always be
80				 * '-'). */
81    int srcIndex;		/* Location from which to read next argument
82				 * from argv. */
83    int dstIndex;		/* Index into argv to which next unused
84				 * argument should be copied (never greater
85				 * than srcIndex). */
86    int argc;			/* # arguments in argv still to process. */
87    size_t length;		/* Number of characters in current argument. */
88    int i;
89
90    if (flags & TK_ARGV_DONT_SKIP_FIRST_ARG) {
91	srcIndex = dstIndex = 0;
92	argc = *argcPtr;
93    } else {
94	srcIndex = dstIndex = 1;
95	argc = *argcPtr-1;
96    }
97
98    while (argc > 0) {
99	curArg = argv[srcIndex];
100	srcIndex++;
101	argc--;
102	length = strlen(curArg);
103	if (length > 0) {
104	    c = curArg[1];
105	} else {
106	    c = 0;
107	}
108
109	/*
110	 * Loop throught the argument descriptors searching for one with the
111	 * matching key string. If found, leave a pointer to it in matchPtr.
112	 */
113
114	matchPtr = NULL;
115	for (i = 0; i < 2; i++) {
116	    if (i == 0) {
117		infoPtr = argTable;
118	    } else {
119		infoPtr = defaultTable;
120	    }
121	    for (; (infoPtr != NULL) && (infoPtr->type != TK_ARGV_END);
122		    infoPtr++) {
123		if (infoPtr->key == NULL) {
124		    continue;
125		}
126		if ((infoPtr->key[1] != c)
127			|| (strncmp(infoPtr->key, curArg, length) != 0)) {
128		    continue;
129		}
130		if ((tkwin == NULL)
131			&& ((infoPtr->type == TK_ARGV_CONST_OPTION)
132			|| (infoPtr->type == TK_ARGV_OPTION_VALUE)
133			|| (infoPtr->type == TK_ARGV_OPTION_NAME_VALUE))) {
134		    continue;
135		}
136		if (infoPtr->key[length] == 0) {
137		    matchPtr = infoPtr;
138		    goto gotMatch;
139		}
140		if (flags & TK_ARGV_NO_ABBREV) {
141		    continue;
142		}
143		if (matchPtr != NULL) {
144		    Tcl_AppendResult(interp, "ambiguous option \"", curArg,
145			    "\"", NULL);
146		    return TCL_ERROR;
147		}
148		matchPtr = infoPtr;
149	    }
150	}
151	if (matchPtr == NULL) {
152	    /*
153	     * Unrecognized argument. Just copy it down, unless the caller
154	     * prefers an error to be registered.
155	     */
156
157	    if (flags & TK_ARGV_NO_LEFTOVERS) {
158		Tcl_AppendResult(interp, "unrecognized argument \"",
159			curArg, "\"", NULL);
160		return TCL_ERROR;
161	    }
162	    argv[dstIndex] = curArg;
163	    dstIndex++;
164	    continue;
165	}
166
167	/*
168	 * Take the appropriate action based on the option type
169	 */
170
171    gotMatch:
172	infoPtr = matchPtr;
173	switch (infoPtr->type) {
174	case TK_ARGV_CONSTANT:
175	    *((int *) infoPtr->dst) = PTR2INT(infoPtr->src);
176	    break;
177	case TK_ARGV_INT:
178	    if (argc == 0) {
179		goto missingArg;
180	    } else {
181		char *endPtr;
182
183		*((int *) infoPtr->dst) = strtol(argv[srcIndex], &endPtr, 0);
184		if ((endPtr == argv[srcIndex]) || (*endPtr != 0)) {
185		    Tcl_AppendResult(interp,"expected integer argument for \"",
186			    infoPtr->key, "\" but got \"", argv[srcIndex],
187			    "\"", NULL);
188		    return TCL_ERROR;
189		}
190		srcIndex++;
191		argc--;
192	    }
193	    break;
194	case TK_ARGV_STRING:
195	    if (argc == 0) {
196		goto missingArg;
197	    }
198	    *((CONST char **)infoPtr->dst) = argv[srcIndex];
199	    srcIndex++;
200	    argc--;
201	    break;
202	case TK_ARGV_UID:
203	    if (argc == 0) {
204		goto missingArg;
205	    }
206	    *((Tk_Uid *)infoPtr->dst) = Tk_GetUid(argv[srcIndex]);
207	    srcIndex++;
208	    argc--;
209	    break;
210	case TK_ARGV_REST:
211	    *((int *) infoPtr->dst) = dstIndex;
212	    goto argsDone;
213	case TK_ARGV_FLOAT:
214	    if (argc == 0) {
215		goto missingArg;
216	    } else {
217		char *endPtr;
218
219		*((double *) infoPtr->dst) = strtod(argv[srcIndex], &endPtr);
220		if ((endPtr == argv[srcIndex]) || (*endPtr != 0)) {
221		    Tcl_AppendResult(interp, "expected floating-point ",
222			    "argument for \"", infoPtr->key, "\" but got \"",
223			    argv[srcIndex], "\"", NULL);
224		    return TCL_ERROR;
225		}
226		srcIndex++;
227		argc--;
228	    }
229	    break;
230	case TK_ARGV_FUNC: {
231	    typedef int (ArgvFunc)(char *, char *, CONST char *);
232	    ArgvFunc *handlerProc = (ArgvFunc *) infoPtr->src;
233
234	    if ((*handlerProc)(infoPtr->dst, infoPtr->key, argv[srcIndex])) {
235		srcIndex++;
236		argc--;
237	    }
238	    break;
239	}
240	case TK_ARGV_GENFUNC: {
241	    typedef int (ArgvGenFunc)(char *, Tcl_Interp *, char *, int,
242		    CONST char **);
243	    ArgvGenFunc *handlerProc = (ArgvGenFunc *) infoPtr->src;
244
245	    argc = (*handlerProc)(infoPtr->dst, interp, infoPtr->key,
246		    argc, argv+srcIndex);
247	    if (argc < 0) {
248		return TCL_ERROR;
249	    }
250	    break;
251	}
252	case TK_ARGV_HELP:
253	    PrintUsage(interp, argTable, flags);
254	    return TCL_ERROR;
255	case TK_ARGV_CONST_OPTION:
256	    Tk_AddOption(tkwin, infoPtr->dst, infoPtr->src,
257		    TK_INTERACTIVE_PRIO);
258	    break;
259	case TK_ARGV_OPTION_VALUE:
260	    if (argc < 1) {
261		goto missingArg;
262	    }
263	    Tk_AddOption(tkwin, infoPtr->dst, argv[srcIndex],
264		    TK_INTERACTIVE_PRIO);
265	    srcIndex++;
266	    argc--;
267	    break;
268	case TK_ARGV_OPTION_NAME_VALUE:
269	    if (argc < 2) {
270		Tcl_AppendResult(interp, "\"", curArg,
271			"\" option requires two following arguments", NULL);
272		return TCL_ERROR;
273	    }
274	    Tk_AddOption(tkwin, argv[srcIndex], argv[srcIndex+1],
275		    TK_INTERACTIVE_PRIO);
276	    srcIndex += 2;
277	    argc -= 2;
278	    break;
279	default: {
280	    char buf[64 + TCL_INTEGER_SPACE];
281
282	    sprintf(buf, "bad argument type %d in Tk_ArgvInfo", infoPtr->type);
283	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
284	    return TCL_ERROR;
285	}
286	}
287    }
288
289    /*
290     * If we broke out of the loop because of an OPT_REST argument, copy the
291     * remaining arguments down.
292     */
293
294  argsDone:
295    while (argc) {
296	argv[dstIndex] = argv[srcIndex];
297	srcIndex++;
298	dstIndex++;
299	argc--;
300    }
301    argv[dstIndex] = NULL;
302    *argcPtr = dstIndex;
303    return TCL_OK;
304
305  missingArg:
306    Tcl_AppendResult(interp, "\"", curArg,
307	    "\" option requires an additional argument", NULL);
308    return TCL_ERROR;
309}
310
311/*
312 *----------------------------------------------------------------------
313 *
314 * PrintUsage --
315 *
316 *	Generate a help string describing command-line options.
317 *
318 * Results:
319 *	The interp's result will be modified to hold a help string describing
320 *	all the options in argTable, plus all those in the default table
321 *	unless TK_ARGV_NO_DEFAULTS is specified in flags.
322 *
323 * Side effects:
324 *	None.
325 *
326 *----------------------------------------------------------------------
327 */
328
329static void
330PrintUsage(
331    Tcl_Interp *interp,		/* Place information in this interp's result
332				 * area. */
333    Tk_ArgvInfo *argTable,	/* Array of command-specific argument
334				 * descriptions. */
335    int flags)			/* If the TK_ARGV_NO_DEFAULTS bit is set in
336				 * this word, then don't generate information
337				 * for default options. */
338{
339    register Tk_ArgvInfo *infoPtr;
340    size_t width, i, numSpaces;
341    char tmp[TCL_DOUBLE_SPACE];
342
343    /*
344     * First, compute the width of the widest option key, so that we can make
345     * everything line up.
346     */
347
348    width = 4;
349    for (i = 0; i < 2; i++) {
350	for (infoPtr = i ? defaultTable : argTable;
351		infoPtr->type != TK_ARGV_END; infoPtr++) {
352	    size_t length;
353	    if (infoPtr->key == NULL) {
354		continue;
355	    }
356	    length = strlen(infoPtr->key);
357	    if (length > width) {
358		width = length;
359	    }
360	}
361    }
362
363    Tcl_AppendResult(interp, "Command-specific options:", NULL);
364    for (i = 0; ; i++) {
365	for (infoPtr = i ? defaultTable : argTable;
366		infoPtr->type != TK_ARGV_END; infoPtr++) {
367	    if ((infoPtr->type == TK_ARGV_HELP) && (infoPtr->key == NULL)) {
368		Tcl_AppendResult(interp, "\n", infoPtr->help, NULL);
369		continue;
370	    }
371	    Tcl_AppendResult(interp, "\n ", infoPtr->key, ":", NULL);
372	    numSpaces = width + 1 - strlen(infoPtr->key);
373	    while (numSpaces-- > 0) {
374		Tcl_AppendResult(interp, " ", NULL);
375	    }
376	    Tcl_AppendResult(interp, infoPtr->help, NULL);
377	    switch (infoPtr->type) {
378	    case TK_ARGV_INT:
379		sprintf(tmp, "%d", *((int *) infoPtr->dst));
380		Tcl_AppendResult(interp, "\n\t\tDefault value: ", tmp, NULL);
381		break;
382	    case TK_ARGV_FLOAT:
383		Tcl_PrintDouble(NULL, *((double *) infoPtr->dst), tmp);
384		Tcl_AppendResult(interp, "\n\t\tDefault value: ", tmp, NULL);
385		break;
386	    case TK_ARGV_STRING: {
387		char *string = *((char **) infoPtr->dst);
388
389		if (string != NULL) {
390		    Tcl_AppendResult(interp, "\n\t\tDefault value: \"", string,
391			    "\"", NULL);
392		}
393		break;
394	    }
395	    default:
396		break;
397	    }
398	}
399
400	if ((flags & TK_ARGV_NO_DEFAULTS) || (i > 0)) {
401	    break;
402	}
403	Tcl_AppendResult(interp, "\nGeneric options for all commands:", NULL);
404    }
405}
406
407/*
408 * Local Variables:
409 * mode: c
410 * c-basic-offset: 4
411 * fill-column: 78
412 * End:
413 */
414