[PATCH] Modular Commandline Parsing [Re: command line options for layer23 apps]

Christian Vogel vogelchr at vogel.cx
Wed Oct 27 20:28:53 UTC 2010


Hi everyone,

I was trying to iterate on that subject again, but I think those are
things more easily programmed than discussed. So, my first attempt
looks like the attached commit to libosmocore, you can also download
it at http://vogel.cx/git/0001-Modular-commandline-parsing.patch .

It uses a few arrays and singly linked lists which get iterated way
too often, but I think for commandline parsing it doesn't really matter.

It's used like this:

	/* callback function, called for each option given */
	int option_callback(struct cmdline_item *p, char *argument){
		if(p->shortopt == 'v')
			verbose++;
		if(!strncmp(p->longopt,"port"))
			portnum = atoi(argument);
	}

	struct cmdline_group mygroup = {
		.name = "Program Options Group",
		.nitems = 2,
		.callback = option_callback,
		.items = {
			{ 'v',"verbose", 0,"Add verbosity."      },
			{  0 ,"port",    1,"Specify tcp port."   },
		}
	};

	/* somewhere during initialisation */
	cmdline_register_group(&mygroup);

---
 configure.in                 |    1 +
 include/osmocore/cmdline.h   |   37 +++++++++
 src/Makefile.am              |    2 +-
 src/cmdline.c                |  184 ++++++++++++++++++++++++++++++++++++++++++
 tests/Makefile.am            |    2 +-
 tests/cmdline/Makefile.am    |    5 +
 tests/cmdline/cmdline_test.c |   55 +++++++++++++
 7 files changed, 284 insertions(+), 2 deletions(-)
 create mode 100644 include/osmocore/cmdline.h
 create mode 100644 src/cmdline.c
 create mode 100644 tests/cmdline/Makefile.am
 create mode 100644 tests/cmdline/cmdline_test.c

diff --git a/configure.in b/configure.in
index 30f9d9c..d9c0d30 100644
--- a/configure.in
+++ b/configure.in
@@ -114,4 +114,5 @@ AC_OUTPUT(
 	tests/sms/Makefile
 	tests/msgfile/Makefile
 	tests/ussd/Makefile
+	tests/cmdline/Makefile
 	Makefile)
diff --git a/include/osmocore/cmdline.h b/include/osmocore/cmdline.h
new file mode 100644
index 0000000..e322b45
--- /dev/null
+++ b/include/osmocore/cmdline.h
@@ -0,0 +1,37 @@
+#ifndef _OSMOCORE_CMDLINE_H
+#define _OSMOCORE_CMDLINE_H
+
+struct cmdline_item {
+	char shortopt;                 /* option "-s" */
+	char *longopt;                 /* option "--longopt" */
+	int hasarg;                    /* option has args */
+	char *helptext;                /* annotate usage */
+	union cmdline_item_data { /* random data you want to keep track of */
+		char c;
+		int i;
+		void *ptr;
+	} data;
+};
+
+struct cmdline_group {
+	char *name;            /* name of this group, for usage */
+	/* if an option is called, this callback will trigger.
+	   1st function parameter is the cmdline_item.
+	   2nc function parameter is the option argument (hasarg != 0).
+	   Return != 0 from the callback to signal an error! */
+	int (*callback)(struct cmdline_item*,char *);
+
+	int nitems;                 /* number of items */
+	struct cmdline_group *next; /* used internally */
+	struct cmdline_item items[]; /* items in this group */
+};
+
+/* add cmdline_group to osmocore command line parser */
+extern void cmdline_register_group(struct cmdline_group *p);
+
+/* do the actual parsing */
+extern int cmdline_parse(int argc,char **argv,int *optind);
+
+extern void cmdline_usage(char *argv0);
+
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 64310e0..e4fe4a8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,7 +13,7 @@ libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c rxlev_stat.c \
 			 tlv_parser.c bitvec.c comp128.c gsm_utils.c statistics.c \
 			 write_queue.c utils.c rsl.c gsm48.c gsm48_ie.c \
 			 logging.c gsm0808.c rate_ctr.c gsmtap_util.c \
-			 gprs_cipher_core.c crc16.c panic.c process.c gsm0480.c
+			 gprs_cipher_core.c crc16.c panic.c process.c gsm0480.c cmdline.c
 
 if ENABLE_PLUGIN
 libosmocore_la_SOURCES += plugin.c
diff --git a/src/cmdline.c b/src/cmdline.c
new file mode 100644
index 0000000..ddf5747
--- /dev/null
+++ b/src/cmdline.c
@@ -0,0 +1,184 @@
+/* command line parsing for osmocom applications */
+
+/*
+ * (C) 2010 Christian Vogel <vogelchr at vogel.cx>
+ * All Rights Reserved
+ *
+ * This program 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocore/cmdline.h>
+#include <osmocore/talloc.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <libgen.h>
+
+static struct cmdline_group *cmdline_groups = NULL;
+
+/*
+ * Find short / long option registered with us. shortopt may be \0
+ * and longopt may be NULL, then we ignore this part of an item.
+ *
+ * Returns item found. If grpret != NULL the variable pointed to
+ * will be set to the specific group.
+ */
+static struct cmdline_item *
+cmdline_find_item(char shortopt,
+		  const char *longopt,
+		  struct cmdline_group **grpret)
+{
+	struct cmdline_group *grp = cmdline_groups;
+	int i;
+
+	while(grp){
+		for(i=0;i<grp->nitems;i++){
+			struct cmdline_item *item = &grp->items[i];
+			if( (shortopt && shortopt== item->shortopt     )||
+			    (longopt  && !strcmp(longopt,item->longopt))){
+				if(grpret)
+					*grpret=grp;
+				return item;
+			}
+		}
+		grp = grp->next;
+	}
+}
+
+/* add cmdline_group to osmocore command line parser */
+void
+cmdline_register_group(struct cmdline_group *p)
+{
+	/* insert group into chain */
+	struct cmdline_group *ogrp,**last = &cmdline_groups;
+	struct cmdline_item *oitem,*item;
+	int i;
+	
+	/* just for debugging / finding duplicate options */
+	for(i=0;i<p->nitems;i++){
+		item = &p->items[i];
+		oitem = cmdline_find_item(item->shortopt,item->longopt,&ogrp);
+		if(oitem){
+			fprintf(stderr,"%s: duplicate option registered!",
+				__FUNCTION__);
+			if(item->shortopt)
+				fprintf(stderr," (short: \'%c\')",
+					item->shortopt);
+			if(item->longopt)
+				fprintf(stderr," (long: \"%s\")",item->longopt);
+			fprintf(stderr,"Old group: %s, this group: %s.\n",
+				p->name,ogrp->name);
+		}
+	}
+
+	while(*last) // add as last group
+		last = &( (*last)->next );
+	*last = p;
+};
+
+void cmdline_usage(char *argv0){
+	struct cmdline_group *grp;
+	char buf[80];
+	int i;
+
+	if(!cmdline_groups){
+		fprintf(stderr,"Usage: %s arguments\n",basename(argv0));
+		return;
+	}
+	fprintf(stderr,"Usage: %s [option] arguments\n\n",basename(argv0));
+	for(grp=cmdline_groups;grp;grp=grp->next){
+		fprintf(stderr,"*** Options in group %s ***\n",grp->name);
+		for(i=0;i<grp->nitems;i++){
+			struct cmdline_item *item = & grp->items[i];
+			if(item->shortopt && item->longopt){
+				sprintf(buf,"-%c|--%s",
+					 item->shortopt,item->longopt);
+			} else if(item->shortopt) {
+				sprintf(buf,"-%c",item->shortopt);
+			} else {
+				sprintf(buf,"--%s",item->longopt);
+			}
+			if(item->hasarg)
+				strcat(buf," ARG");
+			if(item->helptext)
+				fprintf(stderr,"   %-32s %s\n",buf,item->helptext);
+			else
+				fprintf(stderr,"   %s\n",buf);
+		}
+	}
+}
+
+int cmdline_parse(int argc,char **argv,int *optind){
+	struct cmdline_group *grp;
+	struct cmdline_item *item;
+	struct option *opts,*o;
+	char *optstr,*s;
+	int i=0,j;
+	int ret=0;
+
+	/* how many items do we have? */
+	for(grp=cmdline_groups;grp;grp=grp->next)
+		i += grp->nitems;
+
+	/* allocate memory for getopt_long data structures */
+	o = opts = talloc_array(NULL,struct option,i+1);
+	s = optstr = talloc_size(NULL,1+2*i); // worst case
+
+	/* build struct option array and short option string */
+	for(grp=cmdline_groups;grp;grp=grp->next){
+		for(i=0;i<grp->nitems;i++){
+			item = & grp->items[i];
+			if(item->longopt){
+				o->name = item->longopt;
+				o->has_arg = item->hasarg;
+				o->flag = NULL;
+				o->val = 0;
+				o++;
+			}
+			if(item->shortopt){
+				*s++ = item->shortopt;
+				if(item->hasarg)
+					*s++ = ':';
+			}
+		}
+	}
+	o->name = NULL;
+	o->has_arg = 0;
+	o->flag = NULL;
+	o->val = 0;
+	*s = '\0';
+
+	while(-1 != (i=getopt_long(argc,argv,optstr,opts,&j))){
+		item = NULL;
+		if(i==':' || i=='?'){
+			ret=-1; // getopt should write error message
+			goto out;
+		}
+		if(i==0) // long option
+			item = cmdline_find_item(0,opts[j].name,&grp);
+		else
+			item = cmdline_find_item(i,NULL,&grp);
+		if(grp->callback)
+			grp->callback(item,optarg);
+	}
+out:
+	talloc_free(opts);
+	talloc_free(optstr);
+	return ret;
+}
+
+
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0166b4f..ecdc8ad 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,5 +1,5 @@
 if ENABLE_TESTS
-SUBDIRS = timer sms ussd
+SUBDIRS = timer sms ussd cmdline
 if ENABLE_MSGFILE
 SUBDIRS += msgfile
 endif
diff --git a/tests/cmdline/Makefile.am b/tests/cmdline/Makefile.am
new file mode 100644
index 0000000..3b155e1
--- /dev/null
+++ b/tests/cmdline/Makefile.am
@@ -0,0 +1,5 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = cmdline_test
+
+cmdline_test_SOURCES = cmdline_test.c
+cmdline_test_LDADD = $(top_builddir)/src/libosmocore.la
diff --git a/tests/cmdline/cmdline_test.c b/tests/cmdline/cmdline_test.c
new file mode 100644
index 0000000..295b5aa
--- /dev/null
+++ b/tests/cmdline/cmdline_test.c
@@ -0,0 +1,55 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <osmocore/cmdline.h>
+
+int option_callback(struct cmdline_item *p,char *argument){
+	printf("Option specified:");
+	if(p->shortopt)
+		printf(" -%c",p->shortopt);
+	if(p->shortopt && p->longopt)
+		printf(" or");
+	if(p->longopt)
+		printf(" --%s",p->longopt);	
+	if(p->hasarg)
+		printf(" argument=%s",argument);
+	printf("\n");
+	return 0;
+}
+
+struct cmdline_group group_quux = {
+	.name = "The QUUX group.",
+	.nitems = 3,
+	.callback = option_callback,
+	.items = {
+		// -s  --long, hasarg, description
+		{ 'a',"aaaah", 1,"The Aaaaaaah option."},
+		{ 'b',"bleech",0,"The Bleech option."},
+		{ 'c',"cccc",  1,"The CCCC option."}
+	}
+};
+
+struct cmdline_group group_foo = {
+	.name = "The FOO group.",
+	.callback = option_callback,
+	.nitems = 2,
+	.items = {
+		{  0 ,"foo", 0,"The foo option."},
+		{ 'v',NULL , 1,"The v option."}
+	}
+};
+
+
+int main(int argc,char **argv){
+	int optind;
+
+	/* in layer23, these would each go into a different
+	   part of the application, e.g. logging, gsm, ... */
+	cmdline_register_group(&group_quux);
+	cmdline_register_group(&group_foo);
+
+	/* in layer23, this would go to the "common" code */
+	if(-1 == cmdline_parse(argc,argv,&optind))
+		cmdline_usage(argv[0]);
+}
-- 
1.6.3.3








More information about the baseband-devel mailing list