+/*
+ * Copyright (c) 2017 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#define __STDC_WANT_LIB_EXT1__ 1
+#include <string.h>
+
+#include "SecArgParse.h"
+
+// Trinary:
+// If flag is set and argument is not, it has no_argument
+// If flag is set and argument is set, it is optional_argument
+// If flag is not set and argument is set, it is required_argument
+// If flag is not set and argument is not set, what are you doing? There's no output.
+
+static int argument_status(struct argument* option) {
+ if(option == NULL) {
+ return no_argument;
+ }
+ return (!!(option->flag) && !(option->argument) ? no_argument :
+ (!!(option->flag) && !!(option->argument) ? optional_argument :
+ ( !(option->flag) && !!(option->argument) ? required_argument :
+ no_argument)));
+}
+
+static bool fill_long_option_array(struct argument* options, size_t noptions, struct option long_options[], size_t nloptions) {
+ size_t i = 0;
+ size_t longi = 0;
+
+ for(i = 0; i <= noptions; i++) {
+ if(longi >= nloptions) {
+ return false;
+ }
+
+ if(options[i].longname) {
+ long_options[longi].name = options[i].longname;
+ long_options[longi].has_arg = argument_status(&options[i]);
+ long_options[longi].flag = options[i].flag;
+ long_options[longi].val = options[i].flagval;
+ longi++;
+ }
+ }
+
+ if(longi >= nloptions) {
+ return false;
+ }
+
+ long_options[longi].name = NULL;
+ long_options[longi].has_arg = 0;
+ long_options[longi].flag = 0;
+ long_options[longi].val = 0;
+
+ return true;
+}
+
+static bool fill_short_option_array(struct argument* options, size_t noptions, char* short_options, size_t nshort_options) {
+ size_t index = 0;
+ for(size_t i = 0; i < noptions; i++) {
+ if(options[i].shortname != '\0') {
+ if(index >= nshort_options) {
+ return false;
+ }
+ short_options[index] = options[i].shortname;
+ index += 1;
+
+ if(argument_status(&options[i]) == required_argument) {
+ if(index >= nshort_options) {
+ return false;
+ }
+ short_options[index] = ':';
+ index += 1;
+ }
+ }
+ }
+ short_options[index] = '\0';
+ return true;
+}
+
+static void trigger(struct argument option, char* argument) {
+ if(option.flag) {
+ *(option.flag) = option.flagval;
+ }
+ if(option.argument) {
+ asprintf(option.argument, "%.1048576s", argument);
+ }
+}
+
+static size_t num_arguments(struct arguments* args) {
+ size_t n = 0;
+
+ if(!args) {
+ return 0;
+ }
+
+ struct argument* a = args->arguments;
+ // Make an all-zero struct
+ struct argument final = {};
+
+ // Only 1024 arguments allowed.
+ while(a && n < 1024 && (memcmp(a, &final, sizeof(struct argument)) != 0)) {
+ n++;
+ a++;
+ }
+
+ return n;
+}
+
+bool options_parse(int argc, char * const *argv, struct arguments* args) {
+ if(!args) {
+ return false;
+ }
+ bool success = false;
+
+ struct arguments realargs;
+ realargs.programname = args->programname;
+ realargs.description = args->description;
+ size_t num_args = num_arguments(args);
+ size_t noptions = num_args + 1;
+ realargs.arguments = (struct argument*) malloc((noptions +1) * sizeof(struct argument)); // extra array slot for null array
+
+ struct argument help = {.shortname = 'h', .longname="help", .description="show this help message and exit"};
+
+ realargs.arguments[0] = help;
+ // Adds one to include the null terminator struct!
+ for(size_t i = 0; i < num_args + 1; i++) {
+ realargs.arguments[i+1] = args->arguments[i];
+ }
+
+ struct option* long_options = (struct option*) malloc((noptions+1) * sizeof(struct option));
+ size_t short_options_length = 2* noptions * sizeof(char) + 2; // 2: one for -h, one for the null terminator
+ char* short_options = (char*) malloc(short_options_length);
+
+ fill_long_option_array(realargs.arguments, noptions, long_options, noptions);
+ fill_short_option_array(realargs.arguments, noptions, short_options, short_options_length);
+
+ int c;
+ int option_index = 0;
+ while((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) {
+ // We have a short arg or an arg with an argument. Parse it.
+ if(c==0) {
+ if(option_index == 0) {
+ // This is the --help option
+ print_usage(&realargs);
+ exit(1);
+ } else {
+ struct option* long_option = &long_options[option_index];
+
+ for(size_t i = 0; i < noptions; i++) {
+ if(realargs.arguments[i].longname && strncmp(long_option->name, realargs.arguments[i].longname, strlen(realargs.arguments[i].longname)) == 0) {
+ trigger(realargs.arguments[i], optarg);
+ }
+ }
+ }
+ } else {
+ // Handle short name
+ if(c == 'h') {
+ // This is the --help option
+ print_usage(&realargs);
+ exit(1);
+ }
+ size_t i = 0;
+ for(i = 0; i < noptions; i++) {
+ if(realargs.arguments[i].shortname == c) {
+ trigger(realargs.arguments[i], optarg);
+ break;
+ }
+ }
+ if(i == noptions) {
+ return false;
+ }
+ }
+ }
+
+ if(optind < argc) {
+ bool command_triggered = false;
+ size_t positional_argument_index = 0;
+
+ for(int parg = optind; parg < argc; parg++) {
+ for(size_t i = 0; i < noptions; i++) {
+ if(realargs.arguments[i].command) {
+ if(strcmp(argv[parg], realargs.arguments[i].command) == 0) {
+ trigger(realargs.arguments[i], NULL);
+ command_triggered = true;
+ break;
+ }
+ }
+ }
+
+ if(command_triggered) {
+ break;
+ }
+
+ while(positional_argument_index < noptions && !realargs.arguments[positional_argument_index].positional_name) {
+ positional_argument_index++;
+ }
+
+ if(positional_argument_index >= noptions) {
+ // no positional argument found to save
+ // explode
+ goto out;
+ } else {
+ if(realargs.arguments[positional_argument_index].argument) {
+ *(realargs.arguments[positional_argument_index].argument) = argv[parg];
+ positional_argument_index++;
+ }
+
+ }
+ }
+ }
+
+ success = true;
+
+out:
+ free(realargs.arguments);
+ free(long_options);
+ free(short_options);
+
+ return success;
+}
+
+void print_usage(struct arguments* args) {
+ if(!args) {
+ return;
+ }
+ printf("usage: %s", args->programname ? args->programname : "command");
+
+ size_t num_args = num_arguments(args);
+
+ // Print all short options
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].shortname) {
+ printf(" [-%c", args->arguments[i].shortname);
+
+ if(argument_status(&args->arguments[i]) != no_argument) {
+ printf(" %s", args->arguments[i].argname ? args->arguments[i].argname : "arg");
+ }
+
+ printf("]");
+ }
+ }
+
+ // Print all long args->arguments that don't have short args->arguments
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].longname && !args->arguments[i].shortname) {
+
+ printf(" [--%s", args->arguments[i].longname);
+
+ if(argument_status(&args->arguments[i]) != no_argument) {
+ printf(" %s", args->arguments[i].argname ? args->arguments[i].argname : "arg");
+ }
+
+ printf("]");
+ }
+ }
+
+ // Print all commands
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].command) {
+ printf(" [%s]", args->arguments[i].command);
+ }
+ }
+
+ // Print all positional arguments
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].positional_name) {
+ if(args->arguments[i].positional_optional) {
+ printf(" [<%s>]", args->arguments[i].positional_name);
+ } else {
+ printf(" <%s>", args->arguments[i].positional_name);
+ }
+ }
+ }
+
+ printf("\n");
+
+ if(args->description) {
+ printf("\n%s\n", args->description);
+ }
+
+ printf("\npositional arguments:\n");
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].positional_name) {
+ printf(" %-31s %s\n", args->arguments[i].positional_name, args->arguments[i].description);
+ }
+ }
+
+ printf("\noptional arguments:\n");
+ // List all short args->arguments
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].shortname) {
+ if(!args->arguments[i].longname) {
+
+ if(argument_status(&args->arguments[i]) != no_argument) {
+ printf(" -%c %-*s", args->arguments[i].shortname, 28, "arg");
+ } else {
+ printf(" -%-30c", args->arguments[i].shortname);
+ }
+
+ } else {
+ printf(" -%c", args->arguments[i].shortname);
+
+ if(argument_status(&args->arguments[i]) != no_argument) {
+ printf(" %s", args->arguments[i].argname ? args->arguments[i].argname : "arg");
+ }
+
+ if(args->arguments[i].longname) {
+ if(argument_status(&args->arguments[i]) == no_argument) {
+ printf(", --%-*s", 28 - (int) strlen(args->arguments[i].argname ? args->arguments[i].argname : "arg"), args->arguments[i].longname);
+ } else {
+ printf(", --%s %-*s", args->arguments[i].longname, 28 -5 - (int) strlen(args->arguments[i].longname) - (int) strlen(args->arguments[i].argname ? args->arguments[i].argname : "arg"), "arg");
+ }
+ }
+ }
+
+ printf("%s\n", args->arguments[i].description);
+ }
+ }
+
+ // List all long args->arguments
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].longname && !args->arguments[i].shortname) {
+ if(argument_status(&args->arguments[i]) != no_argument) {
+ printf(" --%s %-*s %s\n",
+ args->arguments[i].longname,
+ 28 - (int) strlen(args->arguments[i].longname),
+ args->arguments[i].argname ? args->arguments[i].argname : "arg",
+ args->arguments[i].description);
+ } else {
+ printf(" --%-29s %s\n", args->arguments[i].longname, args->arguments[i].description);
+ }
+ }
+ }
+
+ printf("\noptional commands:\n");
+ // Print all commands
+ for(size_t i = 0; i < num_args; i++) {
+ if(args->arguments[i].command) {
+ printf(" %-30s %s\n", args->arguments[i].command, args->arguments[i].description);
+ }
+ }
+
+ printf("\n");
+}