| 1 | #include "argparse/libargparse.h" |
| 2 | |
| 3 | #include <stdio.h> |
| 4 | |
| 5 | #define ARGPARSE_MSG_INVALID "invalid option" |
| 6 | #define ARGPARSE_MSG_MISSING "option requires an argument" |
| 7 | #define ARGPARSE_MSG_TOOMANY "option takes no arguments" |
| 8 | |
| 9 | static int argparse_error(argparse_state_t *state, const char *msg, const char *data) |
| 10 | { |
| 11 | unsigned p = 0; |
| 12 | const char *sep = " -- '" ; |
| 13 | while (*msg) |
| 14 | state->errmsg[p++] = *msg++; |
| 15 | while (*sep) |
| 16 | state->errmsg[p++] = *sep++; |
| 17 | while (p < sizeof(state->errmsg) - 2 && *data) |
| 18 | state->errmsg[p++] = *data++; |
| 19 | state->errmsg[p++] = '\''; |
| 20 | state->errmsg[p++] = '\0'; |
| 21 | return '?'; |
| 22 | } |
| 23 | |
| 24 | void argparse_init(argparse_state_t *state, const char *argv[]) |
| 25 | { |
| 26 | state->argv = argv; |
| 27 | state->permute = 1; |
| 28 | state->optind = argv[0] != 0; |
| 29 | state->subopt = 0; |
| 30 | state->optarg = 0; |
| 31 | state->errmsg[0] = '\0'; |
| 32 | } |
| 33 | |
| 34 | static int argparse_is_dashdash(const char *arg) |
| 35 | { |
| 36 | return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; |
| 37 | } |
| 38 | |
| 39 | static int argparse_is_shortopt(const char *arg) |
| 40 | { |
| 41 | return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; |
| 42 | } |
| 43 | |
| 44 | static int argparse_is_longopt(const char *arg) |
| 45 | { |
| 46 | return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; |
| 47 | } |
| 48 | |
| 49 | static void argparse_permute(argparse_state_t *state, int index) |
| 50 | { |
| 51 | const char *nonoption = state->argv[index]; |
| 52 | int i; |
| 53 | for (i = index; i < state->optind - 1; i++) |
| 54 | state->argv[i] = state->argv[i + 1]; |
| 55 | state->argv[state->optind - 1] = nonoption; |
| 56 | } |
| 57 | |
| 58 | static int argparse_get_argtype(const char *optstring, char c) |
| 59 | { |
| 60 | int count = ARGPARSE_NONE; |
| 61 | if (c == ':') |
| 62 | return -1; |
| 63 | for (; *optstring && c != *optstring; optstring++) |
| 64 | ; |
| 65 | if (!*optstring) |
| 66 | return -1; |
| 67 | if (optstring[1] == ':') |
| 68 | count += optstring[2] == ':' ? 2 : 1; |
| 69 | return count; |
| 70 | } |
| 71 | |
| 72 | int argparse(argparse_state_t *state, const char *optstring) |
| 73 | { |
| 74 | int type; |
| 75 | const char *next; |
| 76 | const char *option = state->argv[state->optind]; |
| 77 | state->errmsg[0] = '\0'; |
| 78 | state->optopt = 0; |
| 79 | state->optarg = 0; |
| 80 | if (option == 0) |
| 81 | { |
| 82 | return -1; |
| 83 | } |
| 84 | else if (argparse_is_dashdash(arg: option)) |
| 85 | { |
| 86 | state->optind++; /* consume "--" */ |
| 87 | return -1; |
| 88 | } |
| 89 | else if (!argparse_is_shortopt(arg: option)) |
| 90 | { |
| 91 | if (state->permute) |
| 92 | { |
| 93 | int index = state->optind++; |
| 94 | int r = argparse(state, optstring); |
| 95 | argparse_permute(state, index); |
| 96 | state->optind--; |
| 97 | return r; |
| 98 | } |
| 99 | else |
| 100 | { |
| 101 | return -1; |
| 102 | } |
| 103 | } |
| 104 | option += state->subopt + 1; |
| 105 | state->optopt = option[0]; |
| 106 | type = argparse_get_argtype(optstring, c: option[0]); |
| 107 | next = state->argv[state->optind + 1]; |
| 108 | switch (type) |
| 109 | { |
| 110 | case -1: |
| 111 | { |
| 112 | char str[2] = { 0, 0 }; |
| 113 | str[0] = option[0]; |
| 114 | state->optind++; |
| 115 | return argparse_error(state, ARGPARSE_MSG_INVALID, data: str); |
| 116 | } |
| 117 | case ARGPARSE_NONE: |
| 118 | if (option[1]) |
| 119 | { |
| 120 | state->subopt++; |
| 121 | } |
| 122 | else |
| 123 | { |
| 124 | state->subopt = 0; |
| 125 | state->optind++; |
| 126 | } |
| 127 | return option[0]; |
| 128 | case ARGPARSE_REQUIRED: |
| 129 | state->subopt = 0; |
| 130 | state->optind++; |
| 131 | if (option[1]) |
| 132 | { |
| 133 | state->optarg = option + 1; |
| 134 | } |
| 135 | else if (next != 0) |
| 136 | { |
| 137 | state->optarg = next; |
| 138 | state->optind++; |
| 139 | } |
| 140 | else |
| 141 | { |
| 142 | char str[2] = { 0, 0 }; |
| 143 | str[0] = option[0]; |
| 144 | state->optarg = 0; |
| 145 | return argparse_error(state, ARGPARSE_MSG_MISSING, data: str); |
| 146 | } |
| 147 | return option[0]; |
| 148 | case ARGPARSE_OPTIONAL: |
| 149 | state->subopt = 0; |
| 150 | state->optind++; |
| 151 | if (option[1]) |
| 152 | state->optarg = option + 1; |
| 153 | else |
| 154 | state->optarg = 0; |
| 155 | return option[0]; |
| 156 | } |
| 157 | return 0; |
| 158 | } |
| 159 | |
| 160 | const char *argparse_arg(argparse_state_t *options) |
| 161 | { |
| 162 | const char *option = options->argv[options->optind]; |
| 163 | options->subopt = 0; |
| 164 | if (option != 0) |
| 165 | options->optind++; |
| 166 | return option; |
| 167 | } |
| 168 | |
| 169 | static int argparse_longopts_end(const argparse_arg_t *longopts, int i) |
| 170 | { |
| 171 | return !longopts[i].full && !longopts[i].abbr; |
| 172 | } |
| 173 | |
| 174 | static void argparse_from_long(const argparse_arg_t *longopts, char *optstring) |
| 175 | { |
| 176 | char *p = optstring; |
| 177 | int i; |
| 178 | for (i = 0; !argparse_longopts_end(longopts, i); i++) |
| 179 | { |
| 180 | if (longopts[i].abbr && longopts[i].abbr < 127) |
| 181 | { |
| 182 | int a; |
| 183 | *p++ = longopts[i].abbr; |
| 184 | for (a = 0; a < (int) longopts[i].argtype; a++) |
| 185 | *p++ = ':'; |
| 186 | } |
| 187 | } |
| 188 | *p = '\0'; |
| 189 | } |
| 190 | |
| 191 | /* Unlike strcmp(), handles options containing "=". */ |
| 192 | static int argparse_longopts_match(const char *longname, const char *option) |
| 193 | { |
| 194 | const char *a = option, *n = longname; |
| 195 | if (longname == 0) |
| 196 | return 0; |
| 197 | for (; *a && *n && *a != '='; a++, n++) |
| 198 | if (*a != *n) |
| 199 | return 0; |
| 200 | return *n == '\0' && (*a == '\0' || *a == '='); |
| 201 | } |
| 202 | |
| 203 | /* Return the part after "=", or NULL. */ |
| 204 | static const char *argparse_longopts_arg(const char *option) |
| 205 | { |
| 206 | for (; *option && *option != '='; option++) |
| 207 | ; |
| 208 | if (*option == '=') |
| 209 | return option + 1; |
| 210 | else |
| 211 | return 0; |
| 212 | } |
| 213 | |
| 214 | static int argparse_long_fallback(argparse_state_t *options, const argparse_arg_t *longopts, int *longindex) |
| 215 | { |
| 216 | int result; |
| 217 | char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ |
| 218 | argparse_from_long(longopts, optstring); |
| 219 | result = argparse(state: options, optstring); |
| 220 | if (longindex != 0) |
| 221 | { |
| 222 | *longindex = -1; |
| 223 | if (result != -1) |
| 224 | { |
| 225 | int i; |
| 226 | for (i = 0; !argparse_longopts_end(longopts, i); i++) |
| 227 | if (longopts[i].abbr == options->optopt) |
| 228 | *longindex = i; |
| 229 | } |
| 230 | } |
| 231 | return result; |
| 232 | } |
| 233 | |
| 234 | int argparse_long(argparse_state_t *options, const argparse_arg_t *longopts, int *longindex) |
| 235 | { |
| 236 | int i; |
| 237 | const char *option = options->argv[options->optind]; |
| 238 | if (option == 0) |
| 239 | { |
| 240 | return -1; |
| 241 | } |
| 242 | else if (argparse_is_dashdash(arg: option)) |
| 243 | { |
| 244 | options->optind++; /* consume "--" */ |
| 245 | return -1; |
| 246 | } |
| 247 | else if (argparse_is_shortopt(arg: option)) |
| 248 | { |
| 249 | return argparse_long_fallback(options, longopts, longindex); |
| 250 | } |
| 251 | else if (!argparse_is_longopt(arg: option)) |
| 252 | { |
| 253 | if (options->permute) |
| 254 | { |
| 255 | int index = options->optind++; |
| 256 | int r = argparse_long(options, longopts, longindex); |
| 257 | argparse_permute(state: options, index); |
| 258 | options->optind--; |
| 259 | return r; |
| 260 | } |
| 261 | else |
| 262 | { |
| 263 | return -1; |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | /* Parse as long option. */ |
| 268 | options->errmsg[0] = '\0'; |
| 269 | options->optopt = 0; |
| 270 | options->optarg = 0; |
| 271 | option += 2; /* skip "--" */ |
| 272 | options->optind++; |
| 273 | for (i = 0; !argparse_longopts_end(longopts, i); i++) |
| 274 | { |
| 275 | const char *name = longopts[i].full; |
| 276 | if (argparse_longopts_match(longname: name, option)) |
| 277 | { |
| 278 | const char *arg; |
| 279 | if (longindex) |
| 280 | *longindex = i; |
| 281 | options->optopt = longopts[i].abbr; |
| 282 | arg = argparse_longopts_arg(option); |
| 283 | if (longopts[i].argtype == ARGPARSE_NONE && arg != 0) |
| 284 | { |
| 285 | return argparse_error(state: options, ARGPARSE_MSG_TOOMANY, data: name); |
| 286 | } |
| 287 | if (arg != 0) |
| 288 | { |
| 289 | options->optarg = arg; |
| 290 | } |
| 291 | else if (longopts[i].argtype == ARGPARSE_REQUIRED) |
| 292 | { |
| 293 | options->optarg = options->argv[options->optind]; |
| 294 | if (options->optarg == 0) |
| 295 | return argparse_error(state: options, ARGPARSE_MSG_MISSING, data: name); |
| 296 | else |
| 297 | options->optind++; |
| 298 | } |
| 299 | return options->optopt; |
| 300 | } |
| 301 | } |
| 302 | return argparse_error(state: options, ARGPARSE_MSG_INVALID, data: option); |
| 303 | } |
| 304 | |
| 305 | void argparse_usage(argparse_state_t *options, const argparse_arg_t *args, const char *usage) |
| 306 | { |
| 307 | fprintf(stream: stderr, format: "Usage: %s, %s\n" , options->argv[0], usage); |
| 308 | static const int help_indent = 24; |
| 309 | |
| 310 | for (int i = 0; !argparse_longopts_end(longopts: args, i); i++) |
| 311 | { |
| 312 | const char *name = args[i].full; |
| 313 | const char abbr = args[i].abbr; |
| 314 | const char *help = args[i].help ? args[i].help : "" ; |
| 315 | const char *arg = args[i].argtype == ARGPARSE_NONE ? "" : args[i].argtype == ARGPARSE_REQUIRED ? " ARG" : " [ARG]" ; |
| 316 | |
| 317 | int printed = 0; |
| 318 | if (abbr) |
| 319 | printed += fprintf(stream: stderr, format: " -%c" , abbr); |
| 320 | |
| 321 | if (name) |
| 322 | printed += fprintf(stream: stderr, format: ", --%s%s" , name, arg); |
| 323 | else |
| 324 | printed++; |
| 325 | |
| 326 | if (printed < help_indent) |
| 327 | fprintf(stream: stderr, format: "%*s" , help_indent - printed, "" ); |
| 328 | else |
| 329 | fprintf(stream: stderr, format: "\n%*s" , help_indent, "" ); |
| 330 | |
| 331 | fprintf(stream: stderr, format: "%s\n" , help); |
| 332 | } |
| 333 | |
| 334 | fprintf(stream: stderr, format: "\n" ); |
| 335 | } |
| 336 | |