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 }