lib/cli++/main.cc
changeset 2 b3afb9f1e801
parent 0 6f7a81934006
equal deleted inserted replaced
1:30113bfbe723 2:b3afb9f1e801
       
     1 // Copyright (C) 1999,2000 Bruce Guenter <bruceg@em.ca>
       
     2 //
       
     3 // This program is free software; you can redistribute it and/or modify
       
     4 // it under the terms of the GNU General Public License as published by
       
     5 // the Free Software Foundation; either version 2 of the License, or
       
     6 // (at your option) any later version.
       
     7 //
       
     8 // This program is distributed in the hope that it will be useful,
       
     9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    11 // GNU General Public License for more details.
       
    12 //
       
    13 // You should have received a copy of the GNU General Public License
       
    14 // along with this program; if not, write to the Free Software
       
    15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    16 
       
    17 #include <config.h>
       
    18 #include "ac/time.h"
       
    19 #include "fdbuf/fdbuf.h"
       
    20 #include <stdlib.h>
       
    21 #include <string.h>
       
    22 #include "cli++.h"
       
    23 
       
    24 #ifndef HAVE_SRANDOM
       
    25 void srandom(unsigned int seed);
       
    26 #endif
       
    27 
       
    28 static bool do_show_usage = false;
       
    29 const char* argv0;
       
    30 const char* argv0base;
       
    31 const char* argv0dir;
       
    32 
       
    33 static cli_option help_option = { 'h', "help", cli_option::flag,
       
    34 				  true, &do_show_usage,
       
    35 				  "Display this help and exit", 0 };
       
    36 
       
    37 static cli_option** options;
       
    38 static unsigned optionc;
       
    39 
       
    40 static void build_options()
       
    41 {
       
    42   for(optionc = 0;
       
    43       cli_options[optionc].ch || cli_options[optionc].name;
       
    44       optionc++) ;
       
    45   optionc++;
       
    46   options = new cli_option*[optionc];
       
    47   for(unsigned i = 0; i < optionc-1; i++)
       
    48     options[i] = &cli_options[i];
       
    49   options[optionc-1] = &help_option;
       
    50 }
       
    51 
       
    52 static inline unsigned max(unsigned a, unsigned b)
       
    53 {
       
    54   return (a>b) ? a : b;
       
    55 }
       
    56 
       
    57 static const char* fill(unsigned i)
       
    58 {
       
    59   static unsigned lastlen = 0;
       
    60   static char* buf = 0;
       
    61   if(i > lastlen) {
       
    62     delete[] buf;
       
    63     buf = new char[i+1];
       
    64     lastlen = i;
       
    65   }
       
    66   memset(buf, ' ', i);
       
    67   buf[i] = 0;
       
    68   return buf;
       
    69 }
       
    70   
       
    71 static void show_usage()
       
    72 {
       
    73   fout << "usage: " << cli_program << " [flags] " << cli_args_usage << endl;
       
    74 }
       
    75 
       
    76 static unsigned calc_max_width()
       
    77 {
       
    78   // maxwidth is the maximum width of the long argument
       
    79   unsigned maxwidth = 0;
       
    80   for(unsigned i = 0; i < optionc; i++) {
       
    81     unsigned width = 0;
       
    82     cli_option* o = options[i];
       
    83     if(o->name) {
       
    84       width += strlen(o->name);
       
    85       switch(o->type) {
       
    86       case cli_option::string:     width += 6; break;
       
    87       case cli_option::integer:    width += 4; break;
       
    88       case cli_option::uinteger:   width += 4; break;
       
    89       case cli_option::stringlist: width += 5; break;
       
    90       case cli_option::flag:       break;
       
    91       case cli_option::counter:    break;
       
    92       }
       
    93     }
       
    94     if(width > maxwidth)
       
    95       maxwidth = width;
       
    96   }
       
    97   return maxwidth;
       
    98 }
       
    99 
       
   100 static void show_option(cli_option* o, unsigned maxwidth)
       
   101 {
       
   102   if(o == &help_option)
       
   103     fout << '\n';
       
   104   if(o->ch)
       
   105     fout << "  -" << o->ch;
       
   106   else
       
   107     fout << "    ";
       
   108   fout << (o->ch && o->name ? ", " : "  ");
       
   109   if(o->name) {
       
   110     const char* extra = "";
       
   111     switch(o->type) {
       
   112     case cli_option::string:     extra = "=VALUE"; break;
       
   113     case cli_option::integer:    extra = "=INT"; break;
       
   114     case cli_option::uinteger:   extra = "=UNS"; break;
       
   115     case cli_option::stringlist: extra = "=ITEM"; break;
       
   116     case cli_option::flag:       break;
       
   117     case cli_option::counter:    break;
       
   118     }
       
   119     fout << "--" << o->name << extra
       
   120 	 << fill(maxwidth - strlen(o->name) - strlen(extra) + 2);
       
   121   }
       
   122   else
       
   123     fout << fill(maxwidth+4);
       
   124   fout << o->helpstr << '\n';
       
   125   if(o->defaultstr)
       
   126     fout << fill(maxwidth+10) << "(Defaults to " << o->defaultstr << ")\n";
       
   127 }
       
   128 
       
   129 static void show_help()
       
   130 {
       
   131   if(cli_help_prefix)
       
   132     fout << cli_help_prefix;
       
   133   unsigned maxwidth = calc_max_width();
       
   134   for(unsigned i = 0; i < optionc; i++)
       
   135     show_option(options[i], maxwidth);
       
   136   if(cli_help_suffix)
       
   137     fout << cli_help_suffix;
       
   138 }
       
   139 
       
   140 void usage(int exit_value, const char* errorstr)
       
   141 {
       
   142   if(errorstr)
       
   143     ferr << cli_program << ": " << errorstr << endl;
       
   144   show_usage();
       
   145   show_help();
       
   146   exit(exit_value);
       
   147 }
       
   148 
       
   149 cli_stringlist* stringlist_append(cli_stringlist* node, const char* newstr)
       
   150 {
       
   151   cli_stringlist* newnode = new cli_stringlist(newstr);
       
   152   if(node) {
       
   153     cli_stringlist* head = node;
       
   154     while(node->next)
       
   155       node = node->next;
       
   156     node->next = newnode;
       
   157     return head;
       
   158   }
       
   159   else
       
   160     return newnode;
       
   161 }
       
   162 
       
   163 int cli_option::set(const char* arg)
       
   164 {
       
   165   char* endptr;
       
   166   switch(type) {
       
   167   case flag:
       
   168     *(int*)dataptr = flag_value;
       
   169     return 0;
       
   170   case counter:
       
   171     *(int*)dataptr += flag_value;
       
   172     return 0;
       
   173   case integer:
       
   174     *(int*)dataptr = strtol(arg, &endptr, 10);
       
   175     if(*endptr) {
       
   176       ferr << argv0 << ": invalid integer: " << arg << endl;
       
   177       return -1;
       
   178     }
       
   179     return 1;
       
   180   case uinteger:
       
   181     *(unsigned*)dataptr = strtoul(arg, &endptr, 10);
       
   182     if(*endptr) {
       
   183       ferr << argv0 << ": invalid unsigned integer: " << arg << endl;
       
   184       return -1;
       
   185     }
       
   186     return 1;
       
   187   case stringlist:
       
   188     *(cli_stringlist**)dataptr =
       
   189       stringlist_append(*(cli_stringlist**)dataptr, arg);
       
   190     return 1;
       
   191   default: // string
       
   192     *(const char**)dataptr = arg;
       
   193     return 1;
       
   194   }
       
   195 }
       
   196 
       
   197 static int parse_short(int argc, char* argv[])
       
   198 {
       
   199   int end = strlen(argv[0]) - 1;
       
   200   for(int i = 1; i <= end; i++) {
       
   201     int ch = argv[0][i];
       
   202     unsigned j;
       
   203     for(j = 0; j < optionc; j++) {
       
   204       cli_option* o = options[j];
       
   205       if(o->ch == ch) {
       
   206 	if(o->type != cli_option::flag &&
       
   207 	   o->type != cli_option::counter) {
       
   208 	  if(i < end) {
       
   209 	    if(o->set(argv[0]+i+1) != -1)
       
   210 	      return 0;
       
   211 	  }
       
   212 	  else if(argc <= 1) {
       
   213 	    ferr << argv0 << ": option -" << o->ch
       
   214 		 << " requires a value." << endl;
       
   215 	  }
       
   216 	  else
       
   217 	    if(o->set(argv[1]) != -1)
       
   218 	      return 1;
       
   219 	}
       
   220 	else if(o->set(0) != -1)
       
   221 	  break;
       
   222 	return -1;
       
   223       }
       
   224     }
       
   225     if(j >= optionc) {
       
   226       ferr << argv0 << ": unknown option letter -" << argv[0][i] << endl;
       
   227       return -1;
       
   228     }
       
   229   }
       
   230   return 0;
       
   231 }
       
   232 
       
   233 int cli_option::parse_long_eq(const char* arg)
       
   234 {
       
   235   if(type == flag || type == counter) {
       
   236     ferr << argv0 << ": option --" << name
       
   237 	 << " does not take a value." << endl;
       
   238     return -1;
       
   239   }
       
   240   else
       
   241     return set(arg)-1;
       
   242 }
       
   243 
       
   244 int cli_option::parse_long_noeq(const char* arg)
       
   245 {
       
   246   if(type == flag || type == counter)
       
   247     return set(0);
       
   248   else if(arg)
       
   249     return set(arg);
       
   250   else {
       
   251     ferr << argv0 << ": option --" << name
       
   252 	 << " requires a value." << endl;
       
   253     return -1;
       
   254   }
       
   255 }
       
   256 
       
   257 static int parse_long(int, char* argv[])
       
   258 {
       
   259   const char* arg = argv[0]+2;
       
   260   for(unsigned j = 0; j < optionc; j++) {
       
   261     cli_option* o = options[j];
       
   262     if(o->name) {
       
   263       size_t len = strlen(o->name);
       
   264       if(!memcmp(arg, o->name, len)) {
       
   265 	if(arg[len] == '\0')
       
   266 	  return o->parse_long_noeq(argv[1]);
       
   267 	else if(arg[len] == '=')
       
   268 	  return o->parse_long_eq(arg+len+1);
       
   269       }
       
   270     }
       
   271   }
       
   272   ferr << argv0 << ": unknown option string: '--" << arg << "'" << endl;
       
   273   return -1;
       
   274 }
       
   275 
       
   276 static int parse_args(int argc, char* argv[])
       
   277 {
       
   278   build_options();
       
   279   int i;
       
   280   for(i = 1; i < argc; i++) {
       
   281     const char* arg = argv[i];
       
   282     // Stop at the first non-option argument
       
   283     if(arg[0] != '-')
       
   284       break;
       
   285     // Stop after the first "-" or "--"
       
   286     if(arg[1] == '\0' ||
       
   287        (arg[1] == '-' && arg[2] == '\0')) {
       
   288       i++;
       
   289       break;
       
   290     }
       
   291     int j = (arg[1] != '-') ?
       
   292       parse_short(argc-i, argv+i) :
       
   293       parse_long(argc-i, argv+i);
       
   294     if(j < 0)
       
   295       usage(1);
       
   296     else
       
   297       i += j;
       
   298   }
       
   299   return i;
       
   300 }
       
   301 
       
   302 static void set_argv0(const char* p)
       
   303 {
       
   304   argv0 = p;
       
   305   static const char* empty = "";
       
   306   const char* s = strrchr(p, '/');
       
   307   if(s) {
       
   308     ++s;
       
   309     argv0base = s;
       
   310     size_t length = s-p;
       
   311     char* tmp = new char[length+1];
       
   312     memcpy(tmp, p, length);
       
   313     tmp[length] = 0;
       
   314     argv0dir = tmp;
       
   315   }
       
   316   else {
       
   317     argv0base = p;
       
   318     argv0dir = empty;
       
   319   }
       
   320 }
       
   321 
       
   322 int main(int argc, char* argv[])
       
   323 {
       
   324   struct timeval tv;
       
   325   gettimeofday(&tv, 0);
       
   326   srandom(tv.tv_usec ^ tv.tv_sec);
       
   327   
       
   328   set_argv0(argv[0]);
       
   329   int lastarg = parse_args(argc, argv);
       
   330 
       
   331   if(do_show_usage)
       
   332     usage(0);
       
   333 
       
   334   argc -= lastarg;
       
   335   argv += lastarg;
       
   336   if(argc < cli_args_min)
       
   337     usage(1, "Too few command-line arguments");
       
   338   if(cli_args_max >= cli_args_min && argc > cli_args_max)
       
   339     usage(1, "Too many command-line arguments");
       
   340   
       
   341   return cli_main(argc, argv);
       
   342 }