Parse short options in order given
Fixes: * Short options cannot be repeated in the same group: e.g., `-nn` fails, but `--null-input --null-input` works * Short options are processed in an arbitrary fixed order, not the order given by the user: e.g., `-hV`, `-Vh`, `-h -V`, and `--help --version` are processed as `-h`, but `-V -h` and `--version --help` are processed as `-V` * `-L` cannot be a part of a short option group (as the last one): e.g., `-nLlib` and `-nL lib` fail
This commit is contained in:
1 parent
562d5c5222
commit
ff4acba769
2 files changed
+193
-225
No files matched your search
+171
-225
@@ -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; i++, short_opts = 0) {
|
||||
for (int i=1; i<argc; i++) {
|
||||
if (args_done || !isoptish(argv[i])) {
|
||||
if (!program) {
|
||||
program = argv[i];
|
||||
@@ -366,223 +363,172 @@ int main(int argc, char* argv[]) {
|
||||
} else if (!strcmp(argv[i], "--")) {
|
||||
args_done = 1;
|
||||
} else {
|
||||
if (argv[i][1] == 'L') {
|
||||
if (jv_get_kind(lib_search_paths) == JV_KIND_NULL)
|
||||
lib_search_paths = jv_array();
|
||||
if (argv[i][2] != 0) { // -Lname (faster check than strlen)
|
||||
lib_search_paths = jv_array_append(lib_search_paths, jq_realpath(jv_string(argv[i]+2)));
|
||||
} 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++;
|
||||
}
|
||||
continue;
|
||||
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
|
||||
|
||||
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)) {
|
||||
// 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);
|
||||
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* 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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
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
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 -h -V > $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 -V -h > $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
|
||||
|
||||
Reference in new issue
Block a user