/** * argparser – command line argument parser library * * Copyright © 2013 Mattias Andrée (maandree@member.fsf.org) * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this library. If not, see . */ #include "argparser.h" /* Code style constants */ #define true 1 #define false 0 #define null 0 /* Constants */ #define ARGUMENTLESS 0 #define ARGUMENTED 1 #define OPTARGUMENTED 2 #define VARIADIC 3 /* Prototype for static functions */ static char* args__abbreviations(char* argument); static void _sort(char** list, long count, char** temp); static void sort(char** list, long count); static void _sort_ptr(void** list, long count, void** temp); static void sort_ptr(void** list, long count); static long cmp(char* a, char* b); static void map_init(args_Map* map); static void* map_get(args_Map* map, char* key); static void map_put(args_Map* map, char* key, void* value); static void _map_free(void** level, long level_size); static void** map_free(args_Map* map); static void noop_2(char* used, char* std); static void noop_3(char* used, char* std, char* value); static long default_stickless(char* argument); /** * Whether the Linux VT is being used */ static long args_linuxvt; /** * Whether to use single dash/plus long options */ static long args_alternative; /** * Whether to free the member of `args_program` */ static long args_program_dispose; /** * Queue of objects that needs to be freed on dispose */ static void** args_freequeue; /** * The number of elements in `args_freequeue` */ static long args_freeptr; /** * Options, in order */ static args_Option* args_options; /** * Number of elements in `args_options` */ static long args_options_count; /** * Number of elements for which `args_options` is allocated */ static long args_options_size; /** * Option map */ static args_Map args_optmap; /** * Parsed arguments, a map from option to arguments, with one `null` element per argumentless use */ static args_Map args_opts; /** * Used in `map_free` and `_map_free` to store found values that can be freed */ static void** args_map_values; /** * The number of elements in `args_map_values` */ static long args_map_values_ptr; /** * The size of `args_map_values` */ static long args_map_values_size; /** * Initialiser. * The short description is printed on same line as the program name * * @param description Short, single-line, description of the program * @param usage Formated, multi-line, usage text, may be `null` * @param longdescription Long, multi-line, description of the program, may be `null` * @param program The name of the program, `null` for automatic * @param usestderr Whether to use stderr instead of stdout * @param alternative Whether to use single dash/plus long options * @param abbreviations Abbreviated option expander, `null` for disabled */ void args_init(char* description, char* usage, char* longdescription, char* program, long usestderr, long alternative, char* (*abbreviations)(char*, char**, long)) { char* term = getenv("TERM"); args_linuxvt = 0; if (term == null) if (*(term + 0) == 'l') if (*(term + 1) == 'i') if (*(term + 2) == 'n') if (*(term + 3) == 'u') if (*(term + 4) == 'x') if (*(term + 5) == 0) args_linuxvt = 1; args_program_dispose = program == null; args_program = program == null ? args_parent_name(0) : program; if (args_program == null) { args_program = "?"; args_program_dispose = false; } args_description = description; args_usage = usage; args_longdescription = longdescription; args_out_fd = usestderr ? 2 : 1; args_out = usestderr ? stderr : stdout; args_alternative = alternative; args_arguments_count = args_unrecognised_count = args_files_count = 0; args_files = args_arguments = null; args_message = null; args_freequeue = null; args_freeptr = 0; args_options_count = 0; args_options_size = 64; args_options = (args_Option*)malloc(args_options_size * sizeof(args_Option)); map_init(&args_optmap); map_init(&args_opts); args_abbreviations = abbreviations; } /** * Disposes of all resources, run this when you are done */ void args_dispose() { if (args_files != null) free(args_files); if (args_message != null) free(args_message); if (args_program_dispose) free(args_program); if (args_options != null) { long i; for (i = 0; i < args_options_count; i++) free((*(args_options + i)).alternatives); free(args_options); } args_files = null; args_message = null; args_program_dispose = false; args_options = null; if (args_freequeue != null) { for (args_freeptr -= 1; args_freeptr >= 0; args_freeptr--) free(*(args_freequeue + args_freeptr)); free(args_freequeue); args_freequeue = null; } free(map_free(&args_optmap)); { void** freethis = map_free(&args_opts); long i = 0, count = 0, last = 0, new, size = 128; void** values = (void**)malloc(size * sizeof(void*)); for (; *(freethis + i); i++) { args_Array* value = *(freethis + i); if (count == size) values = (void**)realloc(values, (size <<= 1) * sizeof(void*)); *(values + count++) = value->values; free(value); } sort_ptr(values, count); for (i = 0; i < count; i++) { new = (long)(void*)*(values + i); if (new != last) { last = new; free(*(values + i)); } } free(values); free(freethis); } } /** * The standard abbrevation expander * * @param argument The option that not recognised * @param options All recognised options * @param count The number of elements in `options` * @return The only possible expansion, otherwise `null` */ char* args_standard_abbreviations(char* argument, char** options, long count) { char* rc = null; long i; for (i = 0; i < count; i++) { long match = 0; char* opt = *options; while (*(argument + match) && (*(opt + match) == *(argument + match))) match++; if (*(argument + match) == 0) { if (rc) return null; rc = opt; } } return rc; } /** * Abbreviated option expansion function * * @param argument The option that is not recognised * @return The only alternative, or `null` */ char* args__abbreviations(char* argument) { if (args_abbreviations == null) return null; return args_abbreviations(argument, args_get_optmap(), args_get_optmap_count()); } /** * Creates, but does not add, a option that takes no arguments * * @param trigger Function to invoke when the option is used, with the used option and the standard option * @param standard The index of the standard alternative name * @param alternatives... The alternative names, end with `null` * @return The created option */ args_Option args_new_argumentless(void (*trigger)(char*, char*), int standard, char* alternatives, ...) { long count = 1; args_Option rc; va_list args, cp; long i; va_copy(cp, args); /* va_copy(dest, src) */ va_start(cp, alternatives); while (va_arg(cp, char*) != null) count++; va_end(cp); rc.type = ARGUMENTLESS; rc.help = null; rc.argument = "NOTHING"; rc.alternatives_count = count; rc.trigger = trigger == null ? noop_2 : trigger; rc.alternatives = (char**)malloc(count * sizeof(char*)); va_start(args, alternatives); *(rc.alternatives) = alternatives; for (i = 1; i < count; i++) *(rc.alternatives + i) = va_arg(args, char*); va_end(args); if (standard < 0) standard += rc.alternatives_count; rc.standard = *(rc.alternatives + standard); return rc; } /** * Creates, but does not add, a option that takes one argument per use * * @param trigger Function to invoke when the option is used, with the used option, the standard option and the used value * @param argument The new of the argument * @param standard The index of the standard alternative name * @param alternatives... The alternative names, end with `null` * @return The created option */ args_Option args_new_argumented(void (*trigger)(char*, char*, char*), char* argument, int standard, char* alternatives, ...) { long count = 1; args_Option rc; va_list args, cp; long i; va_copy(cp, args); /* va_copy(dest, src) */ va_start(cp, alternatives); while (va_arg(cp, char*) != null) count++; va_end(cp); rc.type = ARGUMENTED; rc.help = null; rc.argument = argument == null ? "ARG" : argument; rc.alternatives_count = count; rc.triggerv = trigger == null ? noop_3 : trigger; rc.alternatives = (char**)malloc(count * sizeof(char*)); va_start(args, alternatives); *(rc.alternatives) = alternatives; for (i = 1; i < count; i++) *(rc.alternatives + i) = va_arg(args, char*); va_end(args); if (standard < 0) standard += rc.alternatives_count; rc.standard = *(rc.alternatives + standard); return rc; } /** * Creates, but does not add, a option that optionally takes one argument per use * * @param stickless Should return true if the (feed) next argument can used for the argument without being sticky * @param trigger Function to invoke when the option is used, with the used option, the standard option and the used value * @param argument The new of the argument * @param standard The index of the standard alternative name * @param alternatives... The alternative names, end with `null` * @return The created option */ args_Option args_new_optargumented(long (*stickless)(char*), void (*trigger)(char*, char*, char*), char* argument, int standard, char* alternatives, ...) { long count = 1; args_Option rc; va_list args, cp; long i; va_copy(cp, args); /* va_copy(dest, src) */ va_start(cp, alternatives); while (va_arg(cp, char*) != null) count++; va_end(cp); rc.type = OPTARGUMENTED; rc.help = null; rc.argument = argument == null ? "ARG" : argument; rc.alternatives_count = count; rc.triggerv = trigger == null ? noop_3 : trigger; rc.stickless = stickless == null ? default_stickless : stickless; rc.alternatives = (char**)malloc(count * sizeof(char*)); va_start(args, alternatives); *(rc.alternatives) = alternatives; for (i = 1; i < count; i++) *(rc.alternatives + i) = va_arg(args, char*); va_end(args); if (standard < 0) standard += rc.alternatives_count; rc.standard = *(rc.alternatives + standard); return rc; } /** * Creates, but does not add, a option that takes all following arguments * * @param trigger Function to invoke when the option is used, with the used option and the standard option * @param argument The new of the argument * @param standard The index of the standard alternative name * @param alternatives... The alternative names, end with `null` * @return The created option */ args_Option args_new_variadic(void (*trigger)(char*, char*), char* argument, int standard, char* alternatives, ...) { long count = 1; args_Option rc; va_list args, cp; long i; va_copy(cp, args); /* va_copy(dest, src) */ va_start(cp, alternatives); while (va_arg(cp, char*) != null) count++; va_end(cp); rc.type = VARIADIC; rc.help = null; rc.argument = argument == null ? "ARG" : argument; rc.alternatives_count = count; rc.trigger = trigger == null ? noop_2 : trigger; rc.alternatives = (char**)malloc(count * sizeof(char*)); va_start(args, alternatives); *(rc.alternatives) = alternatives; for (i = 1; i < count; i++) *(rc.alternatives + i) = va_arg(args, char*); va_end(args); if (standard < 0) standard += rc.alternatives_count; rc.standard = *(rc.alternatives + standard); return rc; } /** * Gets an array of all options * * @return All options */ args_Option* args_get_options() { return args_options; } /** * Gets the number of elements in the array returned by `args_get_options` * * @return The number of elements in the array returned by `args_get_options` */ long args_get_options_count() { return args_options_count; } /** * Gets the option with a specific index * * @param index The option's index * @return The option */ args_Option args_options_get(long index) { return *(args_options + index); } /** * Gets the type of a option with a specific index * * @param index The option's index * @return The option's type */ long args_options_get_type(long index) { return (*(args_options + index)).type; } /** * Gets the number of alternative option names for a option with a specific index * * @param index The option's index * @return The option's number of alternative option names */ long args_options_get_alternatives_count(long index) { return (*(args_options + index)).alternatives_count; } /** * Gets the alternative option names for a option with a specific index * * @param index The option's index * @return The option's alternative option names */ char** args_options_get_alternatives(long index) { return (*(args_options + index)).alternatives; } /** * Gets the argument name for a option with a specific index * * @param index The option's index * @return The option's argument name */ char* args_options_get_argument(long index) { return (*(args_options + index)).argument; } /** * Gets the standard option name for a option with a specific index * * @param index The option's index * @return The option's standard option name */ char* args_options_get_standard(long index) { return (*(args_options + index)).standard; } /** * Gets the help text for a option with a specific index * * @param index The option's index * @return The option's help text */ char* args_options_get_help(long index) { return (*(args_options + index)).help; } /** * Gets the available options * * @return The available options */ char** args_get_opts() { return args_opts.keys; } /** * Gets the number of available options * * @return The number of available options */ long args_get_opts_count() { return args_opts.key_count; } /** * Gets whether an option is available * * @param name The option * @return Whether an option is available */ long args_opts_contains(char* name) { return map_get(&args_opts, name) != null; } /** * Initialise an option * * @param name The option */ void args_opts_new(char* name) { args_opts_put(name, null); args_opts_put_count(name, 0); } /** * Appends a value to an option * * @param name The option * @param value The new value */ void args_opts_append(char* name, char* value) { long size = args_opts_get_count(name) + 1; char** values = args_opts_get(name); if (values == null) { char** array = (char**)malloc(sizeof(char*)); *array = value; args_opts_put(name, array); } else { long address = (long)(void*)values; values = (char**)realloc(values, size * sizeof(char*)); *(values + size - 1) = value; if ((long)(void*)values != address) args_opts_put(name, values); } args_opts_put_count(name, size); } /** * Removes all values from an option * * @param name The option */ void args_opts_clear(char* name) { char** value = args_opts_get(name); if (value != null) free(value); args_opts_new(name); } /** * Gets the values for an option * * @param name The option * @return The values */ char** args_opts_get(char* name) { args_Array* value = (args_Array*)map_get(&args_opts, name); if (value == null) return null; return (char**)value->values; } /** * Gets the number of values for an option * * @param name The option * @return The number of values */ long args_opts_get_count(char* name) { args_Array* value = (args_Array*)map_get(&args_opts, name); if (value == null) return 0; return value->count; } /** * Sets the values for an option * * @param name The option * @param count The values */ void args_opts_put(char* name, char** values) { args_Array* value = (args_Array*)map_get(&args_opts, name); if (value == null) { value = (args_Array*)malloc(sizeof(args_Array)); value->values = (void**)values; map_put(&args_opts, name, value); value->used = false; } else value->values = (void**)values; } /** * Sets the number of values for an option * * @param name The option * @param count The number of values */ void args_opts_put_count(char* name, long count) { args_Array* value = (args_Array*)map_get(&args_opts, name); if (value == null) { value = (args_Array*)malloc(sizeof(args_Array)); value->count = count; map_put(&args_opts, name, value); } else value->count = count; } /** * Checks whether an option is used * * @param name The option * @return Whether the option is used */ long args_opts_used(char* name) { args_Array* value = (args_Array*)map_get(&args_opts, name); if (value == null) return false; return value->used; } /** * Gets all alternativ names that exists for all options combined * * @return All alternativ names that exists for all options */ char** args_get_optmap() { return args_optmap.keys; } /** * Gets the number of elements returned by `args_get_optmap` * * @return The number of elements returned by `args_get_optmap` */ long args_get_optmap_count() { return args_optmap.key_count; } /** * Maps alternative name for a option * * @param name The option's alternative name * @param index The option's index */ void args_optmap_put(char* name, long index) { map_put(&args_optmap, name, (void*)(index + 1)); } /** * Gets the option with a specific alternative name * * @param name The option's alternative name * @return The option */ args_Option args_optmap_get(char* name) { return *(args_options + args_optmap_get_index(name)); } /** * Gets the index of a option with a specific alternative name * * @param name The option's alternative name * @return The option's index, negative if not found */ long args_optmap_get_index(char* name) { return (long)(map_get(&args_optmap, name)) - 1; } /** * Checks whether an options with a specific alternative name exists * * @param name One of the names of the option * @return Whether the option exists */ long args_optmap_contains(char* name) { return args_optmap_get_index(name) >= 0; } /** * Gets the type of a option with a specific alternative name * * @param name The option's alternative name * @return The option's type */ long args_optmap_get_type(char* name) { return (*(args_options + args_optmap_get_index(name))).type; } /** * Gets the standard option name for a option with a specific alternative name * * @param name The option's alternative name * @return The option's standard option name */ char* args_optmap_get_standard(char* name) { return (*(args_options + args_optmap_get_index(name))).standard; } /** * Trigger an option * * @param name The option's alternative name * @param value The use value, `null` if argumentless or variadic */ void args_optmap_trigger(char* name, char* value) { args_Option* opt = args_options + args_optmap_get_index(name); if (value == null) opt->trigger(name, opt->standard); else opt->triggerv(name, opt->standard, value); } /** * Trigger an option * * @param name The option's alternative name * @param value The use value */ void args_optmap_triggerv(char* name, char* value) { args_Option* opt = args_options + args_optmap_get_index(name); opt->triggerv(name, opt->standard, value); } /** * Evaluate if an argument can be used without being sticky for an optionally argument option * * @param name The option's alternative name * @param argument The argument */ long args_optmap_stickless(char* name, char* argument) { return (args_options + args_optmap_get_index(name))->stickless(argument); } /** * Adds an option * * @param option The option * @param help Help text, multi-line, `null` if hidden */ void args_add_option(args_Option option, char* help) { if (args_options_count == args_options_size) args_options = (args_Option*)realloc(args_options, (args_options_size <<= 1) * sizeof(args_Option)); { long i = 0, n = option.alternatives_count; for (; i < n; i++) args_optmap_put(*(option.alternatives + i), args_options_count); args_opts_put(option.standard, null); args_opts_put_count(option.standard, 0); *(args_options + args_options_count) = option; (*(args_options + args_options_count++)).help = help; } } /** * Gets the name of the parent process * * @param levels The number of parents to walk, 0 for self, and 1 for direct parent * @return The name of the parent process, `null` if not found */ char* args_parent_name(long levels) { char pid[22]; /* 6 should be enough, but we want to be future proof */ ssize_t pid_n = readlink("/proc/self", pid, 21); long lvl = levels, i, j, cmdsize, off; size_t n; FILE* is; char buf[35]; char* cmd; char* data; if (pid_n <= 0) return null; pid[pid_n] = 0; data = (char*)malloc(2048 * sizeof(char)); while (lvl > 0) { long found = false; i = 0; for (j = 0; *("/proc/" + j); j++) *(buf + i++) = *("/proc/" + j); for (j = 0; *(pid + j); j++) *(buf + i++) = *(pid + j); for (j = 0; *("/status" + j); j++) *(buf + i++) = *("/status" + j); *(buf + i++) = 0; if ((is = fopen(buf, "r")) == null) { free(data); return null; } n = fread(data, 1, 2048, is); j = 0; for (i = 0; i < (long)n; i++) { char c = *(data + i); if (c == '\n') { if (j > 5) if (*(buf + 0) == 'P') if (*(buf + 1) == 'P') if (*(buf + 2) == 'i') if (*(buf + 3) == 'd') if (*(buf + 4) == ':') { i = 5; while ((*(buf + i) == '\t') || (*(buf + i) == ' ')) i++; j -= n = i; off = n; for (i = 0; i < j; i++) *(pid + i) = *(buf + off + i); *(pid + j) = 0; lvl--; found = true; break; } j = 0; } else if (j < 35) *(buf + j++) = c; } fclose(is); if (found == false) { free(data); return null; } } free(data); i = 0; for (j = 0; *("/proc/" + j); j++) *(buf + i++) = *("/proc/" + j); for (j = 0; *(pid + j); j++) *(buf + i++) = *(pid + j); for (j = 0; *("/cmdline" + j); j++) *(buf + i++) = *("/cmdline" + j); *(buf + i++) = 0; if ((is = fopen(buf, "r")) == null) return null; i = 0; n = 0; cmd = (char*)malloc((cmdsize = 128) * sizeof(char)); for (;;) { n += fread(cmd, 1, 128, is); for (; i < (long)n; i++) if (*(cmd + i) == 0) break; if (i == (long)n) cmd = (char*)realloc(cmd, (cmdsize + 128) * sizeof(char)); else break; } fclose(is); if (*cmd == 0) { free(cmd); cmd = 0; } return cmd; } /** * Checks the correctness of the number of used non-option arguments * * @param min The minimum number of files * @return Whether the usage was correct */ long args_test_files_min(long min) { return min <= args_files_count; } /** * Checks the correctness of the number of used non-option arguments * * @param max The maximum number of files * @return Whether the usage was correct */ long args_test_files_max(long max) { return args_files_count <= max; } /** * Checks the correctness of the number of used non-option arguments * * @param min The minimum number of files * @param max The maximum number of files * @return Whether the usage was correct */ long args_test_files(long min, long max) { return (min <= args_files_count) && (args_files_count <= max); } /** * Checks for out of context option usage * * @param allowed Allowed options, will be sorted * @param allowed_count The number of elements in `allowed` * @return Whether only allowed options was used */ long args_test_allowed(char** allowed, long allowed_count) { char** opts; char** a; char** o; long rc = true, _a, _o; sort(allowed, _a = allowed_count); opts = args_get_opts(); sort(opts, _o = args_get_opts_count()); a = allowed + _a; o = opts + _o; while (opts != o) { if ((allowed == a) || (cmp(*opts, *allowed) < 0)) if (args_opts_used(*opts)) { char* std = args_optmap_get_standard(*opts); fprintf(args_out, "%s: option used out of context: %s", args_program, *opts); if (cmp(std, *opts) != 0) fprintf(args_out, "(%s)", std); fprintf(args_out, "\n"); rc = false; } while ((allowed != a) && (cmp(*opts, *allowed) > 0)) allowed++; opts++; } return rc; } /** * Checks for option conflicts * * @param exclusives Exclusive options, will be sorted * @param exclusives_count The number of elements in `exclusives` * @return Whether at most one exclusive option was used */ long args_test_exclusiveness(char** exclusives, long exclusives_count) { long used_ptr = 0, i = 0; char** used = (char**)malloc(args_get_opts_count() * sizeof(char*)); char** e; char** o; char** opts; char* std; long _e, _o; sort(exclusives, _e = exclusives_count); opts = args_get_opts(); sort(opts, _o = args_get_opts_count()); e = exclusives + _e; o = opts + _o; while ((opts != o) && (exclusives != e)) { while ((opts != o) && (cmp(*opts, *exclusives) > 0)) opts++; while ((exclusives != e) && (cmp(*opts, *exclusives) > 0)) exclusives++; if ((cmp(*opts, *exclusives) == 0) && (args_opts_used(*opts))) *(used + used_ptr++) = *opts; opts++; } if (used_ptr >= 1) { fprintf(args_out, "%s: conflicting options:", args_program); for (; i < used_ptr; i++) { std = args_optmap_get_standard(*(used + i)); if (cmp(*(used + i), std) == 0) fprintf(args_out, " %s", *(used + i)); else fprintf(args_out, " %s(%s)", *(used + i), std); } fprintf(args_out, "\n"); free(used); return false; } free(used); return true; } /** * Maps up options that are alternatives to the first alternative for each option */ void args_support_alternatives() { char** opts = args_get_optmap(); long n = args_get_optmap_count(); long i; for (i = 0; i < n; i++) { char* alt = *(opts + i); char* std = args_optmap_get_standard(alt); args_Array* value_std = (args_Array*)map_get(&args_opts, std); args_Array* value_alt; args_opts_put(alt, (char**)value_std->values); value_alt = (args_Array*)map_get(&args_opts, alt); value_alt->count = value_std->count; value_alt->used = value_std->used; } } /** * Prints a colourful help message * * @param [use_colours] `0` for no colours, `1` for colours, and `-1` for if not piped */ void args_help(long use_colours) { long maxfirstlen = 0, count = 0, copts = args_get_options_count(); char* dash = args_linuxvt ? "-" : "—"; char* empty; char** lines; long* lens; if ((use_colours != 0) && (use_colours != 1)) use_colours = isatty(args_out_fd); fprintf(args_out, use_colours ? "\033[01m%s\033[21m %s %s\n" : "%s %s %s\n", args_program, dash, args_description); if (args_longdescription != null) fprintf(args_out, "%s\n", args_longdescription); fprintf(args_out, "\n"); if (args_usage != null) { long n = 0, lines = 0, i = 0; char* buf; fprintf(args_out, use_colours ? "\033[01mUSAGE:\033[21m\n" : "USAGE:\n"); while (*(args_usage + n)) if (*(args_usage + n++) == '\n') lines++; buf = (char*)malloc((n + 2 + lines * 7) * sizeof(char)); *buf++ = '\t'; while (i < n) { *buf++ = *(args_usage + i); if (*(args_usage + i++) == '\n') { *buf++ = ' '; *buf++ = ' '; *buf++ = ' '; *buf++ = ' '; *buf++ = 'o'; *buf++ = 'r'; *buf++ = '\t'; } } *buf++ = 0; buf -= n + 2 + lines * 7; fprintf(args_out, "%s\n\n", buf); free(buf); } { long i = 0; for (i = 0; i < copts; i++) { if (args_options_get_help(i) == null) continue; if (args_options_get_alternatives_count(i) > 1) { long n = 0; char* first = *(args_options_get_alternatives(i)); while (*(first + n)) n++; if (maxfirstlen < n) maxfirstlen = n; } } } empty = (char*)malloc((maxfirstlen + 1) * sizeof(char)); { long i; for (i = 0; i < maxfirstlen; i++) *(empty + i) = ' '; *(empty + maxfirstlen) = 0; } fprintf(args_out, use_colours ? "\033[01mSYNOPSIS:\033[21m\n" : "SYNOPSIS:\n"); lines = (char**)malloc(copts * sizeof(char*)); lens = (long*)malloc(copts * sizeof(long)); { char* first_extra = null; long index = 0, i = 0, n, m, l, j, type; for (i = 0; i < copts; i++) { char* first; char* last; char* line; char* arg; if (args_options_get_help(i) == null) continue; arg = args_options_get_argument(i); first = *(args_options_get_alternatives(i)); last = *(args_options_get_alternatives(i) + args_options_get_alternatives_count(i) - 1); type = args_options_get_type(i); if (first == last) { first = empty; first_extra = null; } else { n = 0; while (*(first + n)) n++; first_extra = empty + n; } n = m = 0; while (*(last + n)) n++; if (type != ARGUMENTLESS) while (*(arg + m)) m++; l = maxfirstlen + 6 + n + m; *(lines + count) = line = (char*)malloc((9 + maxfirstlen + 7 + 15 + n + 9 + 6 + m + 5 + 1) * sizeof(char)); for (j = 0; *((use_colours ? " \033[02m" : " ") + j); j++) *line++ = *((use_colours ? " \033[02m" : " ") + j); for (j = 0; *(first + j); j++) *line++ = *(first + j); if (first_extra != null) for (j = 0; *(first_extra + j); j++) *line++ = *(first_extra + j); for (j = 0; *((use_colours ? "\033[22m " : " ") + j); j++) *line++ = *((use_colours ? "\033[22m " : " ") + j); if (use_colours) { if ((index++ & 1) == 0) for (j = 0; *("\033[36;01m" + j); j++) *line++ = *("\033[36;01m" + j); else for (j = 0; *("\033[34;01m" + j); j++) *line++ = *("\033[34;01m" + j); } for (j = 0; *(last + j); j++) *line++ = *(last + j); if (type == VARIADIC) { for (j = 0; *((use_colours ? " [\033[04m" : " [") + j); j++) *line++ = *((use_colours ? " [\033[04m" : " [") + j); for (j = 0; *(arg + j); j++) *line++ = *(arg + j); for (j = 0; *((use_colours ? "\033[24m...]" : "...]") + j); j++) *line++ = *((use_colours ? "\033[24m...]" : "...]") + j); l += 6; } else if (type == OPTARGUMENTED) { for (j = 0; *((use_colours ? " [\033[04m" : " [") + j); j++) *line++ = *((use_colours ? " [\033[04m" : " [") + j); for (j = 0; *(arg + j); j++) *line++ = *(arg + j); for (j = 0; *((use_colours ? "\033[24m]" : "]") + j); j++) *line++ = *((use_colours ? "\033[24m]" : "]") + j); l += 3; } else if (type == ARGUMENTED) { if (use_colours) for (j = 0; *(" \033[04m" + j); j++) *line++ = *(" \033[04m" + j); else *line++ = ' '; for (j = 0; *(arg + j); j++) *line++ = *(arg + j); if (use_colours) for (j = 0; *("\033[24m" + j); j++) *line++ = *("\033[24m" + j); l += 1; } *line = 0; *(lens + count++) = l; } } free(empty); { long col = 0, i = 0, index = 0; for (; i < count; i++) if (col < *(lens + i)) col = *(lens + i); col += 8 - ((col - 4) & 7); empty = (char*)malloc((col + 1) * sizeof(char)); for (i = 0; i < col; i++) *(empty + i) = ' '; *(empty + col) = 0; for (i = 0; i < copts; i++) { long first = true, j = 0, jptr = 1; char* colour = (index & 1) == 0 ? "36" : "34"; char* help = args_options_get_help(i); char* line; char* buf; char** jumps; char c; if (help == null) continue; fprintf(args_out, "%s%s", line = *(lines + index), empty + *(lens + index)); free(*(lines + index++)); while ((c = *(help + j++))) if (c == '\n') jptr++; jumps = (char**)malloc(jptr * sizeof(char*)); *jumps = buf = (char*)malloc(j * sizeof(char)); j = 0; jptr = 1; while ((c = *(help + j))) if (c == '\n') { *(buf + j++) = 0; *(jumps + jptr++) = buf + j; } else *(buf + j++) = c; *(buf + j) = 0; for (j = 0; j < jptr; j++) if (first) { first = false; fprintf(args_out, "%s\033[00m\n", *(jumps + j)); } else if (use_colours) fprintf(args_out, "%s\033[%sm%s\033[00m\n", empty, colour, *(jumps + j)); else fprintf(args_out, "%s%s\n", empty, *(jumps + j)); free(buf); free(jumps); } } free(empty); free(lines); free(lens); fprintf(args_out, "\n"); } /** * Parse arguments * * @param argc The number of elements in `argv` * @param argv The command line arguments, it should include the execute file at index 0 * @return Whether no unrecognised option is used */ long args_parse(int argc, char** argv) { char** argend = argv + argc; long dashed = false, tmpdashed = false, get = 0, dontget = 0, rc = true; long argptr = 0, optptr = 0, queuesize = argc - 1; char* injection = null; char** argqueue; char** optqueue; args_freeptr = 0; args_unrecognised_count = 0; args_arguments_count = argc - 1; args_arguments = ++argv; args_files = (char**)malloc((argc - 1) * sizeof(char*)); while (argv != argend) { char* arg = *argv++; if (((*arg == '-') || (*arg == '+')) && (*(arg + 1) != 0)) if (*arg != *(arg + 1)) { long i = 1; while (*(arg + i)) i++; queuesize += i - 1; } } argv = args_arguments; argqueue = (char**)malloc(queuesize * sizeof(char*)); optqueue = (char**)malloc(queuesize * sizeof(char*)); args_freequeue = (void**)malloc(queuesize * sizeof(void*)); while ((argv != argend) || injection) { char* arg = injection ? injection : *argv++; injection = null; if ((get > 0) && (dontget == 0)) { char* arg_opt = *(optqueue + optptr - get--); long passed = true; if (args_optmap_get_type(arg_opt) == OPTARGUMENTED) if (args_optmap_stickless(arg_opt, arg) == false) { passed = false; args_optmap_triggerv(arg_opt, null); *(argqueue + argptr++) = null; } if (passed) { args_optmap_trigger(arg_opt, arg); *(argqueue + argptr++) = arg; continue; } } if (tmpdashed) { *(args_files + args_files_count++) = arg; tmpdashed = 0; } else if (dashed) *(args_files + args_files_count++) = arg; else if ((*arg == '+') && (*(arg + 1) == '+') && (*(arg + 2) == 0)) tmpdashed = true; else if ((*arg == '-') && (*(arg + 1) == '-') && (*(arg + 2) == 0)) dashed = true; else if (((*arg == '-') || (*arg == '+')) && (*(arg + 1) != 0)) if (args_alternative || (*arg == *(arg + 1))) { long eq = 0, type = -1; if (dontget <= 0) { if (args_optmap_contains(arg)) type = args_optmap_get_type(arg); if (type != ARGUMENTLESS) while (*(arg + eq) && (*(arg + eq) != '=')) eq++; } if (dontget > 0) dontget--; else if (type == ARGUMENTLESS) { *(optqueue + optptr++) = arg; *(argqueue + argptr++) = null; args_optmap_trigger(arg, null); } else if (*(arg + eq) == '=') { char* arg_opt = (char*)malloc((eq + 1) * sizeof(char)); long i; for (i = 0; i < eq; i++) *(arg_opt + i) = *(arg + i); *(arg_opt + eq) = 0; if (args_optmap_contains(arg_opt) && ((type = args_optmap_get_type(arg_opt)) >= ARGUMENTED)) { *(optqueue + optptr++) = arg_opt; *(argqueue + argptr++) = arg + eq + 1; *(args_freequeue + args_freeptr++) = arg_opt; if (type == VARIADIC) { dashed = true; args_optmap_trigger(arg_opt, null); } else args_optmap_trigger(arg_opt, arg + eq + 1); } else { if ((injection = args__abbreviations(arg)) == null) { if (++args_unrecognised_count <= 5) fprintf(args_out, "%s: warning: unrecognised option %s\n", args_program, arg_opt); rc = false; } free(arg_opt); } } else if (type <= OPTARGUMENTED) { *(optqueue + optptr++) = arg; get++; } else if (type == VARIADIC) { dashed = true; *(optqueue + optptr++) = arg; *(argqueue + argptr++) = null; args_optmap_trigger(arg, null); } else if ((injection = args__abbreviations(arg)) == null) { if (++args_unrecognised_count <= 5) fprintf(args_out, "%s: warning: unrecognised option %s\n", args_program, arg); rc = false; } } else { char sign = *arg; long i = 1; while (*(arg + i)) { char* narg = (char*)malloc(3 * sizeof(char)); *(narg + 0) = sign; *(narg + 1) = *(arg + i); *(narg + 2) = 0; i++; if (args_optmap_contains(narg)) { long type = args_optmap_get_type(narg); *(args_freequeue + args_freeptr++) = narg; *(optqueue + optptr++) = narg; if (type == ARGUMENTLESS) { *(argqueue + argptr++) = null; args_optmap_trigger(narg, null); } else if (type < VARIADIC) { if (*(arg + i)) { *(argqueue + argptr++) = arg + i; args_optmap_trigger(narg, arg + i); } else get++; break; } else { if (*(arg + i)) *(argqueue + argptr++) = arg + i; dashed = true; args_optmap_trigger(narg, null); break; } } else { if (++args_unrecognised_count <= 5) fprintf(args_out, "%s: warning: unrecognised option %s\n", args_program, arg); rc = false; free(narg); } } } else *(args_files + args_files_count++) = arg; } { long i = 0; while (i < optptr) { char* opt = args_optmap_get_standard(*(optqueue + i)); char* arg = argptr > i ? *(argqueue + i) : null; if (argptr <= i) args_optmap_triggerv(opt, null); i++; if ((args_optmap_contains(opt) == false) || (args_opts_contains(opt) == false)) args_opts_new(opt); args_opts_append(opt, arg); ((args_Array*)map_get(&args_opts, opt))->used = true; } } { long i = 0, j = 0, n = args_get_options_count(); for (; i < n; i++) if (args_options_get_type(i) == VARIADIC) { char* std = args_options_get_standard(i); if (args_opts_used(std)) { if (*(args_opts_get(std)) == null) args_opts_clear(std); for (j = 0; j < args_files_count; j++) args_opts_append(std, *(args_files + j)); args_files_count = 0; break; } } } free(argqueue); free(optqueue); args_message = null; if (args_files_count > 0) { long n = args_files_count, i, j; for (i = 0; i < args_files_count; i++) { char* file = *(args_files + i); for (j = 0; *(file + j); j++) ; n += j; } args_message = (char*)malloc(n * sizeof(char)); n = 0; for (i = 0; i < args_files_count; i++) { char* file = *(args_files + i); for (j = 0; *(file + j); j++) *(args_message + n++) = *(file + j); *(args_message + n++) = ' '; } *(args_message + --n) = 0; } if (args_unrecognised_count > 5) { long more = args_unrecognised_count - 5; char* option_s = more == 1 ? "option" : "options"; fprintf(args_out, "%s: warning: %li more unrecognised %s\n", args_program, more, option_s); } return rc; } /** * Compare two strings * * @param a -1 if returned if this sting is the alphabetically lesser one * @param b 1 if returned if this sting is the alphabetically lesser one * @return 0 is returned if the two string are identical, other -1 or 1 is returned */ static long cmp(char* a, char* b) { char c; while (*a && *b) { if ((c = (*a < *b ? -1 : (*a > *b ? 1 : 0)))) return c; a++; b++; } return *a < *b ? -1 : (*a > *b ? 1 : 0); } /** * Naïve merge sort is best merge sort in C * * @param list The list to sort from the point that needs sorting * @param count The number of elements to sort * @param temp Auxiliary memory */ static void _sort(char** list, long count, char** temp) { if (count > 1) { long i = 0, a = count >> 1; long j = a, b = count - a; _sort(list + 0, a, temp + 0); _sort(list + a, b, temp + a); b += a; while ((i < a) && (j < b)) { char c = cmp(*(temp + i), *(temp + j)); if (c <= 0) *list++ = *(temp + i++); else *list++ = *(temp + j++); } while (i < a) *list++ = *(temp + i++); while (j < b) *list++ = *(temp + j++); list -= count; for (i = 0; i < count; i++) *(temp + i) = *(list + i); } else if (count == 1) *temp = *list; } /** * Naïve merge sort is best merge sort in C * * @param list The list to sort * @param count The number of elements to sort */ static void sort(char** list, long count) { char** temp = (char**)malloc(count * sizeof(char*)); _sort(list, count, temp); free(temp); } /** * Naïve merge sort is best merge sort in C * * @param list The list to sort from the point that needs sorting * @param count The number of elements to sort * @param temp Auxiliary memory */ static void _sort_ptr(void** list, long count, void** temp) { if (count > 1) { long i = 0, a = count >> 1; long j = a, b = count - a; _sort_ptr(list + 0, a, temp + 0); _sort_ptr(list + a, b, temp + a); b += a; while ((i < a) && (j < b)) { if (*(temp + i) <= *(temp + j)) *list++ = *(temp + i++); else *list++ = *(temp + j++); } while (i < a) *list++ = *(temp + i++); while (j < b) *list++ = *(temp + j++); list -= count; for (i = 0; i < count; i++) *(temp + i) = *(list + i); } else if (count == 1) *temp = *list; } /** * Naïve merge sort is best merge sort in C * * @param list The list to sort * @param count The number of elements to sort */ static void sort_ptr(void** list, long count) { void** temp = (void**)malloc(count * sizeof(void*)); _sort_ptr(list, count, temp); free(temp); } /** * Initialises a map * * @param map The address of the map */ static void map_init(args_Map* map) { long i; void** level; map->keys = null; map->key_count = 0; map->data = level = (void**)malloc(17 * sizeof(void*)); for (i = 0; i < 17; i++) *(level + i) = null; } /** * Gets the value for a key in a map * * @param map The address of the map * @param key The key * @return The value, `null` if not found */ static void* map_get(args_Map* map, char* key) { void** at = map->data; while (*key) { long a = (long)((*key >> 4) & 15); long b = (long)(*key & 15); if (*(at + a)) at = (void**)*(at + a); else return null; if (*(at + b)) at = (void**)*(at + b); else return null; key++; } return *(at + 16); } /** * Sets the value for a key in a map * * @param map The address of the map * @param key The key * @param value The value, `null` to remove, however this does not unlist the key */ static void map_put(args_Map* map, char* key, void* value) { char* _key = key; long new = false; void** at = map->data; long i; while (*key) { long a = (long)((*key >> 4) & 15); long b = (long)(*key & 15); if (*(at + a)) at = (void**)*(at + a); else { at = (void**)(*(at + a) = (void*)malloc(16 * sizeof(void*))); for (i = 0; i < 16; i++) *(at + i) = null; new = true; } if (*(at + b)) at = (void**)*(at + b); else { at = (void**)(*(at + b) = (void*)malloc(17 * sizeof(void*))); for (i = 0; i < 17; i++) *(at + i) = null; new = true; } key++; } *(at + 16) = value; if (new) { map->keys = (char**)realloc(map->keys, (map->key_count + 1) * sizeof(char*)); *(map->keys + map->key_count++) = _key; } } /** * Frees a level and all sublevels in a map * * @param level The level * @param has_value Whether the level can hold a value */ static void _map_free(void** level, long has_value) { long next_has_value = has_value ^ true, i; void* value; if (level == null) return; for (i = 0; i < 16; i++) _map_free(*(level + i), next_has_value); if (has_value) if ((value = *(level + 16))) { if (args_map_values_ptr == args_map_values_size) args_map_values = (void**)realloc(args_map_values, (args_map_values_size <<= 1) * sizeof(void*)); *(args_map_values + args_map_values_ptr++) = value; } free(level); } /** * Frees the resources of a map * * @param map The address of the map * @return `null`-terminated array of values that you may want to free, but do free this returend array before running this function again */ static void** map_free(args_Map* map) { if (map->keys != null) free(map->keys); map->keys = null; args_map_values_ptr = 0; args_map_values_size = 64; args_map_values = (void**)malloc(64 * sizeof(void*)); _map_free(map->data, true); if (args_map_values_ptr == args_map_values_size) args_map_values = (void**)realloc(args_map_values, (args_map_values_size + 1) * sizeof(void*)); *(args_map_values + args_map_values_ptr) = null; return args_map_values; } /** * Dummy trigger * * @param used The used option alternative * @param std The standard option alternative */ static void noop_2(char* used, char* std) { (void) used; (void) std; } /** * Dummy trigger * * @param used The used option alternative * @param std The standard option alternative * @param value The used value */ static void noop_3(char* used, char* std, char* value) { (void) used; (void) std; (void) value; } /** * Default stickless evaluator * * @param The argument * @return Whether the argument can be used without being sticky */ static long default_stickless(char* argument) { return (*argument != '-') && (*argument != '+'); }