3 Copyright © 1995-2007, The AROS Development Team. All rights reserved.
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>
20 # include <proto/dos.h>
23 # define AROS_LH3(t,fn,a1,a2,a3,bt,bn,o,lib) t fn (a1,a2,a3)
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
32 /*****************************************************************************
35 #include <proto/dos.h>
37 AROS_LH3(struct RDArgs
*, ReadArgs
,
40 AROS_LHA(CONST_STRPTR
, template, D1
),
41 AROS_LHA(IPTR
*, array
, D2
),
42 AROS_LHA(struct RDArgs
*, rdargs
, D3
),
45 struct DosLibrary
*, DOSBase
, 133, Dos
)
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.
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
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.
72 /A Argument is required. If it is left out ReadArgs()
74 /K The keyword must be given when filling the option.
75 Normally it's skipped.
76 /M Multiple strings. The result is returned as a string
77 pointer array terminated with NULL. /M eats all strings
78 that don't fit into any other option. If there are
79 unfilled /A arguments after parsing they steal strings
80 from /M. This makes it possible to e.g. write a COPY
81 template like 'FROM/A/M,TO/A'. There may be only one
82 /M option in a template.
83 /F Eats the rest of the line even if there are option
85 array - Array to be filled with the result values. The array must
86 be intialized to the default values before calling
88 rdargs - An optional RDArgs structure determinating the type of
92 A handle for the memory allocated by ReadArgs(). Must be freed
93 with FreeArgs() later.
98 *****************************************************************************/
102 /* Allocated resources */
103 struct DAList
*dalist
= NULL
;
105 STRPTR strbuf
= NULL
, iline
= NULL
;
106 STRPTR
*multvec
= NULL
, *argbuf
= NULL
;
107 ULONG multnum
= 0, multmax
= 0;
111 STRPTR s1
, s2
, *newmult
;
112 ULONG arg
, numargs
, nextarg
;
113 LONG it
, item
, chars
, value
;
114 struct CSource lcs
, *cs
;
116 ASSERT_VALID_PTR(template);
117 ASSERT_VALID_PTR(array
);
118 ASSERT_VALID_PTR_OR_NULL(rdargs
);
120 /* Get pointer to process structure. */
121 struct Process
*me
= (struct Process
*) FindTask(NULL
);
123 /* Error recovery. C has no exceptions. This is a simple replacement. */
127 #define ERROR(a) { error=a; goto end; }
129 /* Template options */
130 #define REQUIRED 0x80 /* /A */
131 #define KEYWORD 0x40 /* /K */
132 #define MULTIPLE 0x20 /* /M */
133 #define TYPEMASK 0x07
134 #define NORMAL 0x00 /* No option */
135 #define SWITCH 0x01 /* /S, implies /K */
136 #define TOGGLE 0x02 /* /T, implies /K */
137 #define NUMERIC 0x03 /* /N */
138 #define REST 0x04 /* /F */
140 /* Flags for each possible character. */
141 static const UBYTE argflags
[] =
143 REQUIRED
, 0, 0, 0, 0, REST
, 0, 0, 0, 0, KEYWORD
, 0, MULTIPLE
,
144 NUMERIC
, 0, 0, 0, 0, SWITCH
| KEYWORD
, TOGGLE
| KEYWORD
, 0, 0,
148 /* Allocate readargs structure (and private internal one) */
151 rdargs
= (struct RDArgs
*) AllocVec(sizeof(struct RDArgs
),
152 MEMF_ANY
| MEMF_CLEAR
);
155 rdargs
->RDA_Flags
|= RDAF_ALLOCATED_BY_READARGS
;
159 dalist
= (struct DAList
*) AllocVec(sizeof(struct DAList
),
160 MEMF_ANY
| MEMF_CLEAR
);
162 if (rdargs
== NULL
|| dalist
== NULL
)
164 ERROR(ERROR_NO_FREE_STORE
);
167 /* Init character source. */
168 if (rdargs
->RDA_Source
.CS_Buffer
)
170 cs
= &rdargs
->RDA_Source
;
174 lcs
.CS_Buffer
= (me
->pr_Arguments
? me
->pr_Arguments
: (UBYTE
*) "");
182 lcs
.CS_Length
= (IPTR
) cs1
- (IPTR
) lcs
.CS_Buffer
- 1;
188 /* Check for optional reprompting */
189 if (!(rdargs
->RDA_Flags
& RDAF_NOPROMPT
))
191 /* Check commandline for a single '?' */
194 /* Skip leading whitespace */
195 while (*cs1
== ' ' || *cs1
== '\t')
203 /* Skip whitespace */
204 while (*cs1
== ' ' || *cs1
== '\t')
210 if (*cs1
== '\n' || !*cs1
)
212 /* Only a single '?' on the commandline. */
213 BPTR input
= Input();
214 BPTR output
= Output();
215 ULONG isize
= 0, ibuf
= 0;
218 /* Prompt for more input */
220 /* printf ("Only ? found\n");
221 printf ("rdargs=%p\n", rdargs);
223 printf ("rdargs->RDA_ExtHelp=%p\n", rdargs->RDA_ExtHelp); */
225 if (rdargs
->RDA_ExtHelp
!= NULL
)
227 if (FPuts(output
, rdargs
->RDA_ExtHelp
))
228 ERROR(me
->pr_Result2
);
231 if (FPuts(output
, template) || FPuts(output
, ": "))
233 ERROR(me
->pr_Result2
);
238 ERROR(me
->pr_Result2
);
241 /* Read a line in. */
246 /* Buffer too small. Get a new one. */
251 newiline
= (STRPTR
) AllocVec(ibuf
, MEMF_ANY
);
253 if (newiline
== NULL
)
255 ERROR(ERROR_NO_FREE_STORE
);
258 CopyMemQuick((ULONG
*) iline
,
259 (ULONG
*) newiline
, isize
);
269 /* Check and write it. */
270 if (c
== EOF
&& me
->pr_Result2
)
272 ERROR(me
->pr_Result2
);
275 if (c
== EOF
|| c
== '\n' || !c
)
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';
292 /* Prepare input source for new line. */
293 cs
->CS_Buffer
= iline
;
294 cs
->CS_Length
= isize
;
300 * Get enough space for string buffer.
301 * It's always smaller than the size of the input line+1.
304 strbuf
= (STRPTR
) AllocVec(cs
->CS_Length
+ 1, MEMF_ANY
);
308 ERROR(ERROR_NO_FREE_STORE
);
311 /* Count the number of items in the template (number of ','+1). */
323 /* Use this count to get space for temporary flag array and result
325 flags
= (UBYTE
*) AllocVec(numargs
+ 1, MEMF_CLEAR
);
327 argbuf
= (STRPTR
*) AllocVec((numargs
+ 1) * sizeof(STRPTR
), MEMF_CLEAR
);
329 if (flags
== NULL
|| argbuf
== NULL
)
331 ERROR(ERROR_NO_FREE_STORE
);
334 /* Fill the flag array. */
340 /* A ',' means: goto next item. */
346 /* In case of a '/' use the next character as option. */
349 *s2
|= argflags
[*cs1
- 'A'];
353 /* Add a dummy so that the whole line is processed. */
357 * Now process commandline for the first time:
358 * Go from left to right and fill all items that need filling.
359 * If an item is given as 'OPTION=VALUE' or 'OPTION VALUE' fill
364 for (arg
= 0; arg
<= numargs
; arg
= nextarg
)
368 /* Skip /K options and options that are already done. */
369 if (flags
[arg
] & KEYWORD
|| argbuf
[arg
] != NULL
)
374 #if 0 /* stegerg: if so a template of CLOSE/S,QUICK/S,COMMAND/F would
375 not work correctly if command line for example is
376 "CLOSE QUICK" it would all end up being eaten by COMMAND/F
379 /* If the current option is of type /F do not look for keywords */
380 if ((flags
[arg
] & TYPEMASK
) != REST
)
384 /* Get item. Quoted items are no keywords. */
385 it
= ReadItem(s1
, ~0ul / 2, cs
);
387 if (it
== ITEM_UNQUOTED
)
389 /* Not quoted. Check if it's a keyword. */
390 item
= FindArg(template, s1
);
392 if (item
>= 0 && item
< numargs
&& argbuf
[item
] == NULL
)
395 * It's a keyword. Fill it and retry the current option
401 /* /S /T may not be given as 'OPTION=VALUE'. */
402 if ((flags
[item
] & TYPEMASK
) != SWITCH
403 && (flags
[item
] & TYPEMASK
) != TOGGLE
)
406 it
= ReadItem(s1
, ~0ul / 2, cs
);
408 if (it
== ITEM_EQUAL
)
410 it
= ReadItem(s1
, ~0ul / 2, cs
);
416 /* Check returncode of ReadItem(). */
417 if (it
== ITEM_EQUAL
)
419 ERROR(ERROR_BAD_TEMPLATE
);
421 else if (it
== ITEM_ERROR
)
423 ERROR(me
->pr_Result2
);
425 else if (it
== ITEM_NOTHING
)
431 /* /F takes all the rest */
432 /* TODO: Take care of quoted strings(?) */
433 if ((flags
[arg
] & TYPEMASK
) == REST
)
436 /* Skip leading whitespace */
437 while (cs
->CS_CurChr
< cs
->CS_Length
438 && (cs
->CS_Buffer
[cs
->CS_CurChr
] == ' '
439 || cs
->CS_Buffer
[cs
->CS_CurChr
] == '\t'))
446 /* Copy part already read above by ReadItem() */
452 /* Find the last non-whitespace character */
455 while (cs
->CS_CurChr
< cs
->CS_Length
456 && cs
->CS_Buffer
[cs
->CS_CurChr
]
457 && cs
->CS_Buffer
[cs
->CS_CurChr
] != '\n')
459 if (cs
->CS_Buffer
[cs
->CS_CurChr
] != ' '
460 && cs
->CS_Buffer
[cs
->CS_CurChr
] != '\t')
465 /* Copy string by the way. */
466 *s1
++ = cs
->CS_Buffer
[cs
->CS_CurChr
++];
469 /* Add terminator (1 after the character found). */
475 if (flags
[arg
] & MULTIPLE
)
477 /* All /M arguments are stored in a buffer. */
478 if (multnum
>= multmax
)
480 /* Buffer too small. Get a new one. */
483 newmult
= (STRPTR
*) AllocVec(multmax
* sizeof(char *),
487 ERROR(ERROR_NO_FREE_STORE
);
490 CopyMemQuick((ULONG
*) multvec
, (ULONG
*) newmult
,
491 multnum
* sizeof(char *));
498 /* Put string into the buffer. */
499 multvec
[multnum
++] = s1
;
505 /* /M takes more than one argument, so retry. */
508 else if ((flags
[arg
] & TYPEMASK
) == SWITCH
509 || (flags
[arg
] & TYPEMASK
) == TOGGLE
)
511 /* /S or /T just set a flag */
512 argbuf
[arg
] = (char *) ~0;
514 else /* NORMAL || NUMERIC */
516 /* Put argument into argument buffer. */
525 /* Unfilled /A options steal Arguments from /M */
526 for (arg
= numargs
; arg
-- > 0;)
528 if (flags
[arg
] & REQUIRED
&& argbuf
[arg
] == NULL
529 && !(flags
[arg
] & MULTIPLE
))
531 if (flags
[arg
] & KEYWORD
)
533 /* /K/A argument, which inisits on keyword
534 * being used, cannot be satisfied */
536 ERROR(ERROR_TOO_MANY_ARGS
); /* yes, strange error number,
537 * but it translates to "wrong
538 * number of arguments" */
544 /* No arguments left? Oh dear! */
545 ERROR(ERROR_REQUIRED_ARG_MISSING
);
548 argbuf
[arg
] = multvec
[--multnum
];
552 /* Put the rest of /M where it belongs */
553 for (arg
= 0; arg
< numargs
; arg
++)
555 if (flags
[arg
] & MULTIPLE
)
557 if (flags
[arg
] & REQUIRED
&& !multnum
)
559 ERROR(ERROR_REQUIRED_ARG_MISSING
);
564 /* NULL terminate it. */
565 if (multnum
>= multmax
)
569 newmult
= (STRPTR
*) AllocVec(multmax
* sizeof(STRPTR
),
574 ERROR(ERROR_NO_FREE_STORE
);
577 CopyMemQuick((ULONG
*) multvec
, (ULONG
*) newmult
,
578 multnum
* sizeof(char *));
585 multvec
[multnum
++] = NULL
;
586 argbuf
[arg
] = (STRPTR
) multvec
;
590 /* Shouldn't be necessary, but some buggy software relies on this */
598 /* There are some arguments left? Return error. */
599 if (multnum
&& arg
== numargs
)
601 ERROR(ERROR_TOO_MANY_ARGS
);
605 * The commandline is processed now. Put the results in the result array.
606 * Convert /N arguments by the way.
608 for (arg
= 0; arg
< numargs
; arg
++)
610 /* Just for the arguments given. */
611 if (argbuf
[arg
] != NULL
)
613 if (flags
[arg
] & MULTIPLE
)
615 array
[arg
] = (IPTR
) argbuf
[arg
];
617 if ((flags
[arg
] & TYPEMASK
) == NUMERIC
)
622 if (multnum
* 2 > multmax
)
624 multmax
= multnum
* 2;
625 newmult
= (STRPTR
*) AllocVec(multmax
* sizeof(STRPTR
),
630 ERROR(ERROR_NO_FREE_STORE
);
633 CopyMemQuick((ULONG
*) multvec
, (ULONG
*) newmult
,
634 multnum
* sizeof(char *));
641 array
[arg
] = (IPTR
) multvec
;
643 q
= (SIPTR
*) (multvec
+ multnum
);
647 /* Convert /N argument. */
648 chars
= StrToLong(*p
, q
);
650 if (chars
<= 0 || (*p
)[chars
])
652 /* Conversion failed. */
653 ERROR(ERROR_BAD_NUMBER
);
656 /* Put the result where it belongs. */
665 switch (flags
[arg
] & TYPEMASK
)
670 /* Simple arguments are just copied. */
671 array
[arg
] = (IPTR
) argbuf
[arg
];
675 /* /T logically inverts the argument. */
676 array
[arg
] = array
[arg
] ? 0 : ~0;
680 /* Convert /N argument. */
681 chars
= StrToLong(argbuf
[arg
], &value
);
683 if (chars
<= 0 || argbuf
[arg
][chars
])
685 /* Conversion failed. */
686 ERROR(ERROR_BAD_NUMBER
);
689 /* Put the result where it belongs. */
691 if (flags
[arg
] & REQUIRED
)
692 /* Required argument. Return number. */
697 /* Abuse the argbuf buffer. It's not needed anymore. */
698 argbuf
[arg
] = (STRPTR
) value
;
699 array
[arg
] = (IPTR
) & argbuf
[arg
];
707 if (flags
[arg
] & MULTIPLE
)
709 /* Shouldn't be necessary, but some buggy software relies on this.
710 * IBrowse's URL field isn't set to zero.
712 array
[arg
] = (IPTR
)NULL
;
720 /* Cleanup and return. */
726 /* ReadArgs() failed. Clean everything up. */
729 if (rdargs
->RDA_Flags
& RDAF_ALLOCATED_BY_READARGS
)
740 me
->pr_Result2
= error
;
746 /* All went well. Prepare result and return. */
747 rdargs
->RDA_DAList
= (IPTR
) dalist
;
748 dalist
->ArgBuf
= argbuf
;
749 dalist
->StrBuf
= strbuf
;
750 dalist
->MultVec
= multvec
;
757 # include <dos/dos.h>
758 # include <dos/rdargs.h>
759 # include <utility/tagitem.h>
761 # include <proto/dos.h>
763 char cmlargs
[] = "TEST/A";
766 "This is exthelp for test\n"
772 LONG cmlvec
[CML_END
];
775 main(int argc
, char **argv
)
777 struct RDArgs
*rdargs
;
779 if ((rdargs
= AllocDosObject(DOS_RDARGS
, NULL
)))
781 rdargs
->RDA_ExtHelp
= usage
; /* FIX: why doesn't this work? */
783 if (!(ReadArgs(cmlargs
, cmlvec
, rdargs
)))
785 PrintFault(IoErr(), "AROS boot");
786 FreeDosObject(DOS_RDARGS
, rdargs
);
792 PrintFault(ERROR_NO_FREE_STORE
, "AROS boot");
797 FreeDosObject(DOS_RDARGS
, rdargs
);