#include #include #include #include #include #include "minitest.h" #include "config-parser.h" /* Returns a `Result` containing a dynamically allocated `Config` or a `char*` * error message depending on the `success` field of the `Result`. */ Result parse_config(int argc, char* argv[]) { Result res; res.success = false; Config tmp; tmp.forward = false; tmp.human_readable = false; tmp.help = false; tmp.multiline = false; tmp.precision = 0; size_t i = 1; char* argument; char* precision = NULL; bool next_is_precision = false; while (i < argc) { argument = argv[i]; size_t arglen = strlen(argument); if (next_is_precision) { i += 1; if (precision != NULL) { char* error_msg = malloc(44); snprintf( error_msg, 44, "precision can't be specified multiple times" ); res.result = error_msg; return res; } if (i == argc) { char* error_msg = malloc(57); snprintf( error_msg, 57, "`%s` should be followed by an integer argument", argument ); res.result = error_msg; return res; } precision = argv[i++]; next_is_precision = false; continue; } size_t tacktacklen = 2; if (strncmp(argument, "--", tacktacklen) == 0) { argument += tacktacklen; if (strcmp(argument, "forward") == 0) { tmp.forward = true; } else if (strcmp(argument, "help") == 0) { tmp.help = true; } else if (strcmp(argument, "human-readable") == 0) { tmp.human_readable = true; } else if (strcmp(argument, "multiline") == 0) { tmp.multiline = true; } else if (strcmp(argument, "precision") == 0) { next_is_precision = true; continue; } else { argument -= tacktacklen; size_t msg_size = 18 + strlen(argument); char* error_msg = malloc(msg_size); snprintf( error_msg, msg_size, "unknown option `%s`", argument ); res.result = error_msg; return res; } i++; continue; } if (strncmp(argument, "-p", 2) == 0 && arglen != 2) { if (precision != NULL) { char* error_msg = malloc(44); snprintf( error_msg, 44, "precision can't be specified multiple times" ); res.result = error_msg; return res; } precision = argument + 2; i++; continue; } size_t opt_i = 0; while (++opt_i < arglen) { size_t msg_size; char opt = *(argument + opt_i); switch (opt) { case 'h': tmp.human_readable = true; break; case 'm': tmp.multiline = true; break; case 'o': tmp.forward = true; break; case 'p': next_is_precision = true; if (opt_i != arglen - 1) { msg_size = 75; char* error_msg = malloc(msg_size); snprintf( error_msg, msg_size, "`-p` should be the last option when combined with other one-letter options" ); res.result = error_msg; return res; } break; case '?': tmp.help = true; break; default: msg_size = 20; char* error_msg = malloc(msg_size); snprintf( error_msg, msg_size, "unknown option `-%c`", opt ); res.result = error_msg; return res; } } i += !next_is_precision; } if (precision != NULL) { char* endptr; errno = 0; tmp.precision = (int) strtol(precision, &endptr, 10); if (errno != 0) { char* error_msg = malloc(64); char* errno_str = strerror(errno); snprintf( error_msg, 64, "invalid precision: %s", errno_str ); free(errno_str); res.result = error_msg; return res; } if (*endptr != '\0') { char* error_msg = malloc(53); snprintf( error_msg, 53, "the precision may not contain non-numeric characters" ); res.result = error_msg; return res; } if (tmp.precision <= 0) { char* error_msg = malloc(50); snprintf( error_msg, 50, "the precision can't be less than or equal to zero" ); res.result = error_msg; return res; } } Config* conf = malloc(sizeof(Config)); conf->forward = tmp.forward; conf->help = tmp.help; conf->human_readable = tmp.human_readable; conf->multiline = tmp.multiline; conf->precision = tmp.precision; res.success = true; res.result = conf; return res; } char* test_default_config() { int argc = 1; char* argv[] = { "stdu" }; Result res = parse_config(argc, argv); Config* conf = (Config*) res.result; mt_assert_eq(res.success, true); mt_assert_eq(conf->human_readable, false); mt_assert_eq(conf->help, false); mt_assert_eq(conf->multiline, false); mt_assert_eq(conf->precision, 0); free(conf); return 0; } char* test_precision_parsing() { int argc; Result res; Config* conf; argc = 3; char* argv[] = { "stdu", "--precision", "3" }; res = parse_config(argc, argv); conf = (Config*) res.result; mt_assert_eq(res.success, true); mt_assert_eq(conf->precision, 3); mt_assert_eq(conf->human_readable, false); free(conf); argc = 2; char* argv2[] = { "stdu", "-p" }; res = parse_config(argc, argv2); mt_assert( res.success == false, "not specifying precision after `-p` didn't error" ); free(res.result); argc = 4; char* argv3[] = { "stdu", "--precision", "1", "-p3" }; res = parse_config(argc, argv3); mt_assert( res.success == false, "specifying precision multiple times didn't error" ); free(res.result); return 0; } char* test_forward_parsing() { int argc = 2; char* argv1[] = { "stdu", "--forward" }; char* argv2[] = { "stdu", "-o" }; Result res1 = parse_config(argc, argv1); Result res2 = parse_config(argc, argv2); Config* conf1 = (Config*) res1.result; Config* conf2 = (Config*) res2.result; mt_assert_eq(res1.success, true); mt_assert_eq(res2.success, true); mt_assert_eq(conf1->forward, true); mt_assert_eq(conf2->forward, true); free(conf1); free(conf2); return 0; } char* test_help_parsing() { int argc = 2; char* argv1[] = { "stdu", "--help" }; char* argv2[] = { "stdu", "-?" }; Result res1 = parse_config(argc, argv1); Result res2 = parse_config(argc, argv2); Config* conf1 = (Config*) res1.result; Config* conf2 = (Config*) res2.result; mt_assert_eq(res1.success, true); mt_assert_eq(res2.success, true); mt_assert_eq(conf1->help, true); mt_assert_eq(conf2->help, true); free(conf1); free(conf2); return 0; } char* test_human_readability_parsing() { int argc = 2; char* argv1[] = { "stdu", "--human-readable" }; char* argv2[] = { "stdu", "-h" }; Result res1 = parse_config(argc, argv1); Result res2 = parse_config(argc, argv2); Config* conf1 = (Config*) res1.result; Config* conf2 = (Config*) res2.result; mt_assert_eq(res1.success, true); mt_assert_eq(res2.success, true); mt_assert_eq(conf1->human_readable, true); mt_assert_eq(conf2->human_readable, true); free(conf1); free(conf2); return 0; } char* test_multioption_parsing() { int argc = 3; char* argv[] = { "stdu", "-mho?p", "3"}; Result res = parse_config(argc, argv); Config* conf = (Config*) res.result; mt_assert_eq(res.success, true); mt_assert_eq(conf->help, true); mt_assert_eq(conf->human_readable, true); mt_assert_eq(conf->multiline, true); mt_assert_eq(conf->forward, true); mt_assert_eq(conf->precision, 3); free(conf); return 0; } void parsing_tests() { mt_run_test(test_default_config); mt_run_test(test_forward_parsing); mt_run_test(test_human_readability_parsing); mt_run_test(test_help_parsing); mt_run_test(test_multioption_parsing); mt_run_test(test_precision_parsing); }