ref: 68a4d1cef5db92f15102ec14ae327430150e8d56
dir: /parg.c/
/* * parg - parse argv * * Written in 2015-2016 by Joergen Ibsen * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to the * public domain worldwide. This software is distributed without any * warranty. <http://creativecommons.org/publicdomain/zero/1.0/> */ #include "parg.h" #include <assert.h> #include <stdlib.h> #include <string.h> /* * Check if state is at end of argv. */ static int is_argv_end(const struct parg_state *ps, int argc, char *const argv[]) { return ps->optind >= argc || argv[ps->optind] == NULL; } /* * Match nextchar against optstring. */ static int match_short(struct parg_state *ps, int argc, char *const argv[], const char *optstring) { const char *p = strchr(optstring, *ps->nextchar); if (p == NULL) { ps->optopt = *ps->nextchar++; return '?'; } /* If no option argument, return option */ if (p[1] != ':') { return *ps->nextchar++; } /* If more characters, return as option argument */ if (ps->nextchar[1] != '\0') { ps->optarg = &ps->nextchar[1]; ps->nextchar = NULL; return *p; } /* If option argument is optional, return option */ if (p[2] == ':') { return *ps->nextchar++; } /* Option argument required, so return next argv element */ if (is_argv_end(ps, argc, argv)) { ps->optopt = *ps->nextchar++; return optstring[0] == ':' ? ':' : '?'; } ps->optarg = argv[ps->optind++]; ps->nextchar = NULL; return *p; } /* * Match string at nextchar against longopts. */ static int match_long(struct parg_state *ps, int argc, char *const argv[], const char *optstring, const struct parg_option *longopts, int *longindex) { size_t len; int num_match = 0; int match = -1; int i; len = strcspn(ps->nextchar, "="); for (i = 0; longopts[i].name; ++i) { if (strncmp(ps->nextchar, longopts[i].name, len) == 0) { match = i; num_match++; /* Take if exact match */ if (longopts[i].name[len] == '\0') { num_match = 1; break; } } } /* Return '?' on no or ambiguous match */ if (num_match != 1) { ps->optopt = 0; ps->nextchar = NULL; return '?'; } assert(match != -1); if (longindex) { *longindex = match; } if (ps->nextchar[len] == '=') { /* Option argument present, check if extraneous */ if (longopts[match].has_arg == PARG_NOARG) { ps->optopt = longopts[match].flag ? 0 : longopts[match].val; ps->nextchar = NULL; return optstring[0] == ':' ? ':' : '?'; } else { ps->optarg = &ps->nextchar[len + 1]; } } else if (longopts[match].has_arg == PARG_REQARG) { /* Option argument required, so return next argv element */ if (is_argv_end(ps, argc, argv)) { ps->optopt = longopts[match].flag ? 0 : longopts[match].val; ps->nextchar = NULL; return optstring[0] == ':' ? ':' : '?'; } ps->optarg = argv[ps->optind++]; } ps->nextchar = NULL; if (longopts[match].flag != NULL) { *longopts[match].flag = longopts[match].val; return 0; } return longopts[match].val; } void parg_init(struct parg_state *ps) { ps->optarg = NULL; ps->optind = 1; ps->optopt = '?'; ps->nextchar = NULL; } int parg_getopt(struct parg_state *ps, int argc, char *const argv[], const char *optstring) { return parg_getopt_long(ps, argc, argv, optstring, NULL, NULL); } int parg_getopt_long(struct parg_state *ps, int argc, char *const argv[], const char *optstring, const struct parg_option *longopts, int *longindex) { assert(ps != NULL); assert(argv != NULL); assert(optstring != NULL); ps->optarg = NULL; if (argc < 2) { return -1; } /* Advance to next element if needed */ if (ps->nextchar == NULL || *ps->nextchar == '\0') { if (is_argv_end(ps, argc, argv)) { return -1; } ps->nextchar = argv[ps->optind++]; /* Check for nonoption element (including '-') */ if (ps->nextchar[0] != '-' || ps->nextchar[1] == '\0') { ps->optarg = ps->nextchar; ps->nextchar = NULL; return 1; } /* Check for '--' */ if (ps->nextchar[1] == '-') { if (ps->nextchar[2] == '\0') { ps->nextchar = NULL; return -1; } if (longopts != NULL) { ps->nextchar += 2; return match_long(ps, argc, argv, optstring, longopts, longindex); } } ps->nextchar++; } /* Match nextchar */ return match_short(ps, argc, argv, optstring); } /* * Reverse elements of `v` from `i` to `j`. */ static void reverse(char *v[], int i, int j) { while (j - i > 1) { char *tmp = v[i]; v[i] = v[j - 1]; v[j - 1] = tmp; ++i; --j; } } /* * Reorder elements of `argv` with no special cases. * * This function assumes there is no `--` element, and the last element * is not an option missing a required argument. * * The algorithm is described here: * http://hardtoc.com/2016/11/07/reordering-arguments.html */ static int parg_reorder_simple(int argc, char *argv[], const char *optstring, const struct parg_option *longopts) { struct parg_state ps; int change; int l = 0; int m = 0; int r = 0; if (argc < 2) { return argc; } do { int nextind; int c; parg_init(&ps); nextind = ps.optind; /* Parse until end of argument */ do { c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); } while (ps.nextchar != NULL && *ps.nextchar != '\0'); change = 0; do { /* Find next non-option */ for (l = nextind; c != 1 && c != -1;) { l = ps.optind; do { c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); } while (ps.nextchar != NULL && *ps.nextchar != '\0'); } /* Find next option */ for (m = l; c == 1;) { m = ps.optind; do { c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); } while (ps.nextchar != NULL && *ps.nextchar != '\0'); } /* Find next non-option */ for (r = m; c != 1 && c != -1;) { r = ps.optind; do { c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); } while (ps.nextchar != NULL && *ps.nextchar != '\0'); } /* Find next option */ for (nextind = r; c == 1;) { nextind = ps.optind; do { c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); } while (ps.nextchar != NULL && *ps.nextchar != '\0'); } if (m < r) { change = 1; reverse(argv, l, m); reverse(argv, m, r); reverse(argv, l, r); } } while (c != -1); } while (change != 0); return l + (r - m); } int parg_reorder(int argc, char *argv[], const char *optstring, const struct parg_option *longopts) { struct parg_state ps; int lastind; int optend; int c; assert(argv != NULL); assert(optstring != NULL); if (argc < 2) { return argc; } parg_init(&ps); /* Find end of normal arguments */ do { lastind = ps.optind; c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); /* Check for trailing option with error */ if ((c == '?' || c == ':') && is_argv_end(&ps, argc, argv)) { lastind = ps.optind - 1; break; } } while (c != -1); optend = parg_reorder_simple(lastind, argv, optstring, longopts); /* Rotate `--` or trailing option with error into position */ if (lastind < argc) { reverse(argv, optend, lastind); reverse(argv, optend, lastind + 1); ++optend; } return optend; }