changeset 4:a4812d57ed13

Started implementing command line parsing in lwlib
author lost@l-w.ca
date Fri, 21 Jan 2011 22:54:48 -0700
parents d4eb3c328a47
children 0e01d1343c02
files lwlib/lw_cmdline.c lwlib/lw_cmdline.h lwlib/rules.make
diffstat 3 files changed, 381 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwlib/lw_cmdline.c	Fri Jan 21 22:54:48 2011 -0700
@@ -0,0 +1,298 @@
+/*
+lw_cmdline.c
+
+Copyright © 2010 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lw_alloc.h"
+
+#define ___lw_cmdline_c_seen___
+#include "lw_cmdline.h"
+
+static struct lw_cmdline_options builtin[3] =
+{
+	{ "help", '?', 0, 0, "give this help list" },
+	{ "usage", 0, 0, 0, "give a short usage message" },
+	{ "version", 'V', 0, 0, "print program version" }
+};
+
+static void lw_cmdline_usage(struct lw_cmdline_parser *parser, char *name)
+{
+	struct lw_cmdline_options **slist, **llist;
+	int nopt;
+	int i;
+	int t;
+	
+	for (nopt = 0; parser -> options[nopt].name; nopt++)
+		/* do nothing */ ;
+	
+	slist = lw_alloc(sizeof(struct lw_cmdline_options *) * (nopt + 3));
+	llist = lw_alloc(sizeof(struct lw_cmdline_options *) * (nopt + 3));	
+
+	for (i = 0; i < nopt; i++)
+	{
+		slist[i] = &(parser -> options[i]);
+		llist[i] = &(parser -> options[i]);
+	}
+	
+	/* now sort the two lists */
+	
+	/* now append the automatic options */
+	slist[nopt] = &(builtin[0]);
+	slist[nopt + 1] = &(builtin[1]);
+	slist[nopt + 2] = &(builtin[2]);
+
+	llist[nopt] = &(builtin[0]);
+	llist[nopt + 1] = &(builtin[1]);
+	llist[nopt + 2] = &(builtin[2]);
+	
+	/* now show the usage message */
+	printf("Usage: %s", name);
+	
+	/* print short options that take no args */
+	t = 0;
+	for (i = 0; i < nopt + 3; i++)
+	{
+		if (slist[i]->key > 0x20 && slist[i]->key < 0x7f)
+		{
+			if (slist[i]->arg == NULL)
+			{
+				if (!t)
+				{
+					printf(" [-");
+					t = 1;
+				}
+				printf("%c", slist[i]->key);
+			}
+		}
+	}
+	if (t)
+		printf("]");
+	
+	/* print short options that take args */
+	for (i = 0; i < nopt + 3; i++)
+	{
+		if (slist[i]->key > 0x20 && slist[i]->key < 0x7f && slist[i] -> arg)
+		{
+			if (slist[i]->flags & lw_cmdline_opt_optional)
+			{
+				printf(" [-%c[%s]]", slist[i]->key, slist[i]->arg);
+			}
+			else
+			{
+				printf(" [-%c %s]", slist[i]->key, slist[i]->arg);
+			}
+		}
+	}
+	
+	/* print long options */
+	for (i = 0; i < nopt + 3; i++)
+	{
+		if (llist[i]->arg)
+		{
+			printf(" [--%s=%s%s%s]", 
+				llist[i] -> name,
+				(llist[i] -> flags & lw_cmdline_opt_optional) ? "[" : "",
+				llist[i] -> arg,
+				(llist[i] -> flags & lw_cmdline_opt_optional) ? "]" : "");
+		}
+		else
+		{
+			printf(" [--%s]", llist[i] -> name);
+		}
+	}
+	
+	/* print "non option" text */
+	if (parser -> args_doc)
+	{
+		printf(" %s", parser -> args_doc);
+	}
+	printf("\n");
+	
+	/* clean up scratch lists */
+	lw_free(slist);
+	lw_free(llist);
+}
+
+static void lw_cmdline_help(struct lw_cmdline_parser *parser)
+{
+	printf("TODO: help\n");
+}
+
+int lw_cmdline_parse(struct lw_cmdline_parser *parser, int argc, char **argv, unsigned flags, int *arg_index, void *input)
+{
+	int i, j, r;
+	int firstarg;
+	int nextarg;
+	char *tstr;
+	int cch;
+	
+	/* first, permute the argv array so that all option arguments are at the start */
+	for (i = 1, firstarg = 1; i < argc; i++)
+	{
+		if (argv[i][0] == '-' && argv[i][1])
+		{
+			/* have an option arg */
+			if (firstarg == i)
+			{
+				firstarg++;
+				continue;
+			}
+			tstr = argv[i];
+			for (j = i; j > firstarg; j--)
+			{
+				argv[j] = argv[j - 1];
+			}
+			argv[firstarg] = tstr;
+			firstarg++;
+			if (argv[firstarg - 1][1] == '-' && argv[firstarg - 1][2] == 0)
+				break;
+		}
+	}
+
+	/* now start parsing options */
+	nextarg = firstarg;
+	i = 1;
+	cch = 0;
+	while (i < firstarg)
+	{
+		if (cch > 0 && argv[i][cch] == 0)
+		{
+			i++;
+			cch = 0;
+			continue;
+		}
+
+		if (cch > 0)
+			goto shortopt;
+		
+		/* skip the "--" option */
+		if (argv[i][1] == '-' && argv[i][2] == 0)
+			break;
+		
+		if (argv[i][1] == '-')
+		{
+			goto longopt;
+		}
+		
+		cch = 1;
+	shortopt:
+		/* handle a short option here */
+		
+		/* automatic options */
+		if (argv[i][cch] == '?')
+			goto do_help;
+		if (argv[i][cch] == 'V')
+			goto do_version;
+		/* look up key */
+		for (j = 0; parser -> options[j].name; j++)
+			if (parser -> options[j].key == argv[i][cch])
+				break;
+		cch++;
+		tstr = argv[i] + cch;
+		if (!*tstr)
+		{
+			if (nextarg < argc)
+				tstr = argv[nextarg];
+			else
+				tstr = NULL;
+		}
+		goto common;
+
+	longopt:
+		if (strcmp(argv[i], "--help") == 0)
+			goto do_help;
+		if (strcmp(argv[i], "--usage") == 0)
+			goto do_usage;
+		if (strcmp(argv[i], "--version") == 0)
+			goto do_version;
+		/* look up name */
+		
+		for (j = 2; argv[i][j] && argv[i][j] != '='; j++)
+			/* do nothing */ ;
+		tstr = lw_alloc(j - 1);
+		strncpy(tstr, argv[i] + 2, j - 2);
+		tstr[j - 1] = 0;
+		if (argv[i][j] == '=')
+			j++;
+		cch = j;
+		
+		for (j = 0; parser -> options[j].name; j++)
+		{
+			if (strcmp(parser -> options[j].name, tstr) == 0)
+				break;
+		}
+		lw_free(tstr);
+		tstr = argv[i] + cch;
+		cch = 0;
+		
+	common:
+		/* j will be the offset into the option table when we get here */
+		/* cch will be zero and tstr will point to the arg if it's a long option */
+		/* cch will be > 0 and tstr points to the theoretical option, either within */
+		/* this string or "nextarg" */
+		if (parser -> options[j].name == NULL)
+		{
+			fprintf(stderr, "Unknown option. See %s --usage.\n", argv[0]);
+			exit(1);
+		}
+		if (parser -> options[j].arg)
+		{
+			if (tstr && cch && argv[i][cch] == 0)
+				nextarg++;
+			
+			if (!*tstr)
+				tstr = NULL;
+			
+			if (!tstr && (parser -> options[j].flags & lw_cmdline_opt_optional) == 0)
+			{
+				fprintf(stderr, "Option %s requires argument.\n", parser -> options[j].name);
+			}
+		}
+		r = (*(parser -> parser))(parser -> options[j].key, tstr, input);
+		if (r != 0)
+			return r;
+	}
+	/* handle non-option args */
+	if (arg_index)
+		*arg_index = nextarg;
+	for (i = nextarg; i < argc; i++)
+	{
+		r = (*(parser -> parser))(lw_cmdline_key_arg, argv[i], input);
+		if (r != 0)
+			return r;
+	}
+	r = (*(parser -> parser))(lw_cmdline_key_end, NULL, input);
+	return r;
+
+do_help:
+	lw_cmdline_help(parser);
+	exit(0);
+
+do_version:
+	printf("%s\n", parser -> program_version);
+	exit(0);
+
+do_usage:
+	lw_cmdline_usage(parser, argv[0]);
+	exit(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwlib/lw_cmdline.h	Fri Jan 21 22:54:48 2011 -0700
@@ -0,0 +1,82 @@
+/*
+lw_cmdline.h
+
+Copyright © 2010 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+
+This argument parser is patterned after argp from the gnu C library but
+has much of the functionality removed. It is provided here because not
+every system has glibc.
+
+Most notably, it does not support option groups or i18n.
+
+*/
+
+#ifndef ___lw_cmdline_h_seen___
+#define ___lw_cmdline_h_seen___
+
+struct lw_cmdline_options
+{
+	char *name;
+	int key;
+	char *arg;
+	int flags;
+	char *doc;
+};
+
+enum
+{
+	lw_cmdline_opt_optional = 1,
+	lw_cmdline_opt_hidden = 2,
+	lw_cmdline_opt_alias = 4,
+	lw_cmdline_opt_doc = 8,
+	lw_cmdline_opt_nousage = 0x10
+};
+
+enum
+{
+	lw_cmdline_err_unknown = -1
+};
+
+enum
+{
+	lw_cmdline_key_arg = -1,
+	lw_cmdline_key_end = 0
+};
+
+struct lw_cmdline_parser
+{
+	struct lw_cmdline_options *options;
+	int (*parser)(int key, char *arg, void *input);
+	char *args_doc;
+	char *doc;
+	char *program_version;
+};
+
+#ifdef ___lw_cmdline_c_seen___
+
+#else /* def ___lw_cmdline_c_seen___ */
+
+extern int lw_cmdline_parse(struct lw_cmdline_parser *parser, int argc, char **argv, unsigned flags, int *arg_index, void *input);
+
+
+#endif /* def ___lw_cmdline_c_seen___ */
+
+#endif /* ___lw_cmdline_h_seen___ */
--- a/lwlib/rules.make	Thu Jan 20 22:56:29 2011 -0700
+++ b/lwlib/rules.make	Fri Jan 21 22:54:48 2011 -0700
@@ -1,6 +1,6 @@
 dirname := $(dir $(lastword $(MAKEFILE_LIST)))
 
 lwlib_srcs_local := lw_alloc.c lw_error.c lw_expr.c lw_stack.c \
-	lw_string.c lw_stringlist.c
+	lw_string.c lw_stringlist.c lw_cmdline.c
 
 lwlib_srcs := $(lwlib_srcs) $(addprefix $(dirname),$(lwlib_srcs_local))