[PATCH] Modular Commandline Parsing [Re: command line options for layer23 apps]
Christian Vogel
vogelchr at vogel.cx
Wed Oct 27 22:28:53 CEST 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