diff --git a/src/main.c b/src/main.c index a8c217fcda..d04d9747d7 100644 --- a/src/main.c +++ b/src/main.c @@ -129,20 +129,18 @@ static int isoptish(const char* text) { return text[0] == '-' && (text[1] == '-' || isalpha((unsigned char)text[1])); } -static int isoption(const char* text, char shortopt, const char* longopt, size_t *short_opts) { - if (text[0] != '-' || text[1] == '-') - *short_opts = 0; - if (text[0] != '-') return 0; - - // check long option - if (text[1] == '-' && !strcmp(text+2, longopt)) return 1; - else if (text[1] == '-') return 0; - - // must be short option; check it and... - if (!shortopt) return 0; - if (strchr(text, shortopt) != NULL) { - (*short_opts)++; // ...count it (for option stacking) - return 1; +static int isoption(const char** text, char shortopt, const char* longopt, int is_short) { + if (is_short) { + if (shortopt && **text == shortopt) { + (*text)++; + if (!**text) *text = NULL; + return 1; + } + } else { + if (longopt != NULL && !strcmp(*text, longopt)) { + *text = NULL; + return 1; + } } return 0; } @@ -344,9 +342,8 @@ int main(int argc, char* argv[]) { int further_args_are_json = 0; int args_done = 0; int jq_flags = 0; - size_t short_opts = 0; jv lib_search_paths = jv_null(); - for (int i=1; i= argc - 1) { - fprintf(stderr, "-L takes a parameter: (e.g. -L /search/path or -L/search/path)\n"); - die(); - } else { - lib_search_paths = jv_array_append(lib_search_paths, jq_realpath(jv_string(argv[i+1]))); - i++; - } - continue; - } - - if (isoption(argv[i], 's', "slurp", &short_opts)) { - options |= SLURP; - if (!short_opts) continue; - } - if (isoption(argv[i], 'r', "raw-output", &short_opts)) { - options |= RAW_OUTPUT; - if (!short_opts) continue; - } - if (isoption(argv[i], 0, "raw-output0", &short_opts)) { - options |= RAW_OUTPUT | RAW_NO_LF | RAW_OUTPUT0; - if (!short_opts) continue; - } - if (isoption(argv[i], 'j', "join-output", &short_opts)) { - options |= RAW_OUTPUT | RAW_NO_LF; - if (!short_opts) continue; - } - if (isoption(argv[i], 'c', "compact-output", &short_opts)) { - dumpopts &= ~(JV_PRINT_TAB | JV_PRINT_INDENT_FLAGS(7)); - if (!short_opts) continue; - } - if (isoption(argv[i], 'C', "color-output", &short_opts)) { - options |= COLOR_OUTPUT; - if (!short_opts) continue; - } - if (isoption(argv[i], 'M', "monochrome-output", &short_opts)) { - options |= NO_COLOR_OUTPUT; - if (!short_opts) continue; - } - if (isoption(argv[i], 'a', "ascii-output", &short_opts)) { - options |= ASCII_OUTPUT; - if (!short_opts) continue; - } - if (isoption(argv[i], 0, "unbuffered", &short_opts)) { - options |= UNBUFFERED_OUTPUT; - continue; - } - if (isoption(argv[i], 'S', "sort-keys", &short_opts)) { - options |= SORTED_OUTPUT; - if (!short_opts) continue; - } - if (isoption(argv[i], 'R', "raw-input", &short_opts)) { - options |= RAW_INPUT; - if (!short_opts) continue; - } - if (isoption(argv[i], 'n', "null-input", &short_opts)) { - options |= PROVIDE_NULL; - if (!short_opts) continue; - } - if (isoption(argv[i], 'f', "from-file", &short_opts)) { - options |= FROM_FILE; - if (!short_opts) continue; - } - if (isoption(argv[i], 'b', "binary", &short_opts)) { + const char* text = argv[i]; + int is_short; + // First '-' already checked by isoptish + if (text[1] == '-') { + text += 2; + is_short = 0; + } else { + text++; + is_short = 1; + } + int raw; // Temporary for --rawfile + + // Parse a long option in one iteration or iterate over short options + while (text != NULL) { + if (isoption(&text, 's', "slurp", is_short)) { + options |= SLURP; + } else if (isoption(&text, 'r', "raw-output", is_short)) { + options |= RAW_OUTPUT; + } else if (isoption(&text, 0, "raw-output0", is_short)) { + options |= RAW_OUTPUT | RAW_NO_LF | RAW_OUTPUT0; + } else if (isoption(&text, 'j', "join-output", is_short)) { + options |= RAW_OUTPUT | RAW_NO_LF; + } else if (isoption(&text, 'c', "compact-output", is_short)) { + dumpopts &= ~(JV_PRINT_TAB | JV_PRINT_INDENT_FLAGS(7)); + } else if (isoption(&text, 'C', "color-output", is_short)) { + options |= COLOR_OUTPUT; + } else if (isoption(&text, 'M', "monochrome-output", is_short)) { + options |= NO_COLOR_OUTPUT; + } else if (isoption(&text, 'a', "ascii-output", is_short)) { + options |= ASCII_OUTPUT; + } else if (isoption(&text, 0, "unbuffered", is_short)) { + options |= UNBUFFERED_OUTPUT; + } else if (isoption(&text, 'S', "sort-keys", is_short)) { + options |= SORTED_OUTPUT; + } else if (isoption(&text, 'R', "raw-input", is_short)) { + options |= RAW_INPUT; + } else if (isoption(&text, 'n', "null-input", is_short)) { + options |= PROVIDE_NULL; + } else if (isoption(&text, 'f', "from-file", is_short)) { + options |= FROM_FILE; + } else if (isoption(&text, 'L', NULL, is_short)) { + if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) + lib_search_paths = jv_array(); + if (text != NULL) { // -Lname + lib_search_paths = jv_array_append(lib_search_paths, jq_realpath(jv_string(text))); + text = NULL; + } else if (i >= argc - 1) { + fprintf(stderr, "-L takes a parameter: (e.g. -L /search/path or -L/search/path)\n"); + die(); + } else { + lib_search_paths = jv_array_append(lib_search_paths, jq_realpath(jv_string(argv[i+1]))); + i++; + } + } else if (isoption(&text, 'b', "binary", is_short)) { #ifdef WIN32 - fflush(stdout); - fflush(stderr); - _setmode(fileno(stdin), _O_BINARY); - _setmode(fileno(stdout), _O_BINARY); - _setmode(fileno(stderr), _O_BINARY); + fflush(stdout); + fflush(stderr); + _setmode(fileno(stdin), _O_BINARY); + _setmode(fileno(stdout), _O_BINARY); + _setmode(fileno(stderr), _O_BINARY); #endif - if (!short_opts) continue; - } - if (isoption(argv[i], 0, "tab", &short_opts)) { - dumpopts &= ~JV_PRINT_INDENT_FLAGS(7); - dumpopts |= JV_PRINT_TAB | JV_PRINT_PRETTY; - continue; - } - if (isoption(argv[i], 0, "indent", &short_opts)) { - if (i >= argc - 1) { - fprintf(stderr, "%s: --indent takes one parameter\n", progname); - die(); - } - dumpopts &= ~(JV_PRINT_TAB | JV_PRINT_INDENT_FLAGS(7)); - int indent = atoi(argv[i+1]); - if (indent < -1 || indent > 7) { - fprintf(stderr, "%s: --indent takes a number between -1 and 7\n", progname); - die(); - } - dumpopts |= JV_PRINT_INDENT_FLAGS(indent); - i++; - continue; - } - if (isoption(argv[i], 0, "seq", &short_opts)) { - options |= SEQ; - continue; - } - if (isoption(argv[i], 0, "stream", &short_opts)) { - parser_flags |= JV_PARSE_STREAMING; - continue; - } - if (isoption(argv[i], 0, "stream-errors", &short_opts)) { - parser_flags |= JV_PARSE_STREAMING | JV_PARSE_STREAM_ERRORS; - continue; - } - if (isoption(argv[i], 'e', "exit-status", &short_opts)) { - options |= EXIT_STATUS; - if (!short_opts) continue; - } - // FIXME: For --arg* we should check that the varname is acceptable - if (isoption(argv[i], 0, "args", &short_opts)) { - further_args_are_strings = 1; - further_args_are_json = 0; - continue; - } - if (isoption(argv[i], 0, "jsonargs", &short_opts)) { - further_args_are_strings = 0; - further_args_are_json = 1; - continue; - } - if (isoption(argv[i], 0, "arg", &short_opts)) { - if (i >= argc - 2) { - fprintf(stderr, "%s: --arg takes two parameters (e.g. --arg varname value)\n", progname); - die(); - } - if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) - program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), jv_string(argv[i+2])); - i += 2; // skip the next two arguments - continue; - } - if (isoption(argv[i], 0, "argjson", &short_opts)) { - if (i >= argc - 2) { - fprintf(stderr, "%s: --argjson takes two parameters (e.g. --argjson varname text)\n", progname); - die(); - } - if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) { - jv v = jv_parse(argv[i+2]); - if (!jv_is_valid(v)) { - fprintf(stderr, "%s: invalid JSON text passed to --argjson\n", progname); + } else if (isoption(&text, 0, "tab", is_short)) { + dumpopts &= ~JV_PRINT_INDENT_FLAGS(7); + dumpopts |= JV_PRINT_TAB | JV_PRINT_PRETTY; + } else if (isoption(&text, 0, "indent", is_short)) { + if (i >= argc - 1) { + fprintf(stderr, "%s: --indent takes one parameter\n", progname); die(); } - program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), v); - } - i += 2; // skip the next two arguments - continue; - } - if (isoption(argv[i], 0, "rawfile", &short_opts) || - isoption(argv[i], 0, "slurpfile", &short_opts)) { - int raw = isoption(argv[i], 0, "rawfile", &short_opts); - const char *which; - if (raw) - which = "rawfile"; - else - which = "slurpfile"; - if (i >= argc - 2) { - fprintf(stderr, "%s: --%s takes two parameters (e.g. --%s varname filename)\n", progname, which, which); - die(); - } - if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) { - jv data = jv_load_file(argv[i+2], raw); - if (!jv_is_valid(data)) { - data = jv_invalid_get_msg(data); - fprintf(stderr, "%s: Bad JSON in --%s %s %s: %s\n", progname, which, - argv[i+1], argv[i+2], jv_string_value(data)); - jv_free(data); - ret = JQ_ERROR_SYSTEM; - goto out; + dumpopts &= ~(JV_PRINT_TAB | JV_PRINT_INDENT_FLAGS(7)); + int indent = atoi(argv[i+1]); + if (indent < -1 || indent > 7) { + fprintf(stderr, "%s: --indent takes a number between -1 and 7\n", progname); + die(); + } + dumpopts |= JV_PRINT_INDENT_FLAGS(indent); + i++; + } else if (isoption(&text, 0, "seq", is_short)) { + options |= SEQ; + } else if (isoption(&text, 0, "stream", is_short)) { + parser_flags |= JV_PARSE_STREAMING; + } else if (isoption(&text, 0, "stream-errors", is_short)) { + parser_flags |= JV_PARSE_STREAMING | JV_PARSE_STREAM_ERRORS; + } else if (isoption(&text, 'e', "exit-status", is_short)) { + options |= EXIT_STATUS; + } else if (isoption(&text, 0, "args", is_short)) { + further_args_are_strings = 1; + further_args_are_json = 0; + } else if (isoption(&text, 0, "jsonargs", is_short)) { + further_args_are_strings = 0; + further_args_are_json = 1; + } else if (isoption(&text, 0, "arg", is_short)) { + // FIXME: For --arg and --argjson we should check that the varname is acceptable + if (i >= argc - 2) { + fprintf(stderr, "%s: --arg takes two parameters (e.g. --arg varname value)\n", progname); + die(); + } + if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) + program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), jv_string(argv[i+2])); + i += 2; // skip the next two arguments + } else if (isoption(&text, 0, "argjson", is_short)) { + if (i >= argc - 2) { + fprintf(stderr, "%s: --argjson takes two parameters (e.g. --argjson varname text)\n", progname); + die(); + } + if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) { + jv v = jv_parse(argv[i+2]); + if (!jv_is_valid(v)) { + fprintf(stderr, "%s: invalid JSON text passed to --argjson\n", progname); + die(); + } + program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), v); } - program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), data); + i += 2; // skip the next two arguments + } else if ((raw = isoption(&text, 0, "rawfile", is_short)) || + isoption(&text, 0, "slurpfile", is_short)) { + const char *which = raw ? "rawfile" : "slurpfile"; + if (i >= argc - 2) { + fprintf(stderr, "%s: --%s takes two parameters (e.g. --%s varname filename)\n", progname, which, which); + die(); + } + if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) { + jv data = jv_load_file(argv[i+2], raw); + if (!jv_is_valid(data)) { + data = jv_invalid_get_msg(data); + fprintf(stderr, "%s: Bad JSON in --%s %s %s: %s\n", progname, which, + argv[i+1], argv[i+2], jv_string_value(data)); + jv_free(data); + ret = JQ_ERROR_SYSTEM; + goto out; + } + program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), data); + } + i += 2; // skip the next two arguments + } else if (isoption(&text, 0, "debug-dump-disasm", is_short)) { + options |= DUMP_DISASM; + } else if (isoption(&text, 0, "debug-trace=all", is_short)) { + jq_flags |= JQ_DEBUG_TRACE_ALL; + } else if (isoption(&text, 0, "debug-trace", is_short)) { + jq_flags |= JQ_DEBUG_TRACE; + } else if (isoption(&text, 'h', "help", is_short)) { + usage(0, 0); + } else if (isoption(&text, 'V', "version", is_short)) { + printf("jq-%s\n", JQ_VERSION); + ret = JQ_OK; + goto out; + } else if (isoption(&text, 0, "build-configuration", is_short)) { + printf("%s\n", JQ_CONFIG); + ret = JQ_OK; + goto out; + } else if (isoption(&text, 0, "run-tests", is_short)) { + i++; + // XXX Pass program_arguments, even a whole jq_state *, through; + // could be useful for testing + ret = jq_testsuite(lib_search_paths, + (options & DUMP_DISASM) || (jq_flags & JQ_DEBUG_TRACE), + argc - i, argv + i); + goto out; + } else { + if (is_short) { + fprintf(stderr, "%s: Unknown option -%c\n", progname, text[0]); + } else { + fprintf(stderr, "%s: Unknown option --%s\n", progname, text); + } + die(); } - i += 2; // skip the next two arguments - continue; - } - if (isoption(argv[i], 0, "debug-dump-disasm", &short_opts)) { - options |= DUMP_DISASM; - continue; - } - if (isoption(argv[i], 0, "debug-trace=all", &short_opts)) { - jq_flags |= JQ_DEBUG_TRACE_ALL; - if (!short_opts) continue; - } - if (isoption(argv[i], 0, "debug-trace", &short_opts)) { - jq_flags |= JQ_DEBUG_TRACE; - continue; - } - if (isoption(argv[i], 'h', "help", &short_opts)) { - usage(0, 0); - if (!short_opts) continue; - } - if (isoption(argv[i], 'V', "version", &short_opts)) { - printf("jq-%s\n", JQ_VERSION); - ret = JQ_OK; - goto out; - } - if (isoption(argv[i], 0, "build-configuration", &short_opts)) { - printf("%s\n", JQ_CONFIG); - ret = JQ_OK; - goto out; - } - if (isoption(argv[i], 0, "run-tests", &short_opts)) { - i++; - // XXX Pass program_arguments, even a whole jq_state *, through; - // could be useful for testing - ret = jq_testsuite(lib_search_paths, - (options & DUMP_DISASM) || (jq_flags & JQ_DEBUG_TRACE), - argc - i, argv + i); - goto out; - } - - // check for unknown options... if this argument was a short option - if (strlen(argv[i]) != short_opts + 1) { - fprintf(stderr, "%s: Unknown option %s\n", progname, argv[i]); - die(); } } } diff --git a/tests/shtest b/tests/shtest index 4aa27823ce..c14cf1c516 100755 --- a/tests/shtest +++ b/tests/shtest @@ -210,6 +210,28 @@ else cmp $d/out $d/expected fi +# Regression tests for #3194 +echo 42 > $d/expected +$JQ -nn 42 > $d/out 2>&1 +cmp $d/out $d/expected + +$JQ -nL. 42 > $d/out 2>&1 +cmp $d/out $d/expected +$JQ -nL . 42 > $d/out 2>&1 +cmp $d/out $d/expected + +$JQ -h > $d/expected 2>&1 +$JQ -hV > $d/out 2>&1 +cmp $d/out $d/expected +$JQ --help --version > $d/out 2>&1 +cmp $d/out $d/expected + +$JQ -V > $d/expected 2>&1 +$JQ -Vh > $d/out 2>&1 +cmp $d/out $d/expected +$JQ --version --help > $d/out 2>&1 +cmp $d/out $d/expected + ## Test streaming parser ## If we add an option to stream to the `import ... as $symbol;` directive