123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 |
- #include <ctype.h>
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include "cargs.h"
- #define ENABLE_VALIDATION
- #define NULLCHAR 0
- #define ALLOCATE(type, amount) \
- (type*)malloc(sizeof(type) * amount)
- #define REALLOCATE(pointer, type, amount) \
- (type*)realloc(pointer, sizeof(type) * amount)
- #define ADD_ELEM(type, pointer, elem, currLen)\
- if (pointer == NULL) {\
- pointer = ALLOCATE(type, 1);\
- pointer[0] = elem;\
- } else {\
- pointer = REALLOCATE(pointer, type, currLen + 1);\
- pointer[currLen] = elem;\
- }\
- #define PTR_NULLSAFE_SET(pointer, value) if (pointer != NULL) *pointer = value
- #define OUT
- const __cargs_bool __cargs_true = 1;
- const __cargs_bool __cargs_false = 0;
- void parseCurrentArg(
- ParseContext* context,
- ParseResult* result,
- const int argc,
- char* argv[],
- const size_t pos,
- OUT size_t* nextPos);
- const __cargs_bool isInteger(char* str)
- {
- const size_t len = strlen(str);
- for (size_t i = 0; i < len; i++) {
- if (!isdigit(str[i])) {
- return __cargs_false;
- }
- }
- return __cargs_true;
- }
- const __cargs_bool isDecimal(char* str)
- {
- const size_t len = strlen(str);
- for (size_t i = 0; i < len; i++) {
- if (!(isdigit(str[i]) || str[i] == '.')) {
- return __cargs_false;
- }
- }
- return __cargs_true;
- }
- const OptionData booleanArg(
- const char shortOption,
- char* longOption,
- OUT __cargs_bool* source,
- char* description)
- {
- OptionData ret = {
- .shortOption = shortOption,
- .type = Boolean,
- .longOption = longOption,
- .description = description,
- .sourcePtr = (void*)source,
- };
- PTR_NULLSAFE_SET(source, 0);
- return ret;
- }
- const OptionData integerArg(
- const char shortOption,
- char* longOption,
- OUT __cargs_int* source,
- char* description)
- {
- OptionData ret = {
- .shortOption = shortOption,
- .type = Integer,
- .longOption = longOption,
- .description = description,
- .sourcePtr = (void*)source,
- };
- PTR_NULLSAFE_SET(source, 0);
- return ret;
- }
- const OptionData decimalArg(
- const char shortOption,
- char* longOption,
- OUT __cargs_decimal* source,
- char* description)
- {
- OptionData ret = {
- .shortOption = shortOption,
- .type = Decimal,
- .longOption = longOption,
- .description = description,
- .sourcePtr = (void*)source,
- };
- PTR_NULLSAFE_SET(source, 0);
- return ret;
- }
- const OptionData stringArg(
- const char shortOption,
- char* longOption,
- char** source,
- char* description)
- {
- OptionData ret = {
- .shortOption = shortOption,
- .type = String,
- .longOption = longOption,
- .description = description,
- .sourcePtr = (void*)source,
- };
- PTR_NULLSAFE_SET(source, "");
- return ret;
- }
- void freeResult(ParseResult* result)
- {
- if (result == NULL) {
- return;
- }
- free(result->decimalValues);
- free(result->decimalOptions);
- free(result->errors);
- free(result->freeValues);
- free(result->integerOptions);
- free(result->integerValues);
- free(result->booleanOptions);
- free(result->stringOptions);
- free(result->stringValues);
- free(result);
- }
- ParseResult* parseArgs(ParseContext* context, const int argc, char* argv[])
- {
- ParseResult* result = ALLOCATE(ParseResult, 1);
- result->booleanOptionsCount = 0;
- result->decimalOptionsCount = 0;
- result->integerOptionsCount = 0;
- result->stringOptionsCount = 0;
- result->errorsCount = 0;
- size_t pos = 1;
- while (pos < argc) {
- size_t nextPos = 0;
- parseCurrentArg(context, result, argc, argv, pos, &nextPos);
- pos = nextPos;
- }
- return result;
- }
- typedef enum
- {
- Long,
- Short,
- None,
- }
- OptionLengthCategory;
- const size_t searchOptionShort(
- ParseContext* context,
- char shortName,
- OUT __cargs_bool* success)
- {
- const OptionData* items = context->items;
- for (size_t i = 0; i < context->itemsCount; i++) {
- if (items[i].shortOption == shortName) {
- *success = __cargs_true;
- return i;
- }
- }
- *success = __cargs_false;
- return 0;
- }
- const size_t searchOptionLong(
- ParseContext* context,
- const char* longName,
- OUT __cargs_bool* success)
- {
- const OptionData* items = context->items;
- for (size_t i = 0; i < context->itemsCount; i++) {
- if (items[i].longOption == NULL) {
- continue;
- }
- if (strcmp(items[i].longOption, longName) == 0) {
- *success = __cargs_true;
- return i;
- }
- }
- *success = __cargs_false;
- return 0;
- }
- const OptionLengthCategory getOptionCategory(char* option)
- {
- if (option == NULL) {
- return None;
- }
- const size_t len = strlen(option);
- if (strncmp(option, "--", 2) == 0) { // if arg starts with '--'
- return len > 2 ? Long : None;
- }
- if (strncmp(option, "-", 1) == 0) { // if arg starts with '-'
- return len > 1 ? Short : None;
- }
- return None;
- }
- void processNonBooleanArgs(
- ParseContext* context,
- ParseResult* result,
- const size_t optionIndex,
- const OptionData* data,
- int argc,
- char* argv[],
- const int pos,
- OUT size_t* nextPos)
- {
- if (pos == argc - 1) {
- ParseError error = {
- .category = NonBooleanOptionDoesntHaveArgument,
- .argIndex = pos,
- .optionShortName = NULLCHAR,
- };
- ADD_ELEM(
- ParseError,
- result->errors,
- error,
- result->errorsCount);
- result->errorsCount++;
- *nextPos = pos + 1;
- return;
- }
- switch (data->type) {
- case Integer:
- if (!isInteger(argv[pos + 1])) {
- ParseError error = {
- .category = IntegerParsingError,
- .argIndex = pos,
- .optionShortName = NULLCHAR,
- };
- ADD_ELEM(
- ParseError,
- result->errors,
- error,
- result->errorsCount);
-
- result->errorsCount++;
- *nextPos = pos + 2;
- return;
- }
- __cargs_int valInt = (__cargs_int)atoi(argv[pos + 1]);
- ADD_ELEM(
- __cargs_int,
- result->integerValues,
- valInt,
- result->integerOptionsCount);
-
- ADD_ELEM(
- size_t,
- result->integerOptions,
- optionIndex,
- result->integerOptionsCount);
-
- result->integerOptionsCount++;
- *nextPos = pos + 2;
- __cargs_int* sourceInt = (__cargs_int*)data->sourcePtr;
- PTR_NULLSAFE_SET(sourceInt, valInt);
-
- break;
- case String:
- { }
- char* valStr = argv[pos + 1];
- ADD_ELEM(
- char*,
- result->stringValues,
- valStr,
- result->stringOptionsCount);
-
- ADD_ELEM(
- size_t,
- result->stringOptions,
- optionIndex,
- result->stringOptionsCount);
-
- result->stringOptionsCount++;
- *nextPos = pos + 2;
- char** sourceStr = (char**)data->sourcePtr;
- PTR_NULLSAFE_SET(sourceStr, valStr);
- break;
- case Decimal:
- if (!isDecimal(argv[pos + 1])) {
- ParseError error = {
- .category = DecimalParsingError,
- .argIndex = pos,
- .optionShortName = NULLCHAR,
- };
- ADD_ELEM(
- ParseError,
- result->errors,
- error,
- result->errorsCount);
-
- result->errorsCount++;
- *nextPos = pos + 2;
- return;
- }
- __cargs_decimal valDecimal = (__cargs_decimal)atof(argv[pos + 1]);
- ADD_ELEM(
- __cargs_decimal,
- result->decimalValues,
- valDecimal,
- result->decimalOptionsCount);
-
- ADD_ELEM(
- size_t,
- result->decimalOptions,
- valDecimal,
- result->decimalOptionsCount);
-
- result->decimalOptionsCount++;
- *nextPos = pos + 2;
- __cargs_decimal* sourceDbl = (__cargs_decimal*)data->sourcePtr;
- PTR_NULLSAFE_SET(sourceDbl, valDecimal);
-
- break;
- default:
- *nextPos = pos + 1;
- break;
- }
- }
- void parseCurrentArg(
- ParseContext* context,
- ParseResult* result,
- const int argc,
- char* argv[],
- const size_t pos,
- OUT size_t* nextPos)
- {
- char* arg = argv[pos];
- const OptionLengthCategory cat = getOptionCategory(arg);
- *nextPos = pos;
-
- OptionData* items = context->items;
- switch (cat) {
- case Long:
- goto longProcessing;
-
- return;
- case Short:
- goto shortProcessing;
- break;
- case None:
- ADD_ELEM(char*, result->freeValues, arg, result->freeValuesCount);
- result->freeValuesCount++;
- *nextPos = *nextPos + 1;
- break;
- }
- return;
- // TODO: REFACTOR THIS!!!
- longProcessing:
- // arg + 2 -> pointer to start of a arg name (after --)
- { }
- __cargs_bool searchSuccess = __cargs_false;
- size_t optionIndex = searchOptionLong(context, arg + 2, &searchSuccess);
- if (!searchSuccess) {
- ParseError error = {
- .category = OptionDoesntExist,
- .argIndex = pos,
- .optionShortName = NULLCHAR,
- };
- ADD_ELEM(ParseError, result->errors, error, result->errorsCount);
- result->errorsCount++;
- *nextPos = *nextPos + 1;
- return;
- }
- OptionData option = items[optionIndex];
- if (option.type != Boolean) {
- processNonBooleanArgs(
- context, result, optionIndex, &option, argc, argv, pos, nextPos);
- return;
- }
- ADD_ELEM(
- size_t,
- result->booleanOptions,
- optionIndex,
- result->booleanOptionsCount);
- __cargs_bool* srcBool = (__cargs_bool*)option.sourcePtr;
- PTR_NULLSAFE_SET(srcBool, __cargs_true);
- *nextPos = *nextPos + 1;
- result->booleanOptionsCount++;
- return;
- // TODO: REFACTOR THIS!!!
- shortProcessing:
- { }
- const size_t len = strlen(arg);
- if (len == 2) {
- __cargs_bool searchSuccess = __cargs_false;
- size_t optionIndex = searchOptionShort(context, arg[1], &searchSuccess);
- if (!searchSuccess) {
- ParseError error = {
- .category = OptionDoesntExist,
- .argIndex = pos,
- .optionShortName = NULLCHAR,
- };
- ADD_ELEM(ParseError, result->errors, error, result->errorsCount);
- result->errorsCount++;
- *nextPos = *nextPos + 1;
- return;
- }
- OptionData option = items[optionIndex];
- if (option.type != Boolean) {
- processNonBooleanArgs(
- context, result, optionIndex,
- &option, argc, argv, pos, nextPos);
- return;
- }
- ADD_ELEM(
- size_t,
- result->booleanOptions,
- optionIndex,
- result->booleanOptionsCount);
-
- __cargs_bool* srcBool = (__cargs_bool*)option.sourcePtr;
- PTR_NULLSAFE_SET(srcBool, __cargs_true);
- *nextPos = *nextPos + 1;
- }
- else {
- // args should be boolean
- for (size_t i = 1; i < len; i++) {
- __cargs_bool charSuccess = __cargs_false;
- const size_t index = searchOptionShort(context, arg[i], &charSuccess);
- if (!charSuccess) {
- ParseError error = {
- .category = OptionDoesntExist,
- .argIndex = pos,
- .optionShortName = arg[i],
- };
- ADD_ELEM(
- ParseError,
- result->errors,
- error,
- result->errorsCount);
-
- result->errorsCount++;
- continue;
- }
- OptionData option = items[index];
- if (option.type != Boolean) {
- ParseError error = {
- .category = CombinedOptionsShouldBeBoolean,
- .argIndex = pos,
- .optionShortName = option.shortOption,
- };
- ADD_ELEM(
- ParseError,
- result->errors,
- error,
- result->errorsCount);
-
- result->errorsCount++;
- continue;
- }
- ADD_ELEM(
- size_t,
- result->booleanOptions,
- optionIndex,
- result->booleanOptionsCount);
-
- __cargs_bool* srcBool = (__cargs_bool*)option.sourcePtr;
- PTR_NULLSAFE_SET(srcBool, __cargs_true);
- }
- *nextPos = *nextPos + 1;
- }
- }
- const __cargs_bool fprintErrors(
- FILE* file,
- ParseContext* context,
- ParseResult* result,
- const int argc,
- char* argv[])
- {
- ParseError* errors = result->errors;
- __cargs_bool status = __cargs_false;
-
- for (size_t i = 0; i < result->errorsCount; i++) {
- const ParseError error = errors[i];
- const ParseErrorCategory cat = error.category;
-
- char* stringFormat;
- switch (cat) {
- case OptionDoesntExist:
- stringFormat = "%s: Unrecognized option '%s'\n";
- break;
- case NonBooleanOptionDoesntHaveArgument:
- stringFormat = "%s: Option '%s' requires an argument\n";
- break;
- case CombinedOptionsShouldBeBoolean:
- stringFormat = "%s: Option '%s' is not boolean so cannot be combined\n";
- break;
- case IntegerParsingError:
- stringFormat = "%s: Unable to cast '%s' argument value to integer\n";
- break;
- case DecimalParsingError:
- stringFormat = "%s: Unable to cast '%s' argument value to decimal\n";
- break;
- }
- if (error.optionShortName == NULLCHAR) {
- fprintf(file, stringFormat, argv[0], argv[error.argIndex]);
- }
- else {
- char argName[4];
- sprintf(OUT argName, "-%c", error.optionShortName);
- fprintf(file, stringFormat, argv[0], argName);
- }
-
- status = __cargs_true;
- }
- return status;
- }
- const __cargs_bool printErrors(
- ParseContext* context,
- ParseResult* result,
- const int argc,
- char* argv[])
- {
- return fprintErrors(stderr, context, result, argc, argv);
- }
- void fprintHelp(FILE* file, ParseContext* context)
- {
- OptionData* options = context->items;
- size_t maxLongNameLen = 0;
- for (size_t i = 0; i < context->itemsCount; i++) {
- const OptionData option = options[i];
- if (option.longOption == NULL) {
- continue;
- }
- const size_t longNameLen = strlen(option.longOption);
- if (longNameLen > maxLongNameLen) {
- maxLongNameLen = longNameLen;
- }
- }
- maxLongNameLen += 2; // --optionName
- char format[64];
- sprintf(format, " %%s%%c %%%zus %%s\n", maxLongNameLen); // %zu - print size_t
- size_t nextLinesWhitespaceLength =
- + 2 // 2 spaces
- + 2 // smallName (-s)
- + 1 // delimiter
- + 1 // 1 space
- + maxLongNameLen // longName (--long)
- + 4; // 4 spaces
-
- char nextLineFormat[64];
- sprintf(nextLineFormat, "%%%zus%%s\n", nextLinesWhitespaceLength + 2);
- char* longName = ALLOCATE(char, maxLongNameLen + 2);
- for (size_t i = 0; i < context->itemsCount; i++) {
- const OptionData option = options[i];
- char delimiter = ' ';
- char shortName[4];
- if (option.shortOption != NULLCHAR && option.longOption != NULL) {
- delimiter = ',';
- }
- if (option.shortOption == NULLCHAR) {
- sprintf(shortName, " ");
- }
- else {
- sprintf(shortName, "-%c", option.shortOption);
- }
-
- if (option.longOption == NULL) {
- sprintf(longName, " ");
- }
- else {
- sprintf(longName, "--%s", option.longOption);
- }
- char* description;
- if (option.description == NULL) {
- description = " ";
- }
- else {
- description = option.description;
- }
- size_t descriptionLength = strlen(description);
- // required for strtok_r
- char* dynDescription = ALLOCATE(char, descriptionLength + 2);
- strcpy(dynDescription, description);
- char* tokSavePtr;
- char* delim = "\n";
- char* token = strtok_r(dynDescription, delim, &tokSavePtr);
- if (token == NULL) {
- token = "";
- }
- fprintf(file, format, shortName, delimiter, longName, token);
- while (__cargs_true) {
- token = strtok_r(NULL, delim, &tokSavePtr);
- if (token == NULL) {
- break;
- }
- fprintf(file, nextLineFormat, " ", token);
- }
- free(dynDescription);
- }
- free(longName);
- }
- void printHelp(ParseContext* context)
- {
- fprintHelp(stdout, context);
- }
|