text2pdf

Git mirror of http://www.eprg.org/pdfcorner/text2pdf/
git clone git://git.finwo.net/app/text2pdf
Log | Files | Refs | README

argparse.c (10933B)


      1 /**
      2  * Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
      3  * All rights reserved.
      4  *
      5  * Use of this source code is governed by a MIT-style license that can be found
      6  * in the LICENSE file.
      7  */
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <assert.h>
     12 #include <errno.h>
     13 #include "argparse.h"
     14 
     15 #define OPT_UNSET 1
     16 #define OPT_LONG  (1 << 1)
     17 
     18 static const char *
     19 prefix_skip(const char *str, const char *prefix)
     20 {
     21     size_t len = strlen(prefix);
     22     return strncmp(str, prefix, len) ? NULL : str + len;
     23 }
     24 
     25 static int
     26 prefix_cmp(const char *str, const char *prefix)
     27 {
     28     for (;; str++, prefix++)
     29         if (!*prefix) {
     30             return 0;
     31         } else if (*str != *prefix) {
     32             return (unsigned char)*prefix - (unsigned char)*str;
     33         }
     34 }
     35 
     36 static void
     37 argparse_error(struct argparse *self, const struct argparse_option *opt,
     38                const char *reason, int flags)
     39 {
     40     (void)self;
     41     if (flags & OPT_LONG) {
     42         fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
     43     } else {
     44         fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
     45     }
     46     exit(1);
     47 }
     48 
     49 static int
     50 argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
     51                   int flags)
     52 {
     53     const char *s = NULL;
     54     if (!opt->value)
     55         goto skipped;
     56     switch (opt->type) {
     57     case ARGPARSE_OPT_BOOLEAN:
     58         if (flags & OPT_UNSET) {
     59             *(int *)opt->value = *(int *)opt->value - 1;
     60         } else {
     61             *(int *)opt->value = *(int *)opt->value + 1;
     62         }
     63         if (*(int *)opt->value < 0) {
     64             *(int *)opt->value = 0;
     65         }
     66         break;
     67     case ARGPARSE_OPT_BIT:
     68         if (flags & OPT_UNSET) {
     69             *(int *)opt->value &= ~opt->data;
     70         } else {
     71             *(int *)opt->value |= opt->data;
     72         }
     73         break;
     74     case ARGPARSE_OPT_STRING:
     75         if (self->optvalue) {
     76             *(const char **)opt->value = self->optvalue;
     77             self->optvalue             = NULL;
     78         } else if (self->argc > 1) {
     79             self->argc--;
     80             *(const char **)opt->value = *++self->argv;
     81         } else {
     82             argparse_error(self, opt, "requires a value", flags);
     83         }
     84         break;
     85     case ARGPARSE_OPT_INTEGER:
     86         errno = 0;
     87         if (self->optvalue) {
     88             *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
     89             self->optvalue     = NULL;
     90         } else if (self->argc > 1) {
     91             self->argc--;
     92             *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
     93         } else {
     94             argparse_error(self, opt, "requires a value", flags);
     95         }
     96         if (errno)
     97             argparse_error(self, opt, strerror(errno), flags);
     98         if (s[0] != '\0')
     99             argparse_error(self, opt, "expects an integer value", flags);
    100         break;
    101     case ARGPARSE_OPT_FLOAT:
    102         errno = 0;
    103         if (self->optvalue) {
    104             *(float *)opt->value = strtof(self->optvalue, (char **)&s);
    105             self->optvalue       = NULL;
    106         } else if (self->argc > 1) {
    107             self->argc--;
    108             *(float *)opt->value = strtof(*++self->argv, (char **)&s);
    109         } else {
    110             argparse_error(self, opt, "requires a value", flags);
    111         }
    112         if (errno)
    113             argparse_error(self, opt, strerror(errno), flags);
    114         if (s[0] != '\0')
    115             argparse_error(self, opt, "expects a numerical value", flags);
    116         break;
    117     default:
    118         assert(0);
    119     }
    120 
    121 skipped:
    122     if (opt->callback) {
    123         return opt->callback(self, opt);
    124     }
    125 
    126     return 0;
    127 }
    128 
    129 static void
    130 argparse_options_check(const struct argparse_option *options)
    131 {
    132     for (; options->type != ARGPARSE_OPT_END; options++) {
    133         switch (options->type) {
    134         case ARGPARSE_OPT_END:
    135         case ARGPARSE_OPT_BOOLEAN:
    136         case ARGPARSE_OPT_BIT:
    137         case ARGPARSE_OPT_INTEGER:
    138         case ARGPARSE_OPT_FLOAT:
    139         case ARGPARSE_OPT_STRING:
    140         case ARGPARSE_OPT_GROUP:
    141             continue;
    142         default:
    143             fprintf(stderr, "wrong option type: %d", options->type);
    144             break;
    145         }
    146     }
    147 }
    148 
    149 static int
    150 argparse_short_opt(struct argparse *self, const struct argparse_option *options)
    151 {
    152     for (; options->type != ARGPARSE_OPT_END; options++) {
    153         if (options->short_name == *self->optvalue) {
    154             self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
    155             return argparse_getvalue(self, options, 0);
    156         }
    157     }
    158     return -2;
    159 }
    160 
    161 static int
    162 argparse_long_opt(struct argparse *self, const struct argparse_option *options)
    163 {
    164     for (; options->type != ARGPARSE_OPT_END; options++) {
    165         const char *rest;
    166         int opt_flags = 0;
    167         if (!options->long_name)
    168             continue;
    169 
    170         rest = prefix_skip(self->argv[0] + 2, options->long_name);
    171         if (!rest) {
    172             // negation disabled?
    173             if (options->flags & OPT_NONEG) {
    174                 continue;
    175             }
    176             // only OPT_BOOLEAN/OPT_BIT supports negation
    177             if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
    178                 ARGPARSE_OPT_BIT) {
    179                 continue;
    180             }
    181 
    182             if (prefix_cmp(self->argv[0] + 2, "no-")) {
    183                 continue;
    184             }
    185             rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
    186             if (!rest)
    187                 continue;
    188             opt_flags |= OPT_UNSET;
    189         }
    190         if (*rest) {
    191             if (*rest != '=')
    192                 continue;
    193             self->optvalue = rest + 1;
    194         }
    195         return argparse_getvalue(self, options, opt_flags | OPT_LONG);
    196     }
    197     return -2;
    198 }
    199 
    200 int
    201 argparse_init(struct argparse *self, struct argparse_option *options,
    202               const char *const *usages, int flags)
    203 {
    204     memset(self, 0, sizeof(*self));
    205     self->options     = options;
    206     self->usages      = usages;
    207     self->flags       = flags;
    208     self->description = NULL;
    209     self->epilog      = NULL;
    210     return 0;
    211 }
    212 
    213 void
    214 argparse_describe(struct argparse *self, const char *description,
    215                   const char *epilog)
    216 {
    217     self->description = description;
    218     self->epilog      = epilog;
    219 }
    220 
    221 int
    222 argparse_parse(struct argparse *self, int argc, const char **argv)
    223 {
    224     self->argc = argc - 1;
    225     self->argv = argv + 1;
    226     self->out  = argv;
    227 
    228     argparse_options_check(self->options);
    229 
    230     for (; self->argc; self->argc--, self->argv++) {
    231         const char *arg = self->argv[0];
    232         if (arg[0] != '-' || !arg[1]) {
    233             if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
    234                 goto end;
    235             }
    236             // if it's not option or is a single char '-', copy verbatim
    237             self->out[self->cpidx++] = self->argv[0];
    238             continue;
    239         }
    240         // short option
    241         if (arg[1] != '-') {
    242             self->optvalue = arg + 1;
    243             switch (argparse_short_opt(self, self->options)) {
    244             case -1:
    245                 break;
    246             case -2:
    247                 goto unknown;
    248             }
    249             while (self->optvalue) {
    250                 switch (argparse_short_opt(self, self->options)) {
    251                 case -1:
    252                     break;
    253                 case -2:
    254                     goto unknown;
    255                 }
    256             }
    257             continue;
    258         }
    259         // if '--' presents
    260         if (!arg[2]) {
    261             self->argc--;
    262             self->argv++;
    263             break;
    264         }
    265         // long option
    266         switch (argparse_long_opt(self, self->options)) {
    267         case -1:
    268             break;
    269         case -2:
    270             goto unknown;
    271         }
    272         continue;
    273 
    274 unknown:
    275         fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
    276         argparse_usage(self);
    277         exit(1);
    278     }
    279 
    280 end:
    281     memmove(self->out + self->cpidx, self->argv,
    282             self->argc * sizeof(*self->out));
    283     self->out[self->cpidx + self->argc] = NULL;
    284 
    285     return self->cpidx + self->argc;
    286 }
    287 
    288 void
    289 argparse_usage(struct argparse *self)
    290 {
    291     if (self->usages) {
    292         fprintf(stdout, "Usage: %s\n", *self->usages++);
    293         while (*self->usages && **self->usages)
    294             fprintf(stdout, "   or: %s\n", *self->usages++);
    295     } else {
    296         fprintf(stdout, "Usage:\n");
    297     }
    298 
    299     // print description
    300     if (self->description)
    301         fprintf(stdout, "%s\n", self->description);
    302 
    303     fputc('\n', stdout);
    304 
    305     const struct argparse_option *options;
    306 
    307     // figure out best width
    308     size_t usage_opts_width = 0;
    309     size_t len;
    310     options = self->options;
    311     for (; options->type != ARGPARSE_OPT_END; options++) {
    312         len = 0;
    313         if ((options)->short_name) {
    314             len += 2;
    315         }
    316         if ((options)->short_name && (options)->long_name) {
    317             len += 2;           // separator ", "
    318         }
    319         if ((options)->long_name) {
    320             len += strlen((options)->long_name) + 2;
    321         }
    322         if (options->type == ARGPARSE_OPT_INTEGER) {
    323             len += strlen("=<int>");
    324         }
    325         if (options->type == ARGPARSE_OPT_FLOAT) {
    326             len += strlen("=<flt>");
    327         } else if (options->type == ARGPARSE_OPT_STRING) {
    328             len += strlen("=<str>");
    329         }
    330         len = (len + 3) - ((len + 3) & 3);
    331         if (usage_opts_width < len) {
    332             usage_opts_width = len;
    333         }
    334     }
    335     usage_opts_width += 4;      // 4 spaces prefix
    336 
    337     options = self->options;
    338     for (; options->type != ARGPARSE_OPT_END; options++) {
    339         size_t pos = 0;
    340         int pad    = 0;
    341         if (options->type == ARGPARSE_OPT_GROUP) {
    342             fputc('\n', stdout);
    343             fprintf(stdout, "%s", options->help);
    344             fputc('\n', stdout);
    345             continue;
    346         }
    347         pos = fprintf(stdout, "    ");
    348         if (options->short_name) {
    349             pos += fprintf(stdout, "-%c", options->short_name);
    350         }
    351         if (options->long_name && options->short_name) {
    352             pos += fprintf(stdout, ", ");
    353         }
    354         if (options->long_name) {
    355             pos += fprintf(stdout, "--%s", options->long_name);
    356         }
    357         if (options->type == ARGPARSE_OPT_INTEGER) {
    358             pos += fprintf(stdout, "=<int>");
    359         } else if (options->type == ARGPARSE_OPT_FLOAT) {
    360             pos += fprintf(stdout, "=<flt>");
    361         } else if (options->type == ARGPARSE_OPT_STRING) {
    362             pos += fprintf(stdout, "=<str>");
    363         }
    364         if (pos <= usage_opts_width) {
    365             pad = usage_opts_width - pos;
    366         } else {
    367             fputc('\n', stdout);
    368             pad = usage_opts_width;
    369         }
    370         fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
    371     }
    372 
    373     // print epilog
    374     if (self->epilog)
    375         fprintf(stdout, "%s\n", self->epilog);
    376 }
    377 
    378 int
    379 argparse_help_cb(struct argparse *self, const struct argparse_option *option)
    380 {
    381     (void)option;
    382     argparse_usage(self);
    383     exit(0);
    384 }