aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorCalvin Rose <calsrose@gmail.com>2026-02-19 20:54:19 -0600
committerCalvin Rose <calsrose@gmail.com>2026-02-19 21:39:32 -0600
commita17483cd5ab58347d067fdc011d57f451a3b8038 (patch)
tree265527e332aaf4f0bc7ea22b27f6b6d87c06ea9f
parentUpdate version tag (diff)
Add *pipefile* and *errexit* to sh-dsl.pipefail-errexit
These dynamic bindings are analogs to set -o pipefile and set -o errexit
-rw-r--r--spork/declare-cc.janet1
-rw-r--r--spork/sh-dsl.janet29
-rw-r--r--test/suite-sh-dsl.janet25
3 files changed, 52 insertions, 3 deletions
diff --git a/spork/declare-cc.janet b/spork/declare-cc.janet
index 288e0c7..7791b76 100644
--- a/spork/declare-cc.janet
+++ b/spork/declare-cc.janet
@@ -201,6 +201,7 @@
be run in the environment dictated by (dyn :modpath)."
[&opt root-directory]
(var errors-found 0)
+ # TODO - flycheck all tests before running them - fail quickly
(defn dodir
[dir]
(each sub (sort (os/dir dir))
diff --git a/spork/sh-dsl.janet b/spork/sh-dsl.janet
index f1cba4c..3f9b877 100644
--- a/spork/sh-dsl.janet
+++ b/spork/sh-dsl.janet
@@ -5,6 +5,9 @@
### sh-dsl.janet
###
+(defdyn *pipefail* "When set, the return value of a pipeline is the last non-zero exit code instead of the default right-most exit code")
+(defdyn *errexit* "When set, error immediately if pipelines run with `$`, `$<`, or `$<_` return with a non-zero exit code.")
+
(def- parse-env-set-peg (peg/compile '(* '(to "=") "=" ':S*)))
# Re-implements ev/go-gather
@@ -40,6 +43,25 @@
# End ev/go-gather
+(defn- last-nonzero
+ [xs]
+ (var ret 0)
+ (each x xs (when (not= 0 x) (set ret x)))
+ ret)
+
+(defn- pipeline-results
+ [xs]
+ (def status-codes (filter number? xs))
+ (assert (next status-codes)) # we must have at least 1 status code
+ (let [rc (if (dyn *pipefail*)
+ (last-nonzero status-codes)
+ (last status-codes))]
+ (if (not= 0 rc)
+ (if (dyn *errexit*)
+ (error (string/format "non-zero exit code %v" rc))
+ rc)
+ (last xs))))
+
(defn- string-token [x]
(if (bytes? x)
(string x)
@@ -73,6 +95,7 @@
(def procs @[])
(def fds @[])
(var pipein nil)
+ (var out nil)
(defn getfd [f] (array/push fds f) f)
(defer
@@ -140,8 +163,10 @@
(ev/read final-pipe :all capture-buf)
capture-buf)))
- (def out (wait-thunks thunks))
- (if return-all out (last out))))
+ (set out (wait-thunks thunks)))
+
+ # Outside defer for better stack trace on error
+ (if return-all out (pipeline-results out)))
###
### DSL Parsing
diff --git a/test/suite-sh-dsl.janet b/test/suite-sh-dsl.janet
index fd5adbf..ec92ad1 100644
--- a/test/suite-sh-dsl.janet
+++ b/test/suite-sh-dsl.janet
@@ -31,6 +31,29 @@
# More pipes using just janet
(def version (buffer janet/version "-" janet/build))
- (assert (deep= version ($<_ ,janet --version | ,janet -e '(prin (:read stdin :all)))) "janet pipe example"))
+ (assert (deep= version ($<_ ,janet --version | ,janet -e '(prin (:read stdin :all)))) "janet pipe example")
+
+ # Pipefail
+ (setdyn *pipefail* true)
+ (assert ($< echo) "echo pipefail 1")
+ (assert (deep= @"hi\n" ($< echo hi)) "echo pipefail 2")
+ (assert (deep= @"hi" ($<_ echo hi)) "echo pipefail 3")
+ (assert (= 1 ($< ,janet -e "(os/exit 0)" | ,janet -e "(os/exit 1)" | ,janet -e "(os/exit 0)")) "pipefail 1")
+
+ # Errexit
+ (setdyn *pipefail* false)
+ (setdyn *errexit* true)
+ (assert ($< echo) "echo errexit 1")
+ (assert (deep= @"hi\n" ($< echo hi)) "echo errexit 2")
+ (assert (deep= @"hi" ($<_ echo hi)) "echo errexit 3")
+ (assert-error "errexit grep" ($ echo hi | grep nope))
+
+ # Pipefail and Errexit
+ (setdyn *pipefail* true)
+ (assert ($< echo) "echo pipefail + errexit 1")
+ (assert (deep= @"hi\n" ($< echo hi)) "echo pipefail + errexit 2")
+ (assert (deep= @"hi" ($<_ echo hi)) "echo pipefail + errexit 3")
+ (assert-error "pipefail + errexit grep" ($ echo hi | grep nope))
+ (assert-error "pipefail + errexit grep" ($ echo hi | grep nope | ,janet -e "(os/exit 0)")))
(end-suite)