1
0
mirror of https://github.com/stedolan/jq.git synced 2024-05-11 05:55:39 +00:00

Add halt, halt_error builtins (fix #386)

This commit is contained in:
Nicolas Williams
2017-02-22 23:01:56 -06:00
parent 6bac4ed059
commit 8ea21a54ad
9 changed files with 163 additions and 7 deletions

View File

@@ -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: |

View File

@@ -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\.

View File

@@ -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},

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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 **);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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