summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Williams <nico@cryptonector.com>2017-02-22 23:01:56 -0600
committerNicolas Williams <nico@cryptonector.com>2017-02-26 16:34:56 -0600
commit8ea21a54adfa9e518f4855d2dc06ae3c0456d69c (patch)
tree1a4636dd292d73d5ac31827ffe9d63d7c0a5e0f2
parent6bac4ed059966007a6bc0e4a3639aca7d59f3b10 (diff)
Add `halt`, `halt_error` builtins (fix #386)
-rw-r--r--docs/content/3.manual/manual.yml23
-rw-r--r--jq.1.prebuilt17
-rw-r--r--src/builtin.c15
-rw-r--r--src/builtin.jq1
-rw-r--r--src/execute.c41
-rw-r--r--src/jq.h5
-rw-r--r--src/main.c33
-rwxr-xr-xtests/setup2
-rwxr-xr-xtests/shtest33
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);
+}
diff --git a/src/jq.h b/src/jq.h
index 7ade128c..b80d442f 100644
--- a/src/jq.h
+++ b/src/jq.h
@@ -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 **);
diff --git a/src/main.c b/src/main.c
index ff66129c..6d3964b1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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