some fixes to accented characters
[tangerine.git] / rom / dos / readargs.c
blob3464842529f1e5ca58e99269c451eea85f39be8f
2 /*
3 Copyright © 1995-2008, 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_LIBFUNC_EXIT
29 # define AROS_LIBFUNC_EXIT
30 #endif
32 /*****************************************************************************
34 NAME */
35 #include <proto/dos.h>
37 AROS_LH3(struct RDArgs *, ReadArgs,
39 /* SYNOPSIS */
40 AROS_LHA(CONST_STRPTR, template, D1),
41 AROS_LHA(IPTR *, array, D2),
42 AROS_LHA(struct RDArgs *, rdargs, D3),
44 /* LOCATION */
45 struct DosLibrary *, DOSBase, 133, Dos)
47 /* FUNCTION
48 Parses the commandline, a given string or Input() and fills
49 an argument array according to the options template given.
50 The array must be initialized to the wanted defaults before
51 each call to ReadArgs(). If the rdargs argument is NULL
52 ReadArgs() tries to parse the commandline and continues
53 on the input channel if it just consists of a single '?',
54 prompting the user for input.
56 INPUTS
57 template - Template string. The template string is given as
58 a number of options separated by ',' and modified
59 by '/' modifiers, e.g. 'NAME,WIDTH/N,HEIGHT/N'
60 means get a name string and two numbers (width and
61 height). The possible modifiers are:
62 /S Option is a switch. It may be either set or
63 left out.
64 /T Option is a boolean value. Requires an argument
65 which may be "ON", "YES" (setting the respective
66 argument to 1), "OFF" or "NO" (setting the
67 respective argument to 0).
68 /N Option is a number. Strings are not allowed.
69 If the option is optional, a pointer to the
70 actual number is returned. This is how you know
71 if it was really given. The number is always of type
72 LONG.
73 /A Argument is required. If it is left out ReadArgs()
74 fails.
75 /K The keyword must be given when filling the option.
76 Normally it's skipped.
77 /M Multiple strings or, when used in combination with /N,
78 numbers. The result is returned as an array of pointers
79 to strings or LONGs, and is terminated with NULL. /M
80 eats all strings that don't fit into any other option.
81 If there are unfilled /A arguments after parsing they
82 steal strings from /M. This makes it possible to, for
83 example, write a Copy command template like
84 'FROM/A/M,TO/A'. There may be only one /M option in a
85 template.
86 /F Eats the rest of the line even if there are option
87 keywords in it.
88 array - Array to be filled with the result values. The array must
89 be intialized to the default values before calling
90 ReadArgs().
91 rdargs - An optional RDArgs structure determinating the type of
92 input to process.
94 RESULT
95 A handle for the memory allocated by ReadArgs(). Must be freed
96 with FreeArgs() later.
98 SEE ALSO
99 FreeArgs(), Input()
101 *****************************************************************************/
103 AROS_LIBFUNC_INIT
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 CONST_STRPTR numstr;
111 ULONG multnum = 0, multmax = 0;
113 /* Some variables */
114 CONST_STRPTR cs1;
115 STRPTR s1, s2, *newmult;
116 ULONG arg, numargs, nextarg;
117 LONG it, item, chars;
118 struct CSource lcs, *cs;
120 ASSERT_VALID_PTR(template);
121 ASSERT_VALID_PTR(array);
122 ASSERT_VALID_PTR_OR_NULL(rdargs);
124 /* Get pointer to process structure. */
125 struct Process *me = (struct Process *) FindTask(NULL);
127 /* Error recovery. C has no exceptions. This is a simple replacement. */
128 LONG error;
130 #undef ERROR
131 #define ERROR(a) { error=a; goto end; }
133 /* Template options */
134 #define REQUIRED 0x80 /* /A */
135 #define KEYWORD 0x40 /* /K */
136 #define MULTIPLE 0x20 /* /M */
137 #define TYPEMASK 0x07
138 #define NORMAL 0x00 /* No option */
139 #define SWITCH 0x01 /* /S, implies /K */
140 #define TOGGLE 0x02 /* /T, implies /K */
141 #define NUMERIC 0x03 /* /N */
142 #define REST 0x04 /* /F */
144 /* Flags for each possible character. */
145 static const UBYTE argflags[] =
147 REQUIRED, 0, 0, 0, 0, REST, 0, 0, 0, 0, KEYWORD, 0, MULTIPLE,
148 NUMERIC, 0, 0, 0, 0, SWITCH | KEYWORD, TOGGLE | KEYWORD, 0, 0,
149 0, 0, 0, 0
152 /* Allocate readargs structure (and private internal one) */
153 if (!rdargs)
155 rdargs = (struct RDArgs *) AllocVec(sizeof(struct RDArgs),
156 MEMF_ANY | MEMF_CLEAR);
157 if (rdargs)
159 rdargs->RDA_Flags |= RDAF_ALLOCATED_BY_READARGS;
163 dalist = (struct DAList *) AllocVec(sizeof(struct DAList),
164 MEMF_ANY | MEMF_CLEAR);
166 if (rdargs == NULL || dalist == NULL)
168 ERROR(ERROR_NO_FREE_STORE);
171 /* Init character source. */
172 if (rdargs->RDA_Source.CS_Buffer)
174 cs = &rdargs->RDA_Source;
176 else
178 lcs.CS_Buffer = (me->pr_Arguments ? me->pr_Arguments : (UBYTE *) "");
180 cs1 = lcs.CS_Buffer;
182 while (*cs1++)
186 lcs.CS_Length = (IPTR) cs1 - (IPTR) lcs.CS_Buffer - 1;
187 lcs.CS_CurChr = 0;
189 cs = &lcs;
192 /* Check for optional reprompting */
193 if (!(rdargs->RDA_Flags & RDAF_NOPROMPT))
195 /* Check commandline for a single '?' */
196 cs1 = cs->CS_Buffer;
198 /* Skip leading whitespace */
199 while (*cs1 == ' ' || *cs1 == '\t')
201 cs1++;
204 /* Check for '?' */
205 if (*cs1++ == '?')
207 /* Skip whitespace */
208 while (*cs1 == ' ' || *cs1 == '\t')
210 cs1++;
213 /* Check for EOL */
214 if (*cs1 == '\n' || !*cs1)
216 /* Only a single '?' on the commandline. */
217 BPTR input = Input();
218 BPTR output = Output();
219 ULONG isize = 0, ibuf = 0;
220 LONG c;
221 ULONG helpdisplayed = FALSE;
223 /* Prompt for more input */
225 /* printf ("Only ? found\n");
226 printf ("rdargs=%p\n", rdargs);
227 if (rdargs)
228 printf ("rdargs->RDA_ExtHelp=%p\n", rdargs->RDA_ExtHelp); */
230 if (FPuts(output, template) || FPuts(output, ": "))
232 ERROR(me->pr_Result2);
235 if (!Flush(output))
237 ERROR(me->pr_Result2);
240 do {
241 /* Read a line in. */
242 for (;;)
244 if (isize >= ibuf)
246 /* Buffer too small. Get a new one. */
247 STRPTR newiline;
249 ibuf += 256;
251 newiline = (STRPTR) AllocVec(ibuf, MEMF_ANY);
253 if (newiline == NULL)
255 ERROR(ERROR_NO_FREE_STORE);
258 if (iline != NULL)
259 CopyMemQuick(iline, newiline, isize);
261 FreeVec(iline);
263 iline = newiline;
266 /* Read character */
267 c = FGetC(input);
269 /* Check and write it. */
270 if (c == EOF && me->pr_Result2)
272 ERROR(me->pr_Result2);
275 if (c == EOF || c == '\n' || c == '\0')
277 /* stegerg: added this. Otherwise try "list ?" then enter only "l" + RETURN
278 * and you will get a broken wall in FreeMem reported. This happens in
279 * FreeArgs() during the FreeVec() of the StrBuf. Appending '\n' here fixes
280 * this, but maybe the real bug is somewhere else. */
282 iline[isize++] = '\n';
284 /* end stegerg: */
286 break;
289 iline[isize++] = c;
292 /* if user entered single ? again or some string ending
293 with space and ? either display template again or
294 extended help if it's available */
295 if ((isize == 2 || (isize > 2 && iline[isize-3] == ' '))
296 && iline[isize-2] == '?' )
298 helpdisplayed = TRUE;
299 isize = 0;
300 if(rdargs->RDA_ExtHelp != NULL)
302 if (FPuts(output, rdargs->RDA_ExtHelp) || FPuts(output, ": "))
303 ERROR(me->pr_Result2);
305 else if (FPuts(output, template) || FPuts(output, ": "))
307 ERROR(me->pr_Result2);
310 if (!Flush(output))
312 ERROR(me->pr_Result2);
315 else
316 helpdisplayed = FALSE;
318 while(helpdisplayed);
320 /* Prepare input source for new line. */
321 cs->CS_Buffer = iline;
322 cs->CS_Length = isize;
328 * Get enough space for string buffer.
329 * It's always smaller than the size of the input line+1.
332 strbuf = (STRPTR) AllocVec(cs->CS_Length + 1, MEMF_ANY);
334 if (strbuf == NULL)
336 ERROR(ERROR_NO_FREE_STORE);
339 /* Count the number of items in the template (number of ','+1). */
340 numargs = 1;
341 cs1 = template;
343 while (*cs1)
345 if (*cs1++ == ',')
347 numargs++;
351 /* Use this count to get space for temporary flag array and result
352 * buffer. */
353 flags = (UBYTE *) AllocVec(numargs + 1, MEMF_CLEAR);
355 argbuf = (STRPTR *) AllocVec((numargs + 1) * sizeof(STRPTR), MEMF_CLEAR);
357 if (flags == NULL || argbuf == NULL)
359 ERROR(ERROR_NO_FREE_STORE);
362 /* Fill the flag array. */
363 cs1 = template;
364 s2 = flags;
366 while (*cs1)
368 /* A ',' means: goto next item. */
369 if (*cs1 == ',')
371 s2++;
374 /* In case of a '/' use the next character as option. */
375 if (*cs1++ == '/')
377 *s2 |= argflags[*cs1 - 'A'];
381 /* Add a dummy so that the whole line is processed. */
382 *++s2 = MULTIPLE;
385 * Now process commandline for the first time:
386 * Go from left to right and fill all items that need filling.
387 * If an item is given as 'OPTION=VALUE' or 'OPTION VALUE' fill
388 * it out of turn.
390 s1 = strbuf;
392 for (arg = 0; arg <= numargs; arg = nextarg)
394 nextarg = arg + 1;
396 /* Skip /K options and options that are already done. */
397 if (flags[arg] & KEYWORD || argbuf[arg] != NULL)
399 continue;
402 #if 0 /* stegerg: if so a template of CLOSE/S,QUICK/S,COMMAND/F would
403 not work correctly if command line for example is
404 "CLOSE QUICK" it would all end up being eaten by COMMAND/F
405 argument */
407 /* If the current option is of type /F do not look for keywords */
408 if ((flags[arg] & TYPEMASK) != REST)
409 #endif
412 /* Get item. Quoted items are never keywords. */
413 it = ReadItem(s1, ~0ul / 2, cs);
415 if (it == ITEM_UNQUOTED)
417 /* Not quoted. Check if it's a keyword. */
418 item = FindArg(template, s1);
420 if (item >= 0 && item < numargs && argbuf[item] == NULL)
423 * It's a keyword. Fill it and retry the current option
424 * at the next turn
426 nextarg = arg;
427 arg = item;
429 /* /S /T may not be given as 'OPTION=VALUE'. */
430 if ((flags[item] & TYPEMASK) != SWITCH
431 && (flags[item] & TYPEMASK) != TOGGLE)
433 /* Get value. */
434 it = ReadItem(s1, ~0ul / 2, cs);
436 if (it == ITEM_EQUAL)
438 it = ReadItem(s1, ~0ul / 2, cs);
444 /* Check returncode of ReadItem(). */
445 if (it == ITEM_EQUAL)
447 ERROR(ERROR_BAD_TEMPLATE);
449 else if (it == ITEM_ERROR)
451 ERROR(me->pr_Result2);
453 else if (it == ITEM_NOTHING)
455 break;
459 /* /F takes all the rest */
460 /* TODO: Take care of quoted strings(?) */
461 if ((flags[arg] & TYPEMASK) == REST)
463 #if 0
464 /* Skip leading whitespace */
465 while (cs->CS_CurChr < cs->CS_Length
466 && (cs->CS_Buffer[cs->CS_CurChr] == ' '
467 || cs->CS_Buffer[cs->CS_CurChr] == '\t'))
469 cs->CS_CurChr++;
471 #endif
472 argbuf[arg] = s1;
474 /* Copy part already read above by ReadItem() */
475 while (*s1)
477 s1++;
480 /* Find the last non-whitespace character */
481 s2 = s1 - 1;
483 while (cs->CS_CurChr < cs->CS_Length
484 && cs->CS_Buffer[cs->CS_CurChr]
485 && cs->CS_Buffer[cs->CS_CurChr] != '\n')
487 if (cs->CS_Buffer[cs->CS_CurChr] != ' '
488 && cs->CS_Buffer[cs->CS_CurChr] != '\t')
490 s2 = s1;
493 /* Copy string by the way. */
494 *s1++ = cs->CS_Buffer[cs->CS_CurChr++];
497 /* Add terminator (1 after the character found). */
498 s2[1] = 0;
499 it = ITEM_NOTHING;
500 break;
503 if (flags[arg] & MULTIPLE)
505 /* All /M arguments are stored in a buffer. */
506 if (multnum >= multmax)
508 /* Buffer too small. Get a new one. */
509 multmax += 16;
511 newmult = (STRPTR *) AllocVec(multmax * sizeof(char *),
512 MEMF_ANY);
513 if (newmult == NULL)
515 ERROR(ERROR_NO_FREE_STORE);
518 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
519 multnum * sizeof(char *));
521 FreeVec(multvec);
523 multvec = newmult;
526 /* Put string into the buffer. */
527 multvec[multnum++] = s1;
529 while (*s1++)
533 /* /M takes more than one argument, so retry. */
534 nextarg = arg;
536 else if ((flags[arg] & TYPEMASK) == SWITCH
537 || (flags[arg] & TYPEMASK) == TOGGLE)
539 /* /S or /T just set a flag */
540 argbuf[arg] = (char *) ~0;
542 else /* NORMAL || NUMERIC */
544 /* Put argument into argument buffer. */
545 argbuf[arg] = s1;
547 while (*s1++)
553 /* Unfilled /A options steal Arguments from /M */
554 for (arg = numargs; arg-- > 0;)
556 if (flags[arg] & REQUIRED && argbuf[arg] == NULL
557 && !(flags[arg] & MULTIPLE))
559 if (flags[arg] & KEYWORD)
561 /* /K/A argument, which inisits on keyword
562 * being used, cannot be satisfied */
564 ERROR(ERROR_TOO_MANY_ARGS); /* yes, strange error number,
565 * but it translates to "wrong
566 * number of arguments" */
570 if (!multnum)
572 /* No arguments left? Oh dear! */
573 ERROR(ERROR_REQUIRED_ARG_MISSING);
576 argbuf[arg] = multvec[--multnum];
580 /* Put the rest of /M where it belongs */
581 for (arg = 0; arg < numargs; arg++)
583 if (flags[arg] & MULTIPLE)
585 if (flags[arg] & REQUIRED && !multnum)
587 ERROR(ERROR_REQUIRED_ARG_MISSING);
590 if (multnum)
592 /* NULL terminate it. */
593 if (multnum >= multmax)
595 multmax += 16;
597 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
598 MEMF_ANY);
600 if (newmult == NULL)
602 ERROR(ERROR_NO_FREE_STORE);
605 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
606 multnum * sizeof(char *));
608 FreeVec(multvec);
610 multvec = newmult;
613 multvec[multnum++] = NULL;
614 argbuf[arg] = (STRPTR) multvec;
616 else
618 /* Shouldn't be necessary, but some buggy software relies on this */
619 argbuf[arg] = NULL;
622 break;
626 /* There are some arguments left? Return error. */
627 if (multnum && arg == numargs)
629 ERROR(ERROR_TOO_MANY_ARGS);
633 * The commandline is processed now. Put the results in the result array.
634 * Convert /N arguments by the way.
636 for (arg = 0; arg < numargs; arg++)
638 /* Just for the arguments given. */
639 if (argbuf[arg] != NULL)
641 if (flags[arg] & MULTIPLE)
643 array[arg] = (IPTR) argbuf[arg];
645 if ((flags[arg] & TYPEMASK) == NUMERIC)
647 STRPTR *p;
648 LONG *q;
650 if (multnum * 2 > multmax)
652 multmax = multnum * 2;
653 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
654 MEMF_ANY);
656 if (newmult == NULL)
658 ERROR(ERROR_NO_FREE_STORE);
661 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
662 multnum * sizeof(char *));
664 FreeVec(multvec);
666 multvec = newmult;
669 array[arg] = (IPTR) multvec;
670 p = multvec;
671 q = (LONG *) (multvec + multnum);
673 while (*p)
675 /* Convert /N argument. */
676 chars = StrToLong(*p, q);
678 if (chars <= 0 || (*p)[chars])
680 /* Conversion failed. */
681 ERROR(ERROR_BAD_NUMBER);
684 /* Put the result where it belongs. */
685 *p = (STRPTR) q;
686 p++;
687 q += sizeof(IPTR) / sizeof(LONG);
691 else
693 switch (flags[arg] & TYPEMASK)
695 case NORMAL:
696 case REST:
697 case SWITCH:
698 /* Simple arguments are just copied. */
699 array[arg] = (IPTR) argbuf[arg];
700 break;
702 case TOGGLE:
703 /* /T logically inverts the argument. */
704 array[arg] = array[arg] ? 0 : ~0;
705 break;
707 case NUMERIC:
708 /* Convert /N argument. */
709 /* Abuse the argbuf buffer. It's not needed anymore. */
710 numstr = (CONST_STRPTR)argbuf[arg];
711 chars = StrToLong(numstr, (LONG *)&argbuf[arg]);
713 if (chars <= 0 || numstr[chars] != '\0')
715 /* Conversion failed. */
716 ERROR(ERROR_BAD_NUMBER);
719 /* Put the result where it belongs. */
720 array[arg] = (IPTR) &argbuf[arg];
721 break;
725 else
727 if (flags[arg] & MULTIPLE)
729 /* Shouldn't be necessary, but some buggy software relies on this.
730 * IBrowse's URL field isn't set to zero.
732 array[arg] = (IPTR)NULL;
737 /* All OK. */
738 error = 0;
739 end:
740 /* Cleanup and return. */
741 FreeVec(iline);
742 FreeVec(flags);
744 if (error)
746 /* ReadArgs() failed. Clean everything up. */
747 if (rdargs)
749 if (rdargs->RDA_Flags & RDAF_ALLOCATED_BY_READARGS)
751 FreeVec(rdargs);
755 FreeVec(dalist);
756 FreeVec(argbuf);
757 FreeVec(strbuf);
758 FreeVec(multvec);
760 me->pr_Result2 = error;
762 return NULL;
764 else
766 /* All went well. Prepare result and return. */
767 rdargs->RDA_DAList = (IPTR) dalist;
768 dalist->ArgBuf = argbuf;
769 dalist->StrBuf = strbuf;
770 dalist->MultVec = multvec;
771 return rdargs;
773 AROS_LIBFUNC_EXIT
774 } /* ReadArgs */
776 #ifdef TEST
777 # include <dos/dos.h>
778 # include <dos/rdargs.h>
779 # include <utility/tagitem.h>
781 # include <proto/dos.h>
783 char cmlargs[] = "TEST/A";
785 char usage[] =
786 "This is exthelp for test\n"
787 "Enter something";
789 #define CML_TEST 0
790 #define CML_END 1
792 LONG cmlvec[CML_END];
795 main(int argc, char **argv)
797 struct RDArgs *rdargs;
799 if ((rdargs = AllocDosObject(DOS_RDARGS, NULL)))
801 rdargs->RDA_ExtHelp = usage; /* FIX: why doesn't this work? */
803 if (!(ReadArgs(cmlargs, cmlvec, rdargs)))
805 PrintFault(IoErr(), "AROS boot");
806 FreeDosObject(DOS_RDARGS, rdargs);
807 exit(RETURN_FAIL);
810 else
812 PrintFault(ERROR_NO_FREE_STORE, "AROS boot");
813 exit(RETURN_FAIL);
816 FreeArgs(rdargs);
817 FreeDosObject(DOS_RDARGS, rdargs);
819 return 0;
820 } /* main */
822 #endif /* TEST */