diff --git a/include/cargs.h b/include/cargs.h index 8bc3ed9..6356d02 100644 --- a/include/cargs.h +++ b/include/cargs.h @@ -88,6 +88,11 @@ typedef struct cag_option_context char *value; } cag_option_context; +/** + * Prototype for printer used in cag_option_printer. For example fprintf have same prototype + */ +typedef int (*cag_printer)(void *ctx, const char *fmt, ...); + /** * This is just a small macro which calculates the size of an array. */ @@ -196,8 +201,26 @@ CAG_PUBLIC char cag_option_get_error_letter(const cag_option_context *context); * @param context Pointer to the context from which the option was fetched. * @param destination Pointer to the file stream where the error information will be printed. */ +#ifndef CAG_NO_FILE CAG_PUBLIC void cag_option_print_error(const cag_option_context *context, FILE *destination); +#endif + +/** + * @brief Prints the error associated with the invalid option using user + * callback. + * + * This function prints information about the error associated with the invalid + * option using user callback. Callback prototype is same with fprintf. It helps + * in displaying the error of the current context. + * + * @param context Pointer to the context from which the option was fetched. + * @param printer The printer callback function. For example fprintf. + * @param printer_ctx The parameter for printer callback. For example fprintf + * could use parameter stderr. + */ +CAG_PUBLIC void cag_option_printer_error(const cag_option_context *context, + cag_printer printer, void *printer_ctx); /** * @brief Prints all options to the terminal. @@ -209,8 +232,27 @@ CAG_PUBLIC void cag_option_print_error(const cag_option_context *context, * @param option_count The option count which will be printed. * @param destination The destination where the output will be printed. */ +#ifndef CAG_NO_FILE CAG_PUBLIC void cag_option_print(const cag_option *options, size_t option_count, FILE *destination); +#endif + + /** + * @brief Prints all options using user callback. + * + * This function prints all options using user callback. This can be used to + * generate the output for a "--help" option. + * Using user callback is useful in tiny system without FILE support + * + * @param options The options which will be printed. + * @param option_count The option count which will be printed. + * @param destination The destination where the output will be printed. + * @param printer The printer callback function. For example fprintf. + * @param printer_ctx The parameter for printer callback. For example fprintf + * could use parameter stderr. + */ +CAG_PUBLIC void cag_option_printer(const cag_option *options, + size_t option_count, cag_printer printer, void *printer_ctx); CAG_DEPRECATED( "cag_option_prepare has been deprecated. Use cag_option_init instead.") diff --git a/src/cargs.c b/src/cargs.c index 8148b22..68553da 100644 --- a/src/cargs.c +++ b/src/cargs.c @@ -8,25 +8,25 @@ #define CAG_OPTION_PRINT_MIN_INDENTION 20 static void cag_option_print_value(const cag_option *option, - size_t *accessor_length, FILE *destination) + size_t *accessor_length, cag_printer printer, void *printer_ctx) { if (option->value_name != NULL) { - *accessor_length += fprintf(destination, "=%s", option->value_name); + *accessor_length += printer(printer_ctx, "=%s", option->value_name); } } static void cag_option_print_letters(const cag_option *option, bool *first, - size_t *accessor_length, FILE *destination) + size_t *accessor_length, cag_printer printer, void *printer_ctx) { const char *access_letter; access_letter = option->access_letters; if (access_letter != NULL) { while (*access_letter) { if (*first) { - *accessor_length += fprintf(destination, "-%c", *access_letter); + *accessor_length += printer(printer_ctx, "-%c", *access_letter); *first = false; } else { - *accessor_length += fprintf(destination, ", -%c", *access_letter); + *accessor_length += printer(printer_ctx, ", -%c", *access_letter); } ++access_letter; } @@ -34,13 +34,13 @@ static void cag_option_print_letters(const cag_option *option, bool *first, } static void cag_option_print_name(const cag_option *option, bool *first, - size_t *accessor_length, FILE *destination) + size_t *accessor_length, cag_printer printer, void *printer_ctx) { if (option->access_name != NULL) { if (*first) { - *accessor_length += fprintf(destination, "--%s", option->access_name); + *accessor_length += printer(printer_ctx, "--%s", option->access_name); } else { - *accessor_length += fprintf(destination, ", --%s", option->access_name); + *accessor_length += printer(printer_ctx, ", --%s", option->access_name); } } } @@ -443,8 +443,8 @@ CAG_PUBLIC char cag_option_get_error_letter(const cag_option_context *context) return context->error_letter; } -CAG_PUBLIC void cag_option_print_error(const cag_option_context *context, - FILE *destination) +CAG_PUBLIC void cag_option_printer_error(const cag_option_context *context, + cag_printer printer, void *printer_ctx) { int error_index; char error_letter; @@ -456,15 +456,31 @@ CAG_PUBLIC void cag_option_print_error(const cag_option_context *context, error_letter = cag_option_get_error_letter(context); if (error_letter) { - fprintf(destination, "Unknown option '%c' in '%s'.\n", error_letter, + printer(printer_ctx, "Unknown option '%c' in '%s'.\n", error_letter, context->argv[error_index]); } else { - fprintf(destination, "Unknown option '%s'.\n", context->argv[error_index]); + printer(printer_ctx, "Unknown option '%s'.\n", context->argv[error_index]); } } -void cag_option_print(const cag_option *options, size_t option_count, - FILE *destination) +#ifndef CAG_NO_FILE +CAG_PUBLIC void cag_option_print_error(const cag_option_context* context, + FILE* destination) +{ + cag_option_printer_error(context, (cag_printer)fprintf, destination); +} +#endif + +#ifndef CAG_NO_FILE +void cag_option_print(const cag_option* options, size_t option_count, + FILE* destination) +{ + cag_option_printer(options, option_count, (cag_printer)fprintf, destination); +} +#endif + +CAG_PUBLIC void cag_option_printer(const cag_option *options, + size_t option_count, cag_printer printer, void *printer_ctx) { size_t option_index, indention, i, accessor_length; const cag_option *option; @@ -477,20 +493,19 @@ void cag_option_print(const cag_option *options, size_t option_count, accessor_length = 0; first = true; - fputs(" ", destination); + printer(printer_ctx, " "); - cag_option_print_letters(option, &first, &accessor_length, destination); - cag_option_print_name(option, &first, &accessor_length, destination); - cag_option_print_value(option, &accessor_length, destination); + cag_option_print_letters(option, &first, &accessor_length, printer, + printer_ctx); + cag_option_print_name(option, &first, &accessor_length, printer, + printer_ctx); + cag_option_print_value(option, &accessor_length, printer, printer_ctx); for (i = accessor_length; i < indention; ++i) { - fputs(" ", destination); + printer(printer_ctx, " "); } - fputs(" ", destination); - fputs(option->description, destination); - - fprintf(destination, "\n"); + printer(printer_ctx, " %s\n", option->description); } } diff --git a/test/option_test.c b/test/option_test.c index b2a7aba..9c8f53f 100755 --- a/test/option_test.c +++ b/test/option_test.c @@ -787,6 +787,7 @@ int option_boundaries_mix(void) int option_print(void) { +#ifndef CAG_NO_FILE char buf[255]; const char *expected; FILE *test_file; @@ -824,6 +825,59 @@ int option_print(void) fclose(test_file); err_open: return EXIT_FAILURE; + +#else // #ifndef CAG_NO_FILE + return EXIT_SUCCESS; +#endif +} + +#ifdef CAG_NO_FILE +typedef void FILE_CTX; +#else +typedef FILE FILE_CTX; +#endif + +int option_printer(void) +{ + char buf[255]; + const char *expected; + FILE_CTX *test_file; + + expected = " -s Simple flag\n" + " -a Another simple flag\n" + " -m, -M, -o, -O Multiple access letters\n" + " --long Long parameter name\n" + " -k, --key=VALUE Parameter value\n"; + + test_file = tmpfile(); + if (test_file == NULL) { + goto err_open; + } + + cag_option_printer(options, CAG_ARRAY_SIZE(options), (cag_printer)fprintf, + test_file); + if (fseek(test_file, 0, SEEK_SET) != 0) { + goto err_seek; + } + + if (fread(buf, sizeof(buf), 1, test_file) != 1 && feof(test_file) == 0) { + goto err_read; + } + + if (memcmp(buf, expected, strlen(expected)) != 0) { + goto err_test; + } + + fclose(test_file); + return EXIT_SUCCESS; + +err_test: +err_read: +err_seek: + fclose(test_file); +err_open: + return EXIT_FAILURE; + } int option_error_print_short(void) @@ -831,7 +885,7 @@ int option_error_print_short(void) int status; char buf[255]; const char *expected; - FILE *test_file; + FILE_CTX *test_file; expected = "Unknown option 'u' in '-abu'.\n"; @@ -850,7 +904,12 @@ int option_error_print_short(void) context.error_index = 1; context.error_letter = 'u'; +#ifdef CAG_NO_FILE + cag_option_printer_error(&context, (cag_printer)fprintf, test_file); +#else cag_option_print_error(&context, test_file); +#endif + if (fseek(test_file, 0, SEEK_SET) != 0) { goto err_seek; } @@ -882,7 +941,7 @@ int option_error_print_long(void) int status; char buf[255]; const char *expected; - FILE *test_file; + FILE_CTX *test_file; expected = "Unknown option '--unknown'.\n"; @@ -901,7 +960,7 @@ int option_error_print_long(void) context.error_index = 1; context.error_letter = 0; - cag_option_print_error(&context, test_file); + cag_option_printer_error(&context, (cag_printer)fprintf, test_file); if (fseek(test_file, 0, SEEK_SET) != 0) { goto err_seek; }