/** dropt_strnicmp * * Compares the first n characters of two strings, ignoring case * differences. Not recommended for non-ASCII strings. * * PARAMETERS: * IN s, t : The strings to compare. * IN n : The maximum number of dropt_char-s to compare. * * RETURNS: * 0 if the strings are equivalent, * < 0 if s is lexically less than t, * > 0 if s is lexically greater than t. */ int dropt_strnicmp(const dropt_char* s, const dropt_char* t, size_t n) { assert(s != NULL); assert(t != NULL); if (s == t) { return 0; } while (n--) { if (*s == DROPT_TEXT_LITERAL('\0') && *t == DROPT_TEXT_LITERAL('\0')) { break; } else if (*s == *t || dropt_tolower(*s) == dropt_tolower(*t)) { s++; t++; } else { return (dropt_tolower(*s) < dropt_tolower(*t)) ? -1 : +1; } } return 0; }
/** dropt_vsnprintf * * vsnprintf wrapper to provide ISO C99-compliant behavior. * * PARAMETERS: * OUT s : The destination buffer. May be NULL if n is 0. * If non-NULL, always NUL-terminated. * IN n : The size of the destination buffer, measured in * dropt_char-s. * IN format : printf-style format specifier. Must not be NULL. * IN args : Arguments to insert into the formatted string. * * RETURNS: * The number of characters that would be written to the destination * buffer if it's sufficiently large, excluding the NUL-terminator. * Returns -1 on error. */ int dropt_vsnprintf(dropt_char* s, size_t n, const dropt_char* format, va_list args) { #if __STDC_VERSION__ >= 199901L || __GNUC__ /* ISO C99-compliant. * * As far as I can tell, gcc's implementation of vsnprintf has always * matched the behavior required by the C99 standard (which is to * return the necessary buffer size). * * Note that this won't work with wchar_t because there is no true, * standard wchar_t equivalent of snprintf. swprintf comes close but * doesn't return the necessary buffer size (and the standard does not * provide a guaranteed way to test if truncation occurred), and its * format string can't be used interchangeably with snprintf. * * It's simpler not to support wchar_t on non-Windows platforms. */ assert(format != NULL); return vsnprintf(s, n, format, args); #elif defined __BORLANDC__ /* Borland's compiler neglects to NUL-terminate. */ int ret; assert(format != NULL); ret = vsnprintf(s, n, format, args); if (n != 0) { s[n - 1] = DROPT_TEXT_LITERAL('\0'); } return ret; #elif defined _MSC_VER /* _vsntprintf and _vsnprintf_s on Windows don't have C99 semantics; * they return -1 if truncation occurs. */ va_list argsCopy; int ret; assert(format != NULL); va_copy(argsCopy, args); ret = _vsctprintf(format, argsCopy); va_end(argsCopy); if (n != 0) { assert(s != NULL); #if _MSC_VER >= 1400 (void) _vsntprintf_s(s, n, _TRUNCATE, format, args); #else /* This version doesn't necessarily NUL-terminate. Sigh. */ (void) _vsnprintf(s, n, format, args); s[n - 1] = DROPT_TEXT_LITERAL('\0'); #endif } return ret; #else #error Unsupported platform. dropt_vsnprintf unimplemented. return -1; #endif }
/** dropt_get_error_message * * PARAMETERS: * IN context : The dropt context. * Must not be NULL. * * RETURNS: * The current error message waiting in the dropt context or the empty * string if there are no errors. Note that calling any dropt * function other than dropt_get_error, dropt_get_error_details, and * dropt_get_error_message may invalidate a previously-returned * string. */ const dropt_char * dropt_get_error_message(dropt_context * context) { if (context == NULL) { DROPT_MISUSE("No dropt context specified."); return DROPT_TEXT_LITERAL(""); } if (context->errorDetails.err == dropt_error_none) { return DROPT_TEXT_LITERAL(""); } if (context->errorDetails.message == NULL) { if (context->errorHandler != NULL) { context->errorDetails.message = context->errorHandler(context->errorDetails.err, context->errorDetails.optionName, context->errorDetails.optionArgument, context->errorHandlerData); } else { #ifndef DROPT_NO_STRING_BUFFERS context->errorDetails.message = dropt_default_error_handler(context->errorDetails.err, context->errorDetails.optionName, context->errorDetails.optionArgument); #endif } } return (context->errorDetails.message == NULL) ? DROPT_TEXT_LITERAL("Unknown error") : context->errorDetails.message; }
/** dropt_strndup * * Duplicates the first n characters of a string. * * PARAMETERS: * IN s : The string to duplicate. * IN n : The maximum number of dropt_char-s to copy, excluding the * NUL-terminator. * * RETURNS: * The duplicated string, which is always NUL-terminated. The caller * is responsible for calling free() on it when no longer needed. * Returns NULL on error. */ dropt_char* dropt_strndup(const dropt_char* s, size_t n) { dropt_char* copy; size_t len = 0; assert(s != NULL); while (len < n && s[len] != DROPT_TEXT_LITERAL('\0')) { len++; } if (len + 1 < len) { /* This overflow check shouldn't be strictly necessary. len can be * at most SIZE_MAX, so SIZE_MAX + 1 can wrap around to 0, but * dropt_safe_malloc will return NULL for a 0-sized allocation. * However, favor defensive paranoia. */ return NULL; } copy = dropt_safe_malloc(len + 1 /* NUL */, sizeof *copy); if (copy != NULL) { memcpy(copy, s, len * sizeof *copy); copy[len] = DROPT_TEXT_LITERAL('\0'); } return copy; }
/** dropt_default_error_handler * * Default error handler. * * PARAMETERS: * IN error : The error code. * IN optionName : The name of the option we failed on. * IN optionArgument : The value of the option we failed on. * Pass NULL if unwanted. * * RETURNS: * An allocated string for the given error. The caller is responsible * for calling free() on it when no longer needed. * May return NULL. */ dropt_char * dropt_default_error_handler(dropt_error error, const dropt_char * optionName, const dropt_char * optionArgument) { dropt_char * s = NULL; const dropt_char * separator = DROPT_TEXT_LITERAL(": "); if (optionArgument == NULL) { separator = optionArgument = DROPT_TEXT_LITERAL(""); } switch (error) { case dropt_error_none: /* This shouldn't happen (unless client code invokes this * directly with dropt_error_none), but it's here for * completeness. */ break; case dropt_error_bad_configuration: s = dropt_strdup(DROPT_TEXT_LITERAL("Invalid option configuration")); break; case dropt_error_invalid_option: s = dropt_asprintf(DROPT_TEXT_LITERAL("Invalid option: %s"), optionName); break; case dropt_error_insufficient_arguments: s = dropt_asprintf(DROPT_TEXT_LITERAL("Value required after option %s"), optionName); break; case dropt_error_mismatch: s = dropt_asprintf(DROPT_TEXT_LITERAL("Invalid value for option %s%s%s"), optionName, separator, optionArgument); break; case dropt_error_overflow: s = dropt_asprintf(DROPT_TEXT_LITERAL("Value too large for option %s%s%s"), optionName, separator, optionArgument); break; case dropt_error_underflow: s = dropt_asprintf(DROPT_TEXT_LITERAL("Value too small for option %s%s%s"), optionName, separator, optionArgument); break; case dropt_error_insufficient_memory: s = dropt_strdup(DROPT_TEXT_LITERAL("Insufficient memory")); break; case dropt_error_unknown: default: s = dropt_asprintf(DROPT_TEXT_LITERAL("Unknown error handling option %s"), optionName); break; } return s; }
/** set_short_option_error_details * * Generates error details in the dropt context. * * PARAMETERS: * IN/OUT context : The dropt context. * IN err : The error code. * IN shortName : the "short" name of the option we failed on. * IN optionArgument : The value of the option we failed on. * Pass NULL if unwanted. */ static void set_short_option_error_details(dropt_context * context, dropt_error err, dropt_char shortName, const dropt_char * optionArgument) { /* "-?" is just a placeholder. */ dropt_char shortNameBuf[] = DROPT_TEXT_LITERAL("-?"); assert(context != NULL); assert(shortName != DROPT_TEXT_LITERAL('\0')); shortNameBuf[1] = shortName; set_error_details(context, err, make_char_array(shortNameBuf, ARRAY_LENGTH(shortNameBuf) - 1), optionArgument); }
/** find_option_short * * Finds the option specification for a short option name (i.e., an * option of the form "-o"). * * PARAMETERS: * IN context : The dropt context. * IN shortName : The short option name to search for. * * RETURNS: * A pointer to the corresponding option specification or NULL if not * found. */ static const dropt_option * find_option_short(const dropt_context * context, dropt_char shortName) { assert(context != NULL); assert(shortName != DROPT_TEXT_LITERAL('\0')); assert(context->ncmpstr != NULL); if (context->sortedByShort != NULL) { option_proxy * found = bsearch(&shortName, context->sortedByShort, context->numOptions, sizeof * (context->sortedByShort), cmp_key_option_proxy_short); return (found == NULL) ? NULL : found->option; } /* Fall back to a linear search. */ { const dropt_option * option; for (option = context->options; is_valid_option(option); option++) { if (context->ncmpstr(&shortName, &option->short_name, 1) == 0) { return option; } } } return NULL; }
/** dropt_ssclear * * Clears and re-initializes a dropt_stringstream. * * PARAMETERS: * IN/OUT ss : The dropt_stringstream */ void dropt_ssclear(dropt_stringstream* ss) { assert(ss != NULL); ss->string[0] = DROPT_TEXT_LITERAL('\0'); ss->used = 0; dropt_ssresize(ss, default_stringstream_buffer_size); }
/** is_valid_option * * PARAMETERS: * IN option : Specification for an individual option. * * RETURNS: * true if the specified option is valid, false if it's a sentinel * value. */ static bool is_valid_option(const dropt_option * option) { return option != NULL && !(option->long_name == NULL && option->short_name == DROPT_TEXT_LITERAL('\0') && option->description == NULL && option->arg_description == NULL && option->handler == NULL && option->handler_data == NULL && option->attr == 0); }
/** dropt_new_context * * Creates a new dropt context. * * PARAMETERS: * IN options : The list of option specifications. * Must not be NULL. * * RETURNS: * An allocated dropt context. The caller is responsible for freeing * it with dropt_free_context when no longer needed. * Returns NULL on error. */ dropt_context * dropt_new_context(const dropt_option * options) { dropt_context * context = NULL; size_t n; if (options == NULL) { DROPT_MISUSE("No option list specified."); goto exit; } /* Sanity-check the options. */ for (n = 0; is_valid_option(&options[n]); n++) { if (options[n].short_name == DROPT_TEXT_LITERAL('=') || (options[n].long_name != NULL && dropt_strchr(options[n].long_name, DROPT_TEXT_LITERAL('=')) != NULL)) { DROPT_MISUSE("Invalid option list. '=' may not be used in an option name."); goto exit; } } context = malloc(sizeof * context); if (context == NULL) { goto exit; } else { dropt_context emptyContext = { 0 }; *context = emptyContext; context->options = options; context->numOptions = n; dropt_set_strncmp(context, NULL); } exit: return context; }
/** dropt_ssopen * * Constructs a new dropt_stringstream. * * RETURNS: * An initialized dropt_stringstream. The caller is responsible for * calling either dropt_ssclose() or dropt_ssfinalize() on it when * no longer needed. * Returns NULL on error. */ dropt_stringstream * dropt_ssopen(void) { dropt_stringstream * ss = malloc(sizeof * ss); if (ss != NULL) { ss->used = 0; ss->maxSize = default_stringstream_buffer_size; ss->string = dropt_safe_malloc(ss->maxSize, sizeof * ss->string); if (ss->string == NULL) { free(ss); ss = NULL; } else { ss->string[0] = DROPT_TEXT_LITERAL('\0'); } } return ss; }
/** dropt_get_help * * PARAMETERS: * IN context : The dropt context. * Must not be NULL. * IN helpParams : The help parameters. * Pass NULL to use the default help parameters. * * RETURNS: * An allocated help string for the available options. The caller is * responsible for calling free() on it when no longer needed. * Returns NULL on error. */ dropt_char * dropt_get_help(const dropt_context * context, const dropt_help_params * helpParams) { dropt_char * helpText = NULL; dropt_stringstream * ss = dropt_ssopen(); if (context == NULL) { DROPT_MISUSE("No dropt context specified."); } else if (ss != NULL) { const dropt_option * option; dropt_help_params hp; if (helpParams == NULL) { dropt_init_help_params(&hp); } else { hp = *helpParams; } for (option = context->options; is_valid_option(option); option++) { bool hasLongName = option->long_name != NULL && option->long_name[0] != DROPT_TEXT_LITERAL('\0'); bool hasShortName = option->short_name != DROPT_TEXT_LITERAL('\0'); /* The number of characters printed on the current line so far. */ int n; if (option->description == NULL || (option->attr & dropt_attr_hidden)) { /* Undocumented option. Ignore it and move on. */ continue; } else if (hasLongName && hasShortName) { n = dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s-%c, --%s"), hp.indent, DROPT_TEXT_LITERAL(""), option->short_name, option->long_name); } else if (hasLongName) { n = dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s--%s"), hp.indent, DROPT_TEXT_LITERAL(""), option->long_name); } else if (hasShortName) { n = dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s-%c"), hp.indent, DROPT_TEXT_LITERAL(""), option->short_name); } else { /* Comment text. Don't bother with indentation. */ assert(option->description != NULL); dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%s\n"), option->description); goto next; } if (n < 0) { n = 0; } if (option->arg_description != NULL) { int m = dropt_ssprintf(ss, (option->attr & dropt_attr_optional_val) ? DROPT_TEXT_LITERAL("[=%s]") : DROPT_TEXT_LITERAL("=%s"), option->arg_description); if (m > 0) { n += m; } } /* Check for equality to make sure that there's at least one * space between the option name and its description. */ if ((unsigned int) n >= hp.description_start_column) { dropt_ssprintf(ss, DROPT_TEXT_LITERAL("\n")); n = 0; } { const dropt_char * line = option->description; while (line != NULL) { int lineLen; const dropt_char * nextLine; const dropt_char * newline = dropt_strchr(line, DROPT_TEXT_LITERAL('\n')); if (newline == NULL) { lineLen = dropt_strlen(line); nextLine = NULL; } else { lineLen = newline - line; nextLine = newline + 1; } dropt_ssprintf(ss, DROPT_TEXT_LITERAL("%*s%.*s\n"), hp.description_start_column - n, DROPT_TEXT_LITERAL(""), lineLen, line); n = 0; line = nextLine; } } next: if (hp.blank_lines_between_options) { dropt_ssprintf(ss, DROPT_TEXT_LITERAL("\n")); } } helpText = dropt_ssfinalize(ss); } return helpText; }