/** * argparser – command line argument parser library * * Copyright © 2013, 2014 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" #include #include #include #include #define xfree(s) (free(s), s = NULL) #define xmalloc(s, n, t) ((s = malloc((n) * sizeof(t))) == NULL) #define xgetenv(v) (getenv(v) ? getenv(v) : "") /** * Initialise an argument parser * * @param parser The memory address of the parser to initialise * @return Zero on success, -1 on error, `errno` will be set accordingly */ int args_initialise(args_parser_t* parser) { parser->state->arguments = NULL; parser->state->files = NULL; parser->state->message = NULL; parser->state->options = NULL; parser->state->all_options = NULL; parser->state->freequeue = NULL; parser->state->arguments_count = 0; parser->state->unrecognised_count = 0; parser->state->files_count = 0; parser->state->options_count = 0; parser->state->all_options_count = 0; parser->state->freeptr = 0; parser->settings->linuxvt = !strcmp(xgetenv("TERM"), "linux"); parser->settings->alternative = 0; parser->settings->stop_at_first_file = 0; parser->settings->use_colours = AUTO; parser->settings->program = NULL; parser->settings->description = NULL; parser->settings->usage = NULL; parser->settings->longdescription = NULL; parser->settings->error_out = stderr; parser->settings->warning_out = stderr; parser->settings->help_out = stderr; parser->settings->abbreviations = args_standard_abbreviations; if (xmalloc(parser->state->arguments, 1, char*)) goto fail; if (xmalloc(parser->state->files, 1, char*)) goto fail; if (xmalloc(parser->state->options, 1, args_option_t)) goto fail; if (xmalloc(parser->state->all_options, 1, char*)) goto fail; if (xmalloc(parser->state->freequeue, 1, void*)) goto fail; return 0; fail: xfree(parser->state->arguments); xfree(parser->state->files); xfree(parser->state->options); xfree(parser->state->all_options); xfree(parser->state->freequeue); return -1; } /** * Disposes of all resources, run this when you are done * * @param parser The parser */ void args_dispose(args_parser_t* parser) { size_t i; parser->state->arguments_count = 0; parser->state->unrecognised_count = 0; parser->state->files_count = 0; parser->state->all_options_count = 0; xfree(parser->settings->program); xfree(parser->state->arguments); xfree(parser->state->message); xfree(parser->state->files); for (i = 0; i < parser->state->options_count; i++) { free(parser->state->options[i].alternatives); free(parser->state->options[i].arguments); } parser->state->options_count = 0; xfree(parser->state->options); xfree(parser->state->all_options); xfree(parser->state->all_options_standard); for (i = 0; i < parser->state->freeptr; i++) free(parser->state->freequeue[i]); parser->state->freeptr = 0; xfree(parser->state->freequeue); } /** * Maps up options that are alternatives to the standard alternative for each option * * @param parser The parser * @return Zero on success, -1 on error */ int args_support_alternatives(args_parser_t* parser) { size_t i; for (i = 0; i < parser->state->all_options_count; i++) { const char* alternative = parser->state->all_options[i]; const char* standard = parser->state->all_options_standard[i]; /* TODO map `alternative` to `standard` */ } return 0; } /** * Checks the correctness of the number of used non-option arguments * * @param parser The parser * @param min The minimum number of files * @return Whether the usage was correct */ int args_test_files_min(args_parser_t* restrictparser, size_t min) { return min <= parser->state->files_count; } /** * Checks the correctness of the number of used non-option arguments * * @param parser The parser * @param max The maximum number of files * @return Whether the usage was correct */ int args_test_files_max(args_parser_t* parser, size_t max) { return parser->state->files_count <= max; } /** * Checks the correctness of the number of used non-option arguments * * @param parser The parser * @param min The minimum number of files * @param max The maximum number of files * @return Whether the usage was correct */ int args_test_files(args_parser_t* parser, size_t min, size_t max) { return (min <= parser->state->files_count) && (parser->state->files_count <= max); } /** * Checks for out of context option usage * * @param parser The parser * @param ...:const char* Allowed options * @return Whether only allowed options was used, -1 on error */ int args_test_allowed(args_parser_t* parser, ...) { size_t count = 0; va_list args, cp; const char** list; const char* elem; int rc, saved_errno; va_start(args, parser); va_copy(cp, args); while (va_arg(cp, const char*) != NULL) count++; va_end(cp); if ((list = malloc(count * sizeof(const char*))) == NULL) { saved_errno = errno; va_end(args); errno = saved_errno; return -1; } count = 0; while ((elem = va_arg(args, const char*)) != NULL) list[count++] = elem; va_end(args); rc = args_test_allowed_l(parser, list, count); free(list); return rc; } /** * Checks for out of context option usage * * @param parser The parser * @param allowed Allowed options * @param count The number of elements in `allowed` * @return Whether only allowed options was used */ int args_test_allowed_l(args_parser_t* parser, const char** allowed, size_t count) { /* TODO print warnings */ size_t i, j, k; for (i = 0; i < parser->state->options_count; i++) if (parser->state->options[i].arguments_count > 0) { for (j = 0; j < parser->state->options[i].alternatives_count; j++) for (k = 0; k < count; k++) if (!strcmp(parser->state->options[i].alternatives[j], allowed[k])) goto allowed; return 0; allowed: continue; } return 1; } /** * Checks for option conflicts * * @param parser The parser * @param ...:const char* Mutually exclusive options * @return Whether at most one exclusive option was used, -1 on error */ int args_test_exclusiveness(args_parser_t* parser, ...) { size_t count = 0; va_list args, cp; const char** list; const char* elem; int rc, saved_errno; va_start(args, parser); va_copy(cp, args); while (va_arg(cp, const char*) != NULL) count++; va_end(cp); if ((list = malloc(count * sizeof(const char*))) == NULL) { saved_errno = errno; va_end(args); errno = saved_errno; return -1; } count = 0; while ((elem = va_arg(args, const char*)) != NULL) list[count++] = elem; va_end(args); rc = args_test_exclusiveness_l(parser, list, count); free(list); return rc; } /** * Checks for option conflicts * * @param parser The parser * @param exclusives Mutually exclusive options * @param count The number of elements in `exclusives` * @return Whether at most one exclusive option was used */ int args_test_exclusiveness_l(args_parser_t* parser, const char** exclusives, size_t count) { /* TODO print warnings */ size_t i, j, k, have_count; for (i = 0; i < parser->state->options_count; i++) if (parser->state->options[i].arguments_count > 0) { for (j = 0; j < parser->state->options[i].alternatives_count; j++) for (k = 0; k < count; k++) if (!strcmp(parser->state->options[i].alternatives[j], exclusives[k])) goto exclusive; continue; exclusive: have_count++; } return have_count <= 1; } /** * Dummy trigger * * @param user_data User-data * @param used The used option alternative * @param standard The standard option alternative */ void args_noop_trigger(void* user_data, const char* used, const char* standard) { (void) user_data; (void) used; (void) standard; } /** * Dummy trigger * * @param user_data User-data * @param used The used option alternative * @param standard The standard option alternative * @param value The used value */ void args_noop_trigger_v(void* user_data, const char* used, const char* standard, char* value) { (void) user_data; (void) used; (void) standard; (void) value; } /** * Stickless evaluator to always evaluates to false * * @param user_data User-data * @param argument The next argument * @return Whether the argument can be used without being sticky */ int args_no_stickless(void* user_data, const char* value) { (void) user_data; (void) value; return 0; } /** * Default stickless evaluator * * @param user_data User-data * @param argument The next argument * @return Whether the argument can be used without being sticky */ int args_default_stickless(void* user_data, const char* argument) { (void) user_data; return (argument[0] != '-') && (argument[0] != '+'); } /** * Evalutator for end argument of variadic options that always evalutes to false * * @param user_data User-data * @param value The next argument * @return Whether the argument can be used without being sticky */ int args_no_variadic_end(void* user_data, char* value) { (void) user_data; (void) value; return 0; } /** * The standard abbrevation expander * * @param argument The option that not recognised * @param options All recognised options * @param standards The corresponding standard option for options in `options` * @param count The number of elements in `options` and `standards` * @return The only possible expansion, otherwise `NULL` */ const char* args_standard_abbreviations(const char* argument, const char** options, const char** standards, size_t count) { const char* found = NULL; size_t i; for (i = 0; i < count; i++) if (strstr(options[i], argument) == options[i]) { if (found == NULL) found = standards[i]; else if (found != standards[i]) return NULL; } return found; }