diff options
author | Nicolas Williams <nico@cryptonector.com> | 2017-02-22 23:01:56 -0600 |
---|---|---|
committer | Nicolas Williams <nico@cryptonector.com> | 2017-02-26 16:34:56 -0600 |
commit | 8ea21a54adfa9e518f4855d2dc06ae3c0456d69c (patch) | |
tree | 1a4636dd292d73d5ac31827ffe9d63d7c0a5e0f2 | |
parent | 6bac4ed059966007a6bc0e4a3639aca7d59f3b10 (diff) |
Add `halt`, `halt_error` builtins (fix #386)
-rw-r--r-- | docs/content/3.manual/manual.yml | 23 | ||||
-rw-r--r-- | jq.1.prebuilt | 17 | ||||
-rw-r--r-- | src/builtin.c | 15 | ||||
-rw-r--r-- | src/builtin.jq | 1 | ||||
-rw-r--r-- | src/execute.c | 41 | ||||
-rw-r--r-- | src/jq.h | 5 | ||||
-rw-r--r-- | src/main.c | 33 | ||||
-rwxr-xr-x | tests/setup | 2 | ||||
-rwxr-xr-x | tests/shtest | 33 |
9 files changed, 163 insertions, 7 deletions
diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml index cadc6fbd..fa23e355 100644 --- a/docs/content/3.manual/manual.yml +++ b/docs/content/3.manual/manual.yml @@ -209,6 +209,9 @@ sections: problem or system error, 3 if there was a jq program compile error, or 0 if the jq program ran. + Another way to set the exit status is with the `halt_error` + builtin function. + * `--arg name value`: This option passes a value to the jq program as a predefined @@ -982,7 +985,25 @@ sections: Produces an error, just like `.a` applied to values other than null and objects would, but with the given message as the - error's value. + error's value. Errors can be caught with try/catch; see below. + + - title: "`halt`" + body: | + + Stops the jq program with no further outputs. jq will exit + with exit status `0`. + + - title: "`halt_error`, `halt_error(exit_code)`" + body: | + + Stops the jq program with no further outputs. The input will + be printed on `stderr` as raw output (i.e., strings will not + have double quotes) with no decoration, not even a newline. + + The given `exit_code` (defaulting to `5`) will be jq's exit + status. + + For example, `"Error: somthing went wrong\n"|halt_error(1)`. - title: "`$__loc__`" body: | diff --git a/jq.1.prebuilt b/jq.1.prebuilt index efd1307a..f09ee6ad 100644 --- a/jq.1.prebuilt +++ b/jq.1.prebuilt @@ -153,6 +153,9 @@ Prepend \fBdirectory\fR to the search list for modules\. If this option is used .IP Sets the exit status of jq to 0 if the last output values was neither \fBfalse\fR nor \fBnull\fR, 1 if the last output value was either \fBfalse\fR or \fBnull\fR, or 4 if no valid result was ever produced\. Normally jq exits with 2 if there was any usage problem or system error, 3 if there was a jq program compile error, or 0 if the jq program ran\. . +.IP +Another way to set the exit status is with the \fBhalt_error\fR builtin function\. +. .IP "\(bu" 4 \fB\-\-arg name value\fR: . @@ -1017,7 +1020,19 @@ jq \'[1,2,empty,3]\' .IP "" 0 . .SS "error(message)" -Produces an error, just like \fB\.a\fR applied to values other than null and objects would, but with the given message as the error\'s value\. +Produces an error, just like \fB\.a\fR applied to values other than null and objects would, but with the given message as the error\'s value\. Errors can be caught with try/catch; see below\. +. +.SS "halt" +Stops the jq program with no further outputs\. jq will exit with exit status \fB0\fR\. +. +.SS "halt_error, halt_error(exit_code)" +Stops the jq program with no further outputs\. The input will be printed on \fBstderr\fR as raw output (i\.e\., strings will not have double quotes) with no decoration, not even a newline\. +. +.P +The given \fBexit_code\fR (defaulting to \fB5\fR) will be jq\'s exit status\. +. +.P +For example, \fB"Error: somthing went wrong\en"|halt_error(1)\fR\. . .SS "$__loc__" Produces an object with a "file" key and a "line" key, with the filename and line number where \fB$__loc__\fR occurs, as values\. diff --git a/src/builtin.c b/src/builtin.c index 30714aa8..1b879ab5 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1031,6 +1031,19 @@ static jv f_env(jq_state *jq, jv input) { return env; } +static jv f_halt(jq_state *jq, jv input) { + jv_free(input); + jq_halt(jq, jv_invalid(), jv_invalid()); + return jv_true(); +} + +static jv f_halt_error(jq_state *jq, jv input, jv a) { + if (jv_get_kind(a) != JV_KIND_NUMBER) + return type_error(input, "halt_error/1: number required"); \ + jq_halt(jq, a, input); + return jv_true(); +} + static jv f_get_search_list(jq_state *jq, jv input) { jv_free(input); return jq_get_lib_dirs(jq); @@ -1467,6 +1480,8 @@ static const struct cfunction function_list[] = { {(cfunction_ptr)f_error, "error", 2}, {(cfunction_ptr)f_format, "format", 2}, {(cfunction_ptr)f_env, "env", 1}, + {(cfunction_ptr)f_halt, "halt", 1}, + {(cfunction_ptr)f_halt_error, "halt_error", 2}, {(cfunction_ptr)f_get_search_list, "get_search_list", 1}, {(cfunction_ptr)f_get_prog_origin, "get_prog_origin", 1}, {(cfunction_ptr)f_get_jq_origin, "get_jq_origin", 1}, diff --git a/src/builtin.jq b/src/builtin.jq index f01280a7..1432d993 100644 --- a/src/builtin.jq +++ b/src/builtin.jq @@ -1,3 +1,4 @@ +def halt_error: halt_error(5); def error: error(.); def map(f): [.[] | f]; def select(f): if f then . else empty end; diff --git a/src/execute.c b/src/execute.c index 44c76a4c..d4702103 100644 --- a/src/execute.c +++ b/src/execute.c @@ -40,6 +40,10 @@ struct jq_state { int initial_execution; unsigned next_label; + int halted; + jv exit_code; + jv error_message; + jv attrs; jq_input_cb input_cb; void *input_cb_data; @@ -285,6 +289,9 @@ static void jq_reset(jq_state *jq) { jv_free(jq->error); jq->error = jv_null(); + jq->halted = 0; + jv_free(jq->exit_code); + jv_free(jq->error_message); if (jv_get_kind(jq->path) != JV_KIND_INVALID) jv_free(jq->path); jq->path = jv_null(); @@ -320,6 +327,11 @@ jv jq_next(jq_state *jq) { jq->initial_execution = 0; assert(jv_get_kind(jq->error) == JV_KIND_NULL); while (1) { + if (jq->halted) { + if (jq->debug_trace_enabled) + printf("\t<halted>\n"); + return jv_invalid(); + } uint16_t opcode = *pc; raising = 0; @@ -932,6 +944,10 @@ jq_state *jq_init(void) { jq->curr_frame = 0; jq->error = jv_null(); + jq->halted = 0; + jq->exit_code = jv_invalid(); + jq->error_message = jv_invalid(); + jq->err_cb = default_err_cb; jq->err_cb_data = stderr; @@ -1163,3 +1179,28 @@ void jq_get_debug_cb(jq_state *jq, jq_msg_cb *cb, void **data) { *cb = jq->debug_cb; *data = jq->debug_cb_data; } + +void +jq_halt(jq_state *jq, jv exit_code, jv error_message) +{ + assert(!jq->halted); + jq->halted = 1; + jq->exit_code = exit_code; + jq->error_message = error_message; +} + +int +jq_halted(jq_state *jq) +{ + return jq->halted; +} + +jv jq_get_exit_code(jq_state *jq) +{ + return jv_copy(jq->exit_code); +} + +jv jq_get_error_message(jq_state *jq) +{ + return jv_copy(jq->error_message); +} @@ -22,6 +22,11 @@ void jq_start(jq_state *, jv value, int); jv jq_next(jq_state *); void jq_teardown(jq_state **); +void jq_halt(jq_state *, jv, jv); +int jq_halted(jq_state *); +jv jq_get_exit_code(jq_state *); +jv jq_get_error_message(jq_state *); + typedef jv (*jq_input_cb)(jq_state *, void *); void jq_set_input_cb(jq_state *, jq_input_cb, void *); void jq_get_input_cb(jq_state *, jq_input_cb *, void **); @@ -137,10 +137,11 @@ enum { RAW_NO_LF = 1024, UNBUFFERED_OUTPUT = 2048, EXIT_STATUS = 4096, - SEQ = 8192, - RUN_TESTS = 16384, + EXIT_STATUS_EXACT = 8192, + SEQ = 16384, + RUN_TESTS = 32768, /* debugging only */ - DUMP_DISASM = 32768, + DUMP_DISASM = 65536, }; static int options = 0; @@ -182,7 +183,29 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts) { if (options & UNBUFFERED_OUTPUT) fflush(stdout); } - if (jv_invalid_has_msg(jv_copy(result))) { + if (jq_halted(jq)) { + // jq program invoked `halt` or `halt_error` + options |= EXIT_STATUS_EXACT; + jv exit_code = jq_get_exit_code(jq); + if (!jv_is_valid(exit_code)) + ret = 0; + else if (jv_get_kind(exit_code) == JV_KIND_NUMBER) + ret = jv_number_value(exit_code); + else + ret = 5; + jv_free(exit_code); + jv error_message = jq_get_error_message(jq); + if (jv_get_kind(error_message) == JV_KIND_STRING) { + fprintf(stderr, "%s", jv_string_value(error_message)); + } else if (jv_get_kind(error_message) == JV_KIND_NULL) { + // Halt with no output + } else if (jv_is_valid(error_message)) { + error_message = jv_dump_string(jv_copy(error_message), 0); + fprintf(stderr, "%s\n", jv_string_value(error_message)); + } // else no message on stderr; use --debug-trace to see a message + fflush(stderr); + jv_free(error_message); + } else if (jv_invalid_has_msg(jv_copy(result))) { // Uncaught jq exception jv msg = jv_invalid_get_msg(jv_copy(result)); jv input_pos = jq_util_input_get_position(jq); @@ -623,7 +646,7 @@ out: jq_teardown(&jq); if (ret >= 10 && (options & EXIT_STATUS)) return ret - 10; - if (ret >= 10) + if (ret >= 10 && !(options & EXIT_STATUS_EXACT)) return 0; return ret; } diff --git a/tests/setup b/tests/setup index 94bcf1eb..9ce382ba 100755 --- a/tests/setup +++ b/tests/setup @@ -15,9 +15,11 @@ JQ=$JQBASEDIR/jq if [ -z "${NO_VALGRIND-}" ] && which valgrind > /dev/null; then VALGRIND="valgrind --error-exitcode=1 --leak-check=full \ --suppressions=$JQTESTDIR/onig.supp" + VG_EXIT0=--error-exitcode=0 Q=-q else VALGRIND= + VG_EXIT0= Q= fi diff --git a/tests/shtest b/tests/shtest index 9a17aea4..cec1fc59 100755 --- a/tests/shtest +++ b/tests/shtest @@ -211,4 +211,37 @@ if ! $VALGRIND $Q $JQ -L tests/modules -ne 'import "test_bind_order" as check; c exit 1 fi +## Halt + +if ! $VALGRIND $Q $JQ -n halt; then + echo "jq halt didn't work as expected" 1>&2 + exit 1 +fi +if $VALGRIND $Q $VG_EXIT0 $JQ -n 'halt_error(1)'; then + echo "jq halt_error(1) didn't work as expected" 1>&2 + exit 1 +elif [ $? -ne 1 ]; then + echo "jq halt_error(1) had wrong error code" 1>&2 + exit 1 +fi +if $VALGRIND $Q $VG_EXIT0 $JQ -n 'halt_error(11)'; then + echo "jq halt_error(11) didn't work as expected" 1>&2 + exit 1 +elif [ $? -ne 11 ]; then + echo "jq halt_error(11) had wrong error code" 1>&2 + exit 1 +fi +if [ -n "`$VALGRIND $Q $JQ -n 'halt_error(1)' 2>&1`" ]; then + echo "jq halt_error(1) had unexpected output" 1>&2 + exit 1 +fi +if [ -n "`$VALGRIND $Q $JQ -n '"xyz\n"|halt_error(1)' 2>/dev/null`" ]; then + echo "jq halt_error(1) had unexpected output on stdout" 1>&2 + exit 1 +fi +if [ "`$VALGRIND $Q $JQ -n '"xyz\n"|halt_error(1)' 2>&1`" != xyz ]; then + echo "jq halt_error(1) had unexpected output" 1>&2 + exit 1 +fi + exit 0 |