lib/cli/main.cc
changeset 0 6f7a81934006
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/cli/main.cc	Wed Jan 16 22:39:43 2008 +0100
@@ -0,0 +1,342 @@
+// Copyright (C) 1999,2000 Bruce Guenter <bruceg@em.ca>
+//
+// 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#include <config.h>
+#include "ac/time.h"
+#include "fdbuf/fdbuf.h"
+#include <stdlib.h>
+#include <string.h>
+#include "cli.h"
+
+#ifndef HAVE_SRANDOM
+void srandom(unsigned int seed);
+#endif
+
+static bool do_show_usage = false;
+const char* argv0;
+const char* argv0base;
+const char* argv0dir;
+
+static cli_option help_option = { 'h', "help", cli_option::flag,
+				  true, &do_show_usage,
+				  "Display this help and exit", 0 };
+
+static cli_option** options;
+static unsigned optionc;
+
+static void build_options()
+{
+  for(optionc = 0;
+      cli_options[optionc].ch || cli_options[optionc].name;
+      optionc++) ;
+  optionc++;
+  options = new cli_option*[optionc];
+  for(unsigned i = 0; i < optionc-1; i++)
+    options[i] = &cli_options[i];
+  options[optionc-1] = &help_option;
+}
+
+static inline unsigned max(unsigned a, unsigned b)
+{
+  return (a>b) ? a : b;
+}
+
+static const char* fill(unsigned i)
+{
+  static unsigned lastlen = 0;
+  static char* buf = 0;
+  if(i > lastlen) {
+    delete[] buf;
+    buf = new char[i+1];
+    lastlen = i;
+  }
+  memset(buf, ' ', i);
+  buf[i] = 0;
+  return buf;
+}
+  
+static void show_usage()
+{
+  fout << "usage: " << cli_program << " [flags] " << cli_args_usage << endl;
+}
+
+static unsigned calc_max_width()
+{
+  // maxwidth is the maximum width of the long argument
+  unsigned maxwidth = 0;
+  for(unsigned i = 0; i < optionc; i++) {
+    unsigned width = 0;
+    cli_option* o = options[i];
+    if(o->name) {
+      width += strlen(o->name);
+      switch(o->type) {
+      case cli_option::string:     width += 6; break;
+      case cli_option::integer:    width += 4; break;
+      case cli_option::uinteger:   width += 4; break;
+      case cli_option::stringlist: width += 5; break;
+      case cli_option::flag:       break;
+      case cli_option::counter:    break;
+      }
+    }
+    if(width > maxwidth)
+      maxwidth = width;
+  }
+  return maxwidth;
+}
+
+static void show_option(cli_option* o, unsigned maxwidth)
+{
+  if(o == &help_option)
+    fout << '\n';
+  if(o->ch)
+    fout << "  -" << o->ch;
+  else
+    fout << "    ";
+  fout << (o->ch && o->name ? ", " : "  ");
+  if(o->name) {
+    const char* extra = "";
+    switch(o->type) {
+    case cli_option::string:     extra = "=VALUE"; break;
+    case cli_option::integer:    extra = "=INT"; break;
+    case cli_option::uinteger:   extra = "=UNS"; break;
+    case cli_option::stringlist: extra = "=ITEM"; break;
+    case cli_option::flag:       break;
+    case cli_option::counter:    break;
+    }
+    fout << "--" << o->name << extra
+	 << fill(maxwidth - strlen(o->name) - strlen(extra) + 2);
+  }
+  else
+    fout << fill(maxwidth+4);
+  fout << o->helpstr << '\n';
+  if(o->defaultstr)
+    fout << fill(maxwidth+10) << "(Defaults to " << o->defaultstr << ")\n";
+}
+
+static void show_help()
+{
+  if(cli_help_prefix)
+    fout << cli_help_prefix;
+  unsigned maxwidth = calc_max_width();
+  for(unsigned i = 0; i < optionc; i++)
+    show_option(options[i], maxwidth);
+  if(cli_help_suffix)
+    fout << cli_help_suffix;
+}
+
+void usage(int exit_value, const char* errorstr)
+{
+  if(errorstr)
+    ferr << cli_program << ": " << errorstr << endl;
+  show_usage();
+  show_help();
+  exit(exit_value);
+}
+
+cli_stringlist* stringlist_append(cli_stringlist* node, const char* newstr)
+{
+  cli_stringlist* newnode = new cli_stringlist(newstr);
+  if(node) {
+    cli_stringlist* head = node;
+    while(node->next)
+      node = node->next;
+    node->next = newnode;
+    return head;
+  }
+  else
+    return newnode;
+}
+
+int cli_option::set(const char* arg)
+{
+  char* endptr;
+  switch(type) {
+  case flag:
+    *(int*)dataptr = flag_value;
+    return 0;
+  case counter:
+    *(int*)dataptr += flag_value;
+    return 0;
+  case integer:
+    *(int*)dataptr = strtol(arg, &endptr, 10);
+    if(*endptr) {
+      ferr << argv0 << ": invalid integer: " << arg << endl;
+      return -1;
+    }
+    return 1;
+  case uinteger:
+    *(unsigned*)dataptr = strtoul(arg, &endptr, 10);
+    if(*endptr) {
+      ferr << argv0 << ": invalid unsigned integer: " << arg << endl;
+      return -1;
+    }
+    return 1;
+  case stringlist:
+    *(cli_stringlist**)dataptr =
+      stringlist_append(*(cli_stringlist**)dataptr, arg);
+    return 1;
+  default: // string
+    *(const char**)dataptr = arg;
+    return 1;
+  }
+}
+
+static int parse_short(int argc, char* argv[])
+{
+  int end = strlen(argv[0]) - 1;
+  for(int i = 1; i <= end; i++) {
+    int ch = argv[0][i];
+    unsigned j;
+    for(j = 0; j < optionc; j++) {
+      cli_option* o = options[j];
+      if(o->ch == ch) {
+	if(o->type != cli_option::flag &&
+	   o->type != cli_option::counter) {
+	  if(i < end) {
+	    if(o->set(argv[0]+i+1) != -1)
+	      return 0;
+	  }
+	  else if(argc <= 1) {
+	    ferr << argv0 << ": option -" << o->ch
+		 << " requires a value." << endl;
+	  }
+	  else
+	    if(o->set(argv[1]) != -1)
+	      return 1;
+	}
+	else if(o->set(0) != -1)
+	  break;
+	return -1;
+      }
+    }
+    if(j >= optionc) {
+      ferr << argv0 << ": unknown option letter -" << argv[0][i] << endl;
+      return -1;
+    }
+  }
+  return 0;
+}
+
+int cli_option::parse_long_eq(const char* arg)
+{
+  if(type == flag || type == counter) {
+    ferr << argv0 << ": option --" << name
+	 << " does not take a value." << endl;
+    return -1;
+  }
+  else
+    return set(arg)-1;
+}
+
+int cli_option::parse_long_noeq(const char* arg)
+{
+  if(type == flag || type == counter)
+    return set(0);
+  else if(arg)
+    return set(arg);
+  else {
+    ferr << argv0 << ": option --" << name
+	 << " requires a value." << endl;
+    return -1;
+  }
+}
+
+static int parse_long(int, char* argv[])
+{
+  const char* arg = argv[0]+2;
+  for(unsigned j = 0; j < optionc; j++) {
+    cli_option* o = options[j];
+    if(o->name) {
+      size_t len = strlen(o->name);
+      if(!memcmp(arg, o->name, len)) {
+	if(arg[len] == '\0')
+	  return o->parse_long_noeq(argv[1]);
+	else if(arg[len] == '=')
+	  return o->parse_long_eq(arg+len+1);
+      }
+    }
+  }
+  ferr << argv0 << ": unknown option string: '--" << arg << "'" << endl;
+  return -1;
+}
+
+static int parse_args(int argc, char* argv[])
+{
+  build_options();
+  int i;
+  for(i = 1; i < argc; i++) {
+    const char* arg = argv[i];
+    // Stop at the first non-option argument
+    if(arg[0] != '-')
+      break;
+    // Stop after the first "-" or "--"
+    if(arg[1] == '\0' ||
+       (arg[1] == '-' && arg[2] == '\0')) {
+      i++;
+      break;
+    }
+    int j = (arg[1] != '-') ?
+      parse_short(argc-i, argv+i) :
+      parse_long(argc-i, argv+i);
+    if(j < 0)
+      usage(1);
+    else
+      i += j;
+  }
+  return i;
+}
+
+static void set_argv0(const char* p)
+{
+  argv0 = p;
+  static const char* empty = "";
+  const char* s = strrchr(p, '/');
+  if(s) {
+    ++s;
+    argv0base = s;
+    size_t length = s-p;
+    char* tmp = new char[length+1];
+    memcpy(tmp, p, length);
+    tmp[length] = 0;
+    argv0dir = tmp;
+  }
+  else {
+    argv0base = p;
+    argv0dir = empty;
+  }
+}
+
+int main(int argc, char* argv[])
+{
+  struct timeval tv;
+  gettimeofday(&tv, 0);
+  srandom(tv.tv_usec ^ tv.tv_sec);
+  
+  set_argv0(argv[0]);
+  int lastarg = parse_args(argc, argv);
+
+  if(do_show_usage)
+    usage(0);
+
+  argc -= lastarg;
+  argv += lastarg;
+  if(argc < cli_args_min)
+    usage(1, "Too few command-line arguments");
+  if(cli_args_max >= cli_args_min && argc > cli_args_max)
+    usage(1, "Too many command-line arguments");
+  
+  return cli_main(argc, argv);
+}