Fix for the (stupid) case of app (always) passing
[tangerine.git] / rom / dos / readargs.c
blob8d8b0e367ad1edf6011a05e3093e166a82bc4f6f
2 /*
3 Copyright © 1995-2001, The AROS Development Team. All rights reserved.
4 $Id$
6 Desc:
7 Lang: english
8 */
9 #include <exec/memory.h>
10 #include <proto/exec.h>
11 #include <dos/rdargs.h>
12 #include <dos/dosextens.h>
14 #include "dos_intern.h"
16 #include <aros/debug.h>
18 #ifdef TEST
19 # include <stdio.h>
20 # include <proto/dos.h>
21 # undef ReadArgs
22 # undef AROS_LH3
23 # define AROS_LH3(t,fn,a1,a2,a3,bt,bn,o,lib) t fn (a1,a2,a3)
24 # undef AROS_LHA
25 # define AROS_LHA(t,n,r) t n
26 # undef AROS_LIBFUNC_INIT
27 # define AROS_LIBFUNC_INIT
28 # undef AROS_LIBBASE_EXT_DECL
29 # define AROS_LIBBASE_EXT_DECL(bt,bn)
30 # undef AROS_LIBFUNC_EXIT
31 # define AROS_LIBFUNC_EXIT
32 #endif
34 /*****************************************************************************
36 NAME */
37 #include <proto/dos.h>
39 AROS_LH3(struct RDArgs *, ReadArgs,
41 /* SYNOPSIS */
42 AROS_LHA(CONST_STRPTR, template, D1),
43 AROS_LHA(IPTR *, array, D2),
44 AROS_LHA(struct RDArgs *, rdargs, D3),
46 /* LOCATION */
47 struct DosLibrary *, DOSBase, 133, Dos)
49 /* FUNCTION
50 Parses the commandline, a given string or Input() and fills
51 an argument array according to the options template given.
52 The array must be initialized to the wanted defaults before
53 each call to ReadArgs(). If the rdargs argument is NULL
54 ReadArgs() tries to parse the commandline and continues
55 on the input channel if it just consists of a single '?',
56 prompting the user for input.
58 INPUTS
59 template - Template string. The template string is given as
60 a number of options separated by ',' and modified
61 by '/' modifiers, e.g. 'NAME,WIDTH/N,HEIGHT/N'
62 means get a name string and two numbers (width and
63 height). The possible modifiers are:
64 /S Option is a switch. It may be either set or
65 left out.
66 /T Option is a boolean value. Requires an argument
67 which may be "ON", "YES" (setting the respective
68 argument to 1), "OFF" or "NO" (setting the
69 respective argument to 0).
70 /N Option is a number. Strings are not allowed.
71 If the option is optional, a pointer to the
72 actual number is returned. This is how you know
73 if it was really given.
74 /A Argument is required. If it is left out ReadArgs()
75 fails.
76 /K The keyword must be given when filling the option.
77 Normally it's skipped.
78 /M Multiple strings. The result is returned as a string
79 pointer array terminated with NULL. /M eats all strings
80 that don't fit into any other option. If there are
81 unfilled /A arguments after parsing they steal strings
82 from /M. This makes it possible to e.g. write a COPY
83 template like 'FROM/A/M,TO/A'. There may be only one
84 /M option in a template.
85 /F Eats the rest of the line even if there are option
86 keywords in it.
87 array - Array to be filled with the result values. The array must
88 be intialized to the default values before calling
89 ReadArgs().
90 rdargs - An optional RDArgs structure determinating the type of
91 input to process.
93 RESULT
94 A handle for the memory allocated by ReadArgs(). Must be freed
95 with FreeArgs() later.
97 SEE ALSO
98 FreeArgs(), Input()
100 *****************************************************************************/
102 AROS_LIBFUNC_INIT
103 AROS_LIBBASE_EXT_DECL(struct DosLibrary *, DOSBase)
105 /* Allocated resources */
106 struct DAList *dalist = NULL;
107 UBYTE *flags = NULL;
108 STRPTR strbuf = NULL, iline = NULL;
109 STRPTR *multvec = NULL, *argbuf = NULL;
110 ULONG multnum = 0, multmax = 0;
112 /* Some variables */
113 CONST_STRPTR cs1;
114 STRPTR s1, s2, *newmult;
115 ULONG arg, numargs, nextarg;
116 LONG it, item, chars, value;
117 struct CSource lcs, *cs;
119 ASSERT_VALID_PTR(template);
120 ASSERT_VALID_PTR(array);
121 ASSERT_VALID_PTR_OR_NULL(rdargs);
123 /* Get pointer to process structure. */
124 struct Process *me = (struct Process *) FindTask(NULL);
126 /* Error recovery. C has no exceptions. This is a simple replacement. */
127 LONG error;
129 #undef ERROR
130 #define ERROR(a) { error=a; goto end; }
132 /* Template options */
133 #define REQUIRED 0x80 /* /A */
134 #define KEYWORD 0x40 /* /K */
135 #define MULTIPLE 0x20 /* /M */
136 #define TYPEMASK 0x07
137 #define NORMAL 0x00 /* No option */
138 #define SWITCH 0x01 /* /S, implies /K */
139 #define TOGGLE 0x02 /* /T, implies /K */
140 #define NUMERIC 0x03 /* /N */
141 #define REST 0x04 /* /F */
143 /* Flags for each possible character. */
144 static const UBYTE argflags[] =
146 REQUIRED, 0, 0, 0, 0, REST, 0, 0, 0, 0, KEYWORD, 0, MULTIPLE,
147 NUMERIC, 0, 0, 0, 0, SWITCH | KEYWORD, TOGGLE | KEYWORD, 0, 0,
148 0, 0, 0, 0
151 /* Allocate readargs structure (and private internal one) */
152 if (!rdargs)
154 rdargs = (struct RDArgs *) AllocVec(sizeof(struct RDArgs),
155 MEMF_ANY | MEMF_CLEAR);
156 if (rdargs)
158 rdargs->RDA_Flags |= RDAF_ALLOCATED_BY_READARGS;
162 dalist = (struct DAList *) AllocVec(sizeof(struct DAList),
163 MEMF_ANY | MEMF_CLEAR);
165 if (rdargs == NULL || dalist == NULL)
167 ERROR(ERROR_NO_FREE_STORE);
170 /* Init character source. */
171 if (rdargs->RDA_Source.CS_Buffer)
173 cs = &rdargs->RDA_Source;
175 else
177 lcs.CS_Buffer = (me->pr_Arguments ? me->pr_Arguments : (UBYTE *) "");
179 cs1 = lcs.CS_Buffer;
181 while (*cs1++)
185 lcs.CS_Length = (IPTR) cs1 - (IPTR) lcs.CS_Buffer - 1;
186 lcs.CS_CurChr = 0;
188 cs = &lcs;
191 /* Check for optional reprompting */
192 if (!(rdargs->RDA_Flags & RDAF_NOPROMPT))
194 /* Check commandline for a single '?' */
195 cs1 = cs->CS_Buffer;
197 /* Skip leading whitespace */
198 while (*cs1 == ' ' || *cs1 == '\t')
200 cs1++;
203 /* Check for '?' */
204 if (*cs1++ == '?')
206 /* Skip whitespace */
207 while (*cs1 == ' ' || *cs1 == '\t')
209 cs1++;
212 /* Check for EOL */
213 if (*cs1 == '\n' || !*cs1)
215 /* Only a single '?' on the commandline. */
216 BPTR input = Input();
217 BPTR output = Output();
218 ULONG isize = 0, ibuf = 0;
219 LONG c;
221 /* Prompt for more input */
223 /* printf ("Only ? found\n");
224 printf ("rdargs=%p\n", rdargs);
225 if (rdargs)
226 printf ("rdargs->RDA_ExtHelp=%p\n", rdargs->RDA_ExtHelp); */
228 if (rdargs->RDA_ExtHelp != NULL)
230 if (FPuts(output, rdargs->RDA_ExtHelp))
231 ERROR(me->pr_Result2);
234 if (FPuts(output, template) || FPuts(output, ": "))
236 ERROR(me->pr_Result2);
239 if (!Flush(output))
241 ERROR(me->pr_Result2);
244 /* Read a line in. */
245 for (;;)
247 if (isize >= ibuf)
249 /* Buffer too small. Get a new one. */
250 STRPTR newiline;
252 ibuf += 256;
254 newiline = (STRPTR) AllocVec(ibuf, MEMF_ANY);
256 if (newiline == NULL)
258 ERROR(ERROR_NO_FREE_STORE);
261 CopyMemQuick((ULONG *) iline,
262 (ULONG *) newiline, isize);
264 FreeVec(iline);
266 iline = newiline;
269 /* Read character */
270 c = FGetC(input);
272 /* Check and write it. */
273 if (c == EOF && me->pr_Result2)
275 ERROR(me->pr_Result2);
278 if (c == EOF || c == '\n' || !c)
280 /* stegerg: added this. Otherwise try "list ?" then enter only "l" + RETURN
281 * and you will get a broken wall in FreeMem reported. This happens in
282 * FreeArgs() during the FreeVec() of the StrBuf. Appending '\n' here fixes
283 * this, but maybe the real bug is somewhere else. */
285 iline[isize++] = '\n';
287 /* end stegerg: */
289 break;
292 iline[isize++] = c;
295 /* Prepare input source for new line. */
296 cs->CS_Buffer = iline;
297 cs->CS_Length = isize;
303 * Get enough space for string buffer.
304 * It's always smaller than the size of the input line+1.
307 strbuf = (STRPTR) AllocVec(cs->CS_Length + 1, MEMF_ANY);
309 if (strbuf == NULL)
311 ERROR(ERROR_NO_FREE_STORE);
314 /* Count the number of items in the template (number of ','+1). */
315 numargs = 1;
316 cs1 = template;
318 while (*cs1)
320 if (*cs1++ == ',')
322 numargs++;
326 /* Use this count to get space for temporary flag array and result
327 * buffer. */
328 flags = (UBYTE *) AllocVec(numargs + 1, MEMF_CLEAR);
330 argbuf = (STRPTR *) AllocVec((numargs + 1) * sizeof(STRPTR), MEMF_CLEAR);
332 if (flags == NULL || argbuf == NULL)
334 ERROR(ERROR_NO_FREE_STORE);
337 /* Fill the flag array. */
338 cs1 = template;
339 s2 = flags;
341 while (*cs1)
343 /* A ',' means: goto next item. */
344 if (*cs1 == ',')
346 s2++;
349 /* In case of a '/' use the next character as option. */
350 if (*cs1++ == '/')
352 *s2 |= argflags[*cs1 - 'A'];
356 /* Add a dummy so that the whole line is processed. */
357 *++s2 = MULTIPLE;
360 * Now process commandline for the first time:
361 * Go from left to right and fill all items that need filling.
362 * If an item is given as 'OPTION=VALUE' or 'OPTION VALUE' fill
363 * it out of turn.
365 s1 = strbuf;
367 for (arg = 0; arg <= numargs; arg = nextarg)
369 nextarg = arg + 1;
371 /* Skip /K options and options that are already done. */
372 if (flags[arg] & KEYWORD || argbuf[arg] != NULL)
374 continue;
377 #if 0 /* stegerg: if so a template of CLOSE/S,QUICK/S,COMMAND/F would
378 not work correctly if command line for example is
379 "CLOSE QUICK" it would all end up being eaten by COMMAND/F
380 argument */
382 /* If the current option is of type /F do not look for keywords */
383 if ((flags[arg] & TYPEMASK) != REST)
384 #endif
387 /* Get item. Quoted items are no keywords. */
388 it = ReadItem(s1, ~0ul / 2, cs);
390 if (it == ITEM_UNQUOTED)
392 /* Not quoted. Check if it's a keyword. */
393 item = FindArg(template, s1);
395 if (item >= 0 && item < numargs && argbuf[item] == NULL)
398 * It's a keyword. Fill it and retry the current option
399 * at the next turn
401 nextarg = arg;
402 arg = item;
404 /* /S /T may not be given as 'OPTION=VALUE'. */
405 if ((flags[item] & TYPEMASK) != SWITCH
406 && (flags[item] & TYPEMASK) != TOGGLE)
408 /* Get value. */
409 it = ReadItem(s1, ~0ul / 2, cs);
411 if (it == ITEM_EQUAL)
413 it = ReadItem(s1, ~0ul / 2, cs);
419 /* Check returncode of ReadItem(). */
420 if (it == ITEM_EQUAL)
422 ERROR(ERROR_BAD_TEMPLATE);
424 else if (it == ITEM_ERROR)
426 ERROR(me->pr_Result2);
428 else if (it == ITEM_NOTHING)
430 break;
434 /* /F takes all the rest */
435 /* TODO: Take care of quoted strings(?) */
436 if ((flags[arg] & TYPEMASK) == REST)
438 #if 0
439 /* Skip leading whitespace */
440 while (cs->CS_CurChr < cs->CS_Length
441 && (cs->CS_Buffer[cs->CS_CurChr] == ' '
442 || cs->CS_Buffer[cs->CS_CurChr] == '\t'))
444 cs->CS_CurChr++;
446 #endif
447 argbuf[arg] = s1;
449 /* Copy part already read above by ReadItem() */
450 while (*s1)
452 s1++;
455 /* Find the last non-whitespace character */
456 s2 = s1 - 1;
458 while (cs->CS_CurChr < cs->CS_Length
459 && cs->CS_Buffer[cs->CS_CurChr]
460 && cs->CS_Buffer[cs->CS_CurChr] != '\n')
462 if (cs->CS_Buffer[cs->CS_CurChr] != ' '
463 && cs->CS_Buffer[cs->CS_CurChr] != '\t')
465 s2 = s1;
468 /* Copy string by the way. */
469 *s1++ = cs->CS_Buffer[cs->CS_CurChr++];
472 /* Add terminator (1 after the character found). */
473 s2[1] = 0;
474 it = ITEM_NOTHING;
475 break;
478 if (flags[arg] & MULTIPLE)
480 /* All /M arguments are stored in a buffer. */
481 if (multnum >= multmax)
483 /* Buffer too small. Get a new one. */
484 multmax += 16;
486 newmult = (STRPTR *) AllocVec(multmax * sizeof(char *),
487 MEMF_ANY);
488 if (newmult == NULL)
490 ERROR(ERROR_NO_FREE_STORE);
493 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
494 multnum * sizeof(char *));
496 FreeVec(multvec);
498 multvec = newmult;
501 /* Put string into the buffer. */
502 multvec[multnum++] = s1;
504 while (*s1++)
508 /* /M takes more than one argument, so retry. */
509 nextarg = arg;
511 else if ((flags[arg] & TYPEMASK) == SWITCH
512 || (flags[arg] & TYPEMASK) == TOGGLE)
514 /* /S or /T just set a flag */
515 argbuf[arg] = (char *) ~0;
517 else /* NORMAL || NUMERIC */
519 /* Put argument into argument buffer. */
520 argbuf[arg] = s1;
522 while (*s1++)
528 /* Unfilled /A options steal Arguments from /M */
529 for (arg = numargs; arg-- > 0;)
531 if (flags[arg] & REQUIRED && argbuf[arg] == NULL
532 && !(flags[arg] & MULTIPLE))
534 if (flags[arg] & KEYWORD)
536 /* /K/A argument, which inisits on keyword
537 * being used, cannot be satisfied */
539 ERROR(ERROR_TOO_MANY_ARGS); /* yes, strange error number,
540 * but it translates to "wrong
541 * number of arguments" */
545 if (!multnum)
547 /* No arguments left? Oh dear! */
548 ERROR(ERROR_REQUIRED_ARG_MISSING);
551 argbuf[arg] = multvec[--multnum];
555 /* Put the rest of /M where it belongs */
556 for (arg = 0; arg < numargs; arg++)
558 if (flags[arg] & MULTIPLE)
560 if (flags[arg] & REQUIRED && !multnum)
562 ERROR(ERROR_REQUIRED_ARG_MISSING);
565 if (multnum)
567 /* NULL terminate it. */
568 if (multnum >= multmax)
570 multmax += 16;
572 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
573 MEMF_ANY);
575 if (newmult == NULL)
577 ERROR(ERROR_NO_FREE_STORE);
580 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
581 multnum * sizeof(char *));
583 FreeVec(multvec);
585 multvec = newmult;
588 multvec[multnum++] = NULL;
589 argbuf[arg] = (STRPTR) multvec;
591 else
593 /* Shouldn't be necessary, but some buggy software relies on this */
594 argbuf[arg] = NULL;
597 break;
601 /* There are some arguments left? Return error. */
602 if (multnum && arg == numargs)
604 ERROR(ERROR_TOO_MANY_ARGS);
608 * The commandline is processed now. Put the results in the result array.
609 * Convert /N arguments by the way.
611 for (arg = 0; arg < numargs; arg++)
613 /* Just for the arguments given. */
614 if (argbuf[arg] != NULL)
616 if (flags[arg] & MULTIPLE)
618 array[arg] = (IPTR) argbuf[arg];
620 if ((flags[arg] & TYPEMASK) == NUMERIC)
622 STRPTR *p;
623 LONG *q;
625 if (multnum * 2 > multmax)
627 multmax = multnum * 2;
628 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
629 MEMF_ANY);
631 if (newmult == NULL)
633 ERROR(ERROR_NO_FREE_STORE);
636 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
637 multnum * sizeof(char *));
639 FreeVec(multvec);
641 multvec = newmult;
644 array[arg] = (IPTR) multvec;
645 p = multvec;
646 q = (LONG *) (multvec + multnum);
648 while (*p)
650 /* Convert /N argument. */
651 chars = StrToLong(*p, q);
653 if (chars <= 0 || (*p)[chars])
655 /* Conversion failed. */
656 ERROR(ERROR_BAD_NUMBER);
659 /* Put the result where it belongs. */
660 *p = (STRPTR) q;
661 p++;
662 q++;
666 else
668 switch (flags[arg] & TYPEMASK)
670 case NORMAL:
671 case REST:
672 case SWITCH:
673 /* Simple arguments are just copied. */
674 array[arg] = (IPTR) argbuf[arg];
675 break;
677 case TOGGLE:
678 /* /T logically inverts the argument. */
679 array[arg] = array[arg] ? 0 : ~0;
680 break;
682 case NUMERIC:
683 /* Convert /N argument. */
684 chars = StrToLong(argbuf[arg], &value);
686 if (chars <= 0 || argbuf[arg][chars])
688 /* Conversion failed. */
689 ERROR(ERROR_BAD_NUMBER);
692 /* Put the result where it belongs. */
693 #if 0
694 if (flags[arg] & REQUIRED)
695 /* Required argument. Return number. */
696 array[arg] = value;
697 else
698 #endif
700 /* Abuse the argbuf buffer. It's not needed anymore. */
701 argbuf[arg] = (STRPTR) value;
702 array[arg] = (IPTR) & argbuf[arg];
704 break;
708 else
710 if (flags[arg] & MULTIPLE)
712 /* Shouldn't be necessary, but some buggy software relies on this.
713 * IBrowse's URL field isn't set to zero.
715 array[arg] = (IPTR)NULL;
720 /* All OK. */
721 error = 0;
722 end:
723 /* Cleanup and return. */
724 FreeVec(iline);
725 FreeVec(flags);
727 if (error)
729 /* ReadArgs() failed. Clean everything up. */
730 if (rdargs)
732 if (rdargs->RDA_Flags & RDAF_ALLOCATED_BY_READARGS)
734 FreeVec(rdargs);
738 FreeVec(dalist);
739 FreeVec(argbuf);
740 FreeVec(strbuf);
741 FreeVec(multvec);
743 me->pr_Result2 = error;
745 return NULL;
747 else
749 /* All went well. Prepare result and return. */
750 rdargs->RDA_DAList = (IPTR) dalist;
751 dalist->ArgBuf = argbuf;
752 dalist->StrBuf = strbuf;
753 dalist->MultVec = multvec;
754 return rdargs;
756 AROS_LIBFUNC_EXIT
757 } /* ReadArgs */
759 #ifdef TEST
760 # include <dos/dos.h>
761 # include <dos/rdargs.h>
762 # include <utility/tagitem.h>
764 # include <proto/dos.h>
766 char cmlargs[] = "TEST/A";
768 char usage[] =
769 "This is exthelp for test\n"
770 "Enter something";
772 #define CML_TEST 0
773 #define CML_END 1
775 LONG cmlvec[CML_END];
778 main(int argc, char **argv)
780 struct RDArgs *rdargs;
782 if ((rdargs = AllocDosObject(DOS_RDARGS, NULL)))
784 rdargs->RDA_ExtHelp = usage; /* FIX: why doesn't this work? */
786 if (!(ReadArgs(cmlargs, cmlvec, rdargs)))
788 PrintFault(IoErr(), "AROS boot");
789 FreeDosObject(DOS_RDARGS, rdargs);
790 exit(RETURN_FAIL);
793 else
795 PrintFault(ERROR_NO_FREE_STORE, "AROS boot");
796 exit(RETURN_FAIL);
799 FreeArgs(rdargs);
800 FreeDosObject(DOS_RDARGS, rdargs);
802 return 0;
803 } /* main */
805 #endif /* TEST */