Home | History | Annotate | Download | only in tools
      1 #include <stdio.h>
      2 
      3 #include "cmdopt.h"
      4 
      5 #ifdef __cplusplus
      6 extern "C" {
      7 #endif  // __cplusplus
      8 
      9 // Moves `optind' to the end and shifts other arguments.
     10 static void cmdopt_shift(cmdopt_t *h) {
     11   int   i;
     12   char *tmp;
     13 
     14   tmp = h->argv[h->optind];
     15   for (i = h->optind; i < h->argc - 1; i++) {
     16     h->argv[i] = h->argv[i + 1];
     17   }
     18   h->argv[i] = tmp;
     19 
     20   h->nextchar = NULL;
     21   h->optnum--;
     22 }
     23 
     24 // Moves to the next argument.
     25 static void cmdopt_next(cmdopt_t *h) {
     26   h->optind++;
     27   h->nextchar = NULL;
     28 }
     29 
     30 // Checks if the current argument is an option or not.
     31 static int cmdopt_check(cmdopt_t *h) {
     32   int         ret = 1;
     33   const char *arg = h->argv[h->optind];
     34 
     35   if (*arg++ != '-') {
     36     return 0;
     37   }
     38 
     39   if (*arg == '-') {
     40     arg++;
     41     ret++;
     42   }
     43 
     44   return ret - (*arg == '\0');
     45 }
     46 
     47 // Gets an argument of the current option.
     48 static void cmdopt_getopt(cmdopt_t *h) {
     49   // Moves to the next argument if the current argument has no more characters.
     50   if (*h->nextchar == '\0') {
     51     cmdopt_next(h);
     52     h->nextchar = h->argv[h->optind];
     53   }
     54 
     55   // Checks whether the current option has an argument or not.
     56   if (h->optind < h->optnum) {
     57     h->optarg = h->nextchar;
     58     cmdopt_next(h);
     59   } else {
     60     h->optarg = NULL;
     61   }
     62 }
     63 
     64 // Searches an option.
     65 static int cmdopt_search(cmdopt_t *h) {
     66   const char *ptr;
     67 
     68   // Updates an option character.
     69   h->optopt = *h->nextchar++;
     70 
     71   for (ptr = h->optstring; *ptr != '\0'; ptr++) {
     72     if (*ptr == h->optopt) {
     73       // Gets an option argument if required.
     74       if (ptr[1] == ':') {
     75         cmdopt_getopt(h);
     76 
     77         // Returns ':' if there is no argument.
     78         if (h->optarg == NULL && ptr[2] != ':') {
     79           return ':';
     80         }
     81       }
     82       return h->optopt;
     83     }
     84   }
     85 
     86   if (h->optopt == '-') {
     87     cmdopt_next(h);
     88     while (h->optind < h->optnum) {
     89       cmdopt_shift(h);
     90     }
     91     return -1;
     92   }
     93 
     94   // Returns '?' if the option character is undefined.
     95   return '?';
     96 }
     97 
     98 // Compares a long option with an argument and returns the length of the
     99 // matched prefix.
    100 static int cmdopt_match_len(const char *opt, const char *arg) {
    101   int len = 0;
    102 
    103   // Returns 0 if there is a mismatch.
    104   while ((*arg != '\0') && (*arg != '=')) {
    105     if (*arg++ != *opt++) {
    106       return 0;
    107     }
    108     len++;
    109   }
    110 
    111   // Returns a negative value in case of a perfect match.
    112   if ((*arg == '\0') || (*arg == '=')) {
    113     return -len;
    114   }
    115 
    116   return len;
    117 }
    118 
    119 // Checks long options.
    120 static int cmdopt_match(cmdopt_t *h) {
    121   int i, len;
    122   int max = 0, max_optind = -1;
    123 
    124   // Returns -1 if there are no long options.
    125   if (h->longopts == NULL) {
    126     return max_optind;
    127   }
    128 
    129   for (i = 0; h->longopts[i].name != NULL; i++) {
    130     len = cmdopt_match_len(h->longopts[i].name, h->nextchar);
    131     if (len < 0) {
    132       // In case of a perfect match.
    133       h->nextchar -= len;
    134       return i;
    135     } else if (len > max) {
    136       // In case of a prefix match.
    137       max = len;
    138       max_optind = i;
    139     } else if (len == max) {
    140       // There are other candidates.
    141       max_optind = -1;
    142     }
    143   }
    144 
    145   // If there is no perfect match, adopts the longest one.
    146   h->nextchar += max;
    147   return max_optind;
    148 }
    149 
    150 // Gets an argument of a long option.
    151 static void cmdopt_getopt_long(cmdopt_t *h) {
    152   if (*h->nextchar == '=') {
    153     h->optarg = h->nextchar + 1;
    154     cmdopt_next(h);
    155   } else {
    156     cmdopt_next(h);
    157 
    158     // Checks whether there are more options or not.
    159     if (h->optind < h->optnum) {
    160       h->optarg = h->argv[h->optind];
    161       cmdopt_next(h);
    162     } else {
    163       h->optarg = NULL;
    164     }
    165   }
    166 }
    167 
    168 // Searches long options.
    169 static int cmdopt_search_long(cmdopt_t *h) {
    170   const cmdopt_option *option;
    171 
    172   // Keeps the long option.
    173   h->optlong = h->argv[h->optind];
    174 
    175   // Gets the next option.
    176   h->longindex = cmdopt_match(h);
    177   if (h->longindex  < 0) {
    178     cmdopt_next(h);
    179     return '?';
    180   }
    181 
    182   // Gets an argument if required.
    183   option = h->longopts + h->longindex;
    184   if (option->has_arg) {
    185     cmdopt_getopt_long(h);
    186 
    187     // Return ':' if there are no more arguments.
    188     if (h->optarg == NULL) {
    189       return ':';
    190     }
    191   } else if (*h->nextchar == '=') {
    192     // Returns '?' for an extra option argument.
    193     cmdopt_getopt_long(h);
    194     return '?';
    195   }
    196 
    197   // Overwrites a variable if specified in settings.
    198   if (option->flag != NULL) {
    199     *option->flag = option->val;
    200     return 0;
    201   }
    202 
    203   return option->val;
    204 }
    205 
    206 // Analyze command line option.
    207 static int cmdopt_main(cmdopt_t *h) {
    208   int type;
    209 
    210   // Initializes the internal state.
    211   h->optopt = 0;
    212   h->optlong = NULL;
    213   h->optarg = NULL;
    214   h->longindex = 0;
    215 
    216   while (h->optind < h->optnum) {
    217     if (h->nextchar == NULL) {
    218       // Checks whether the next argument is an option or not.
    219       type = cmdopt_check(h);
    220       if (type == 0) {
    221         cmdopt_shift(h);
    222       } else {
    223         h->nextchar = h->argv[h->optind] + type;
    224         if (type == 2) {
    225           return cmdopt_search_long(h);
    226         }
    227       }
    228     } else {
    229       if (*h->nextchar == '\0') {
    230         cmdopt_next(h);
    231         continue;
    232       }
    233       // Searches an option string.
    234       return cmdopt_search(h);
    235     }
    236   }
    237 
    238   return -1;
    239 }
    240 
    241 // cmdopt_init() initializes a cmdopt_t for successive cmdopt_get()s.
    242 void cmdopt_init(cmdopt_t *h, int argc, char **argv,
    243     const char *optstring, const cmdopt_option *longopts) {
    244   static const char empty_optstring[] = "";
    245 
    246   h->argc = argc;
    247   h->argv = argv;
    248   h->optnum = h->argc;
    249 
    250   h->longopts = longopts;
    251   h->optstring = (optstring != NULL) ? optstring : empty_optstring;
    252 
    253   h->optind = 1;
    254   h->nextchar = NULL;
    255   h->optarg = NULL;
    256   h->optopt = 0;
    257   h->optlong = NULL;
    258   h->opterr = 1;
    259   h->longindex = 0;
    260 }
    261 
    262 // cmdopt_get() analyzes command line arguments and gets the next option.
    263 int cmdopt_get(cmdopt_t *h) {
    264   int value = cmdopt_main(h);
    265 
    266   // Prints a warning to the standard error stream if enabled.
    267   if (h->opterr) {
    268     if (value == ':') {
    269       // Warning for a lack of an option argument.
    270       if (h->optlong == NULL) {
    271         fprintf(stderr, "option requires an argument -- %c\n", h->optopt);
    272       } else {
    273         fprintf(stderr, "option `--%s' requires an argument\n",
    274             h->longopts[h->longindex].name);
    275       }
    276     } else if (value == '?') {
    277       // Warning for an invalid option.
    278       if (h->optlong == NULL) {
    279         fprintf(stderr, "invalid option -- %c\n", h->optopt);
    280       } else {
    281         fprintf(stderr, "unrecognized option `%s'\n", h->optlong);
    282       }
    283     } else if ((value != -1) && (h->opterr == 2)) {
    284       // Actually this is not for warning, but for debugging.
    285       if (h->optlong == NULL) {
    286         fprintf(stderr, "option with `%s' -- %c\n", h->optarg, h->optopt);
    287       } else {
    288         fprintf(stderr, "option `--%s' with `%s'\n",
    289             h->longopts[h->longindex].name, h->optarg);
    290       }
    291     }
    292   }
    293   return value;
    294 }
    295 
    296 #ifdef __cplusplus
    297 }  // extern "C"
    298 #endif  // __cplusplus
    299