]> granicus.if.org Git - jq/commitdiff
Add `halt`, `halt_error` builtins (fix #386) 1348/head
authorNicolas Williams <nico@cryptonector.com>
Thu, 23 Feb 2017 05:01:56 +0000 (23:01 -0600)
committerNicolas Williams <nico@cryptonector.com>
Sun, 26 Feb 2017 22:34:56 +0000 (16:34 -0600)
docs/content/3.manual/manual.yml
jq.1.prebuilt
src/builtin.c
src/builtin.jq
src/execute.c
src/jq.h
src/main.c
tests/setup
tests/shtest

index cadc6fbd596d23c1ac7f8db90cf4000c78fc0845..fa23e3559b8465a24982fe9f12936ca423ef717c 100644 (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: |
index efd1307a031fe27db776e51892e6a0395f0c463e..f09ee6ad909b61cb0fe8352f9fdf0b047dd14666 100644 (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\.
index 30714aa80baef51d0ddba51e37e70bd25d2dc61d..1b879ab57711a9830c319b35be627c18f0375b82 100644 (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},
index f01280a734a7cab02069450875d3fe63626aee57..1432d9933eb6916e4b197122cca5425bfce49bf2 100644 (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;
index 44c76a4c70c7e50bdecbf5b20fee8ce8b03f3a02..d47021033369556fa3d6254fb98f63108def779f 100644 (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);
+}
index 7ade128c21f7d905a9a724b3214978ac420030a2..b80d442f491f37ceae7a3d193d8ecd6aaee5fbfc 100644 (file)
--- 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 **);
index ff66129c53b139c2cd7a48becd50e3f88b95518b..6d3964b1e73e3984db4c1b193d6ec9afb62f00a7 100644 (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;
 }
index 94bcf1ebfef3425b0d4852f6809f4e3491f572c4..9ce382ba5dfb14fc78fe34e29a7b7550256dfa3a 100755 (executable)
@@ -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
 
index 9a17aea4832f44491793f1e3070dc2e39038a8d5..cec1fc59314f453b0a7654ca0cb02af4332121fa 100755 (executable)
@@ -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