#include <stdio.h>
-#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+/* common */
+#include "abend.h"
+#include "chartype.h"
+#include "xmalloc.h"
#include "cmdline.h"
-static char** ArgVec = 0;
-static unsigned ArgCount = 0;
+/* Program name - is set after call to InitCmdLine */
+const char* ProgName;
+
+/* The program argument vector */
+char** ArgVec = 0;
+unsigned ArgCount = 0;
+
+/* Struct to pass the command line */
+typedef struct {
+ char** Vec; /* The argument vector */
+ unsigned Count; /* Actual number of arguments */
+ unsigned Size; /* Number of argument allocated */
+} CmdLine;
/*****************************************************************************/
-/* Code */
+/* Helper functions */
/*****************************************************************************/
-void InitCmdLine (unsigned aArgCount, char* aArgVec[])
+static void NewCmdLine (CmdLine* L)
+/* Initialize a CmdLine struct */
+{
+ unsigned I;
+
+ /* Initialize the struct */
+ L->Size = 8;
+ L->Count = 0;
+ L->Vec = xmalloc (L->Size * sizeof (L->Vec[0]));
+
+ /* Copy the arguments. We have to allocate them on free store, otherwise
+ * we would have to keep track which one is on free store and which not,
+ * which is a lot more overhead.
+ */
+ for (I = 0; I < L->Count; ++I) {
+ L->Vec[I] = xstrdup (ArgVec[I]);
+ }
+}
+
+
+
+static void AddArg (CmdLine* L, const char* Arg)
+/* Add one argument to the list */
+{
+ if (L->Size <= L->Count) {
+ /* No space left, reallocate */
+ unsigned NewSize = L->Size * 2;
+ char** NewVec = xmalloc (NewSize * sizeof (L->Vec[0]));
+ memcpy (NewVec, L->Vec, L->Count * sizeof (L->Vec[0]));
+ xfree (L->Vec);
+ L->Vec = NewVec;
+ L->Size = NewSize;
+ }
+
+ /* We have space left, add a copy of the argument */
+ L->Vec [L->Count++] = xstrdup (Arg);
+}
+
+
+
+static void ExpandFile (CmdLine* L, const char* Name)
+/* Add the contents of a file to the command line. Each line is a separate
+ * argument with leading and trailing whitespace removed.
+ */
+{
+ char Buf [256];
+
+ /* Try to open the file for reading */
+ FILE* F = fopen (Name, "r");
+ if (F == 0) {
+ AbEnd ("Cannot open \"%s\": %s", Name, strerror (errno));
+ }
+
+ /* File is open, read all lines */
+ while (fgets (Buf, sizeof (Buf), F) != 0) {
+
+ /* Get a pointer to the buffer */
+ const char* B = Buf;
+
+ /* Skip trailing whitespace (this will also kill the newline that is
+ * appended by fgets().
+ */
+ unsigned Len = strlen (Buf);
+ while (Len > 0 && IsSpace (Buf [Len-1])) {
+ --Len;
+ }
+ Buf [Len] = '\0';
+
+ /* Skip leading spaces */
+ while (IsSpace (*B)) {
+ ++B;
+ }
+
+ /* Skip empty lines to work around problems with some editors */
+ if (*B == '\0') {
+ continue;
+ }
+
+ /* Add anything not empty to the command line */
+ AddArg (L, B);
+
+ }
+
+ /* Close the file, ignore errors here since we had the file open for
+ * reading only.
+ */
+ (void) fclose (F);
+}
+
+
+
+/*****************************************************************************/
+/* Code */
+/*****************************************************************************/
+
+
+
+void InitCmdLine (int* aArgCount, char** aArgVec[], const char* aProgName)
/* Initialize command line parsing. aArgVec is the argument array terminated by
* a NULL pointer (as usual), ArgCount is the number of valid arguments in the
* array. Both arguments are remembered in static storage.
*/
{
- ArgCount = aArgCount;
- ArgVec = aArgVec;
+ CmdLine L;
+ unsigned I;
+
+ /* Get the program name from argv[0] but strip a path */
+ if (*(aArgVec)[0] == 0) {
+ /* Use the default name given */
+ ProgName = aProgName;
+ } else {
+ /* Strip a path */
+ const char* FirstArg = (*aArgVec)[0];
+ ProgName = strchr (FirstArg, '\0');
+ while (ProgName > FirstArg) {
+ --ProgName;
+ if (*ProgName == '/' || *ProgName == '\\') {
+ ++ProgName;
+ break;
+ }
+ }
+ if (ProgName[0] == '\0') {
+ /* Use the default */
+ ProgName = aProgName;
+ }
+ }
+
+ /* Make a CmdLine struct */
+ NewCmdLine (&L);
+
+ /* Walk over the parameters and add them to the CmdLine struct. Add a
+ * special handling for arguments preceeded by the '@' sign - these are
+ * actually files containing arguments.
+ */
+ for (I = 0; I < *aArgCount; ++I) {
+
+ /* Get the next argument */
+ char* Arg = (*aArgVec)[I];
+
+ /* Is this a file argument? */
+ if (Arg && Arg[0] == '@') {
+
+ /* Expand the file */
+ ExpandFile (&L, Arg+1);
+
+ } else {
+
+ /* No file, just add a copy */
+ AddArg (&L, Arg);
+
+ }
+ }
+
+ /* Store the new argument list in a safe place... */
+ ArgCount = L.Count;
+ ArgVec = L.Vec;
+
+ /* ...and pass back the changed data also */
+ *aArgCount = L.Count;
+ *aArgVec = L.Vec;
}
void UnknownOption (const char* Opt)
-/* Print an error about an unknown option. */
+/* Print an error about an unknown option and die. */
{
- fprintf (stderr, "Unknown option: %s\n", Opt);
- exit (EXIT_FAILURE);
+ AbEnd ("Unknown option: %s\n", Opt);
}
void NeedArg (const char* Opt)
/* Print an error about a missing option argument and exit. */
{
- fprintf (stderr, "Option requires an argument: %s\n", Opt);
- exit (EXIT_FAILURE);
+ AbEnd ("Option requires an argument: %s\n", Opt);
}
-void InvSym (const char* Def)
-/* Print an error about an invalid symbol definition and die */
+void InvDef (const char* Def)
+/* Print an error about an invalid definition and die */
{
- fprintf (stderr, "Invalid symbol definition: `%s'\n", Def);
- exit (EXIT_FAILURE);
+ AbEnd ("Invalid definition: `%s'\n", Def);
}
if (strcmp (Opt, OptTab->Option) == 0) {
/* Found, call the function */
if (OptTab->ArgCount > 0) {
- OptTab->Func (Opt, ArgVec[++(*ArgNum)]);
+ /* We need an argument, check if we have one */
+ const char* Arg = ArgVec[++(*ArgNum)];
+ if (Arg == 0) {
+ NeedArg (Opt);
+ }
+ OptTab->Func (Opt, Arg);
} else {
- OptTab->Func (Opt, 0);
+ OptTab->Func (Opt, 0);
}
/* Done */
return;