diff --git a/CHANGELOG.md b/CHANGELOG.md index 667fc019..e21ceed3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] +### Added +- Add `planck.io/tty?` ([#911](https://github.com/planck-repl/planck/issues/911)) ## [2.22.0] - 2019-04-06 ### Added diff --git a/int-test/expected/PLANCK-OUT.txt b/int-test/expected/PLANCK-OUT.txt index 0937b906..3b75a84b 100644 --- a/int-test/expected/PLANCK-OUT.txt +++ b/int-test/expected/PLANCK-OUT.txt @@ -611,3 +611,13 @@ Execution error (ReferenceError) at (:1). Can't find variable: a Auto-load user.cljs 3 +stdio TTY is detected (in a faketty) +[[*in* true] [*out* true] [*err* true]] +stdin non-TTY is detected (in a pipeline) +[*in* false] +stdout non-TTY is detected (in a pipeline) +[*out* false] +stderr non-TTY is detected (redirected to /dev/null) +[*err* false] +*err* TTY is detected when rebound to *out* even when stderr is redirected to /dev/null +[[*out* true] [*err* true]] diff --git a/int-test/script/gen-actual b/int-test/script/gen-actual index fa925994..ed7e9b84 100755 --- a/int-test/script/gen-actual +++ b/int-test/script/gen-actual @@ -795,3 +795,35 @@ echo "(def x 3)" > /tmp/PLANCK_SRC/user.cljs $PLANCK -c /tmp/PLANCK_SRC </dev/null +echo + +echo "*err* TTY is detected when rebound to *out* even when stderr is redirected to /dev/null" +cat </tmp/PLANCK_TTY_REBINDING_TEST +#!/bin/sh +$PLANCK -i $SRC/test_tty/rebinding_err_out.cljs 2>/dev/null +EOF +chmod +x /tmp/PLANCK_TTY_REBINDING_TEST +faketty /tmp/PLANCK_TTY_REBINDING_TEST +echo diff --git a/int-test/src/test_tty/rebinding_err_out.cljs b/int-test/src/test_tty/rebinding_err_out.cljs new file mode 100644 index 00000000..77811327 --- /dev/null +++ b/int-test/src/test_tty/rebinding_err_out.cljs @@ -0,0 +1,5 @@ +(require 'planck.core) +(require 'planck.io) +(binding [planck.core/*err* cljs.core/*out*] + (pr [['*out* (planck.io/tty? cljs.core/*out*)] + ['*err* (planck.io/tty? planck.core/*err*)]])) diff --git a/int-test/src/test_tty/stderr.cljs b/int-test/src/test_tty/stderr.cljs new file mode 100644 index 00000000..2d38be17 --- /dev/null +++ b/int-test/src/test_tty/stderr.cljs @@ -0,0 +1,3 @@ +(require 'planck.core) +(require 'planck.io) +(pr ['*err* (planck.io/tty? planck.core/*err*)]) diff --git a/int-test/src/test_tty/stdin.cljs b/int-test/src/test_tty/stdin.cljs new file mode 100644 index 00000000..0e03db6d --- /dev/null +++ b/int-test/src/test_tty/stdin.cljs @@ -0,0 +1,3 @@ +(require 'planck.core) +(require 'planck.io) +(pr ['*in* (planck.io/tty? planck.core/*in*)]) diff --git a/int-test/src/test_tty/stdio.cljs b/int-test/src/test_tty/stdio.cljs new file mode 100644 index 00000000..e0dec5d1 --- /dev/null +++ b/int-test/src/test_tty/stdio.cljs @@ -0,0 +1,5 @@ +(require 'planck.core) +(require 'planck.io) +(pr [['*in* (planck.io/tty? planck.core/*in*)] + ['*out* (planck.io/tty? cljs.core/*out*)] + ['*err* (planck.io/tty? planck.core/*err*)]]) diff --git a/int-test/src/test_tty/stdout.cljs b/int-test/src/test_tty/stdout.cljs new file mode 100644 index 00000000..bcf74039 --- /dev/null +++ b/int-test/src/test_tty/stdout.cljs @@ -0,0 +1,3 @@ +(require 'planck.core) +(require 'planck.io) +(pr ['*out* (planck.io/tty? cljs.core/*out*)]) diff --git a/planck-c/engine.c b/planck-c/engine.c index ca9d1747..dcd55c44 100644 --- a/planck-c/engine.c +++ b/planck-c/engine.c @@ -523,6 +523,8 @@ void *do_engine_init(void *data) { register_global_function(ctx, "PLANCK_GETENV", function_getenv); + register_global_function(ctx, "PLANCK_ISATTY", function_isatty); + display_launch_timing("register fns"); // Monkey patch cljs.core/system-time to use Planck's high-res timer diff --git a/planck-c/functions.c b/planck-c/functions.c index 0ea08c5a..21b17e04 100644 --- a/planck-c/functions.c +++ b/planck-c/functions.c @@ -1522,3 +1522,18 @@ JSValueRef function_getenv(JSContextRef ctx, JSObjectRef function, JSObjectRef t } return JSValueMakeNull(ctx); } + +JSValueRef function_isatty(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, + size_t argc, const JSValueRef args[], JSValueRef *exception) { + if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeNumber) { + int fd = (int) JSValueToNumber(ctx, args[0], NULL); + if (fd >= 0) { + errno = 0; + bool result = isatty(fd); + if (errno != EBADF) { + return JSValueMakeBoolean(ctx, result); + } + } + } + return JSValueMakeNull(ctx); +} diff --git a/planck-c/functions.h b/planck-c/functions.h index ffef4d6c..17488797 100644 --- a/planck-c/functions.h +++ b/planck-c/functions.h @@ -156,3 +156,6 @@ JSValueRef function_signal_task_complete(JSContextRef ctx, JSObjectRef function, JSValueRef function_getenv(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argc, const JSValueRef args[], JSValueRef *exception); + +JSValueRef function_isatty(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, + size_t argc, const JSValueRef args[], JSValueRef *exception); diff --git a/planck-cljs/src/planck/io.cljs b/planck-cljs/src/planck/io.cljs index 688678c1..64509286 100644 --- a/planck-cljs/src/planck/io.cljs +++ b/planck-cljs/src/planck/io.cljs @@ -552,6 +552,29 @@ :args (s/cat :input any? :output any? :opts (s/* any?)) :ret nil?) +(def ^:private stdio->fd + {planck.core/*in* 0 + cljs.core/*out* 1 + planck.core/*err* 2}) + +(defn ^boolean tty? + "Returns true if x is a file descriptor associated with a terminal, + or x is either a Reader/Writer among *in*, *out*, or *err* which is + associated with a terminal. + + Returns false if x is a file descriptor, *in*, *out*, or *err* and + not associated with a terminal, or an invalid file descriptor." + [x] + (-> (if-let [fd (stdio->fd x)] fd x) + js/PLANCK_ISATTY + boolean)) + +(s/fdef tty? + :args (s/cat :x (s/or :fd-num (s/and integer? (complement neg?)) + :reader #(implements? planck.core/IReader %) + :writer #(implements? planck.core/IWriter %))) + :ret boolean?) + ;; These have been moved (def ^:deprecated read-line planck.core/read-line) (def ^:deprecated slurp planck.core/slurp) diff --git a/planck-cljs/test/planck/io_test.cljs b/planck-cljs/test/planck/io_test.cljs index e2ae9daa..92b86355 100644 --- a/planck-cljs/test/planck/io_test.cljs +++ b/planck-cljs/test/planck/io_test.cljs @@ -71,7 +71,17 @@ (is (= true (planck.io/exists? hidden-directory))) (is (= true (planck.io/hidden-file? hidden-directory))) (is (= false (planck.io/regular-file? hidden-directory))) - (is (= false (planck.io/symbolic-link? hidden-directory)))))) + (is (= false (planck.io/symbolic-link? hidden-directory))) + (is (boolean? (planck.io/tty? 0))) + (is (boolean? (planck.io/tty? 1))) + (is (boolean? (planck.io/tty? 2))) + (is (boolean? (planck.io/tty? planck.core/*in*))) + (is (boolean? (planck.io/tty? *out*))) + (is (boolean? (planck.io/tty? planck.core/*err*))) + (is (= false (planck.io/tty? nil))) + (is (= false (planck.io/tty? -1))) + (is (= false (planck.io/tty? 99999999999999999))) + (is (= false (planck.io/tty? (planck.io/reader regular-file))))))) (deftest coercions (testing "as-file coerceions" diff --git a/site/src/planck-io.md b/site/src/planck-io.md index 73d983d7..1851680a 100644 --- a/site/src/planck-io.md +++ b/site/src/planck-io.md @@ -38,6 +38,7 @@ _Vars_ [regular-file?](#regular-file?)
[resource](#resource)
[symbolic-link?](#symbolic-link?)
+[tty?](#tty?)
[writer](#writer)
## Protocols @@ -299,6 +300,15 @@ Spec
_args_: `(cat :f (or :string string? :file file?))`
_ret_: `boolean?`
+### tty? +`([x])` + +Checks if `x` is a file descriptor associated with a terminal. `x` may be a file descriptor number or one of `planck.core/*in*`, `cljs.core/*out*` or `planck.core/*err*`. + +Spec
+_x_: `(or :fd-num (and integer? (complement neg?)) :reader #(implements? planck.core/IReader %) :writer #(implements? planck.core/IWriter %))`
+_ret_: `boolean?`
+ ### writer `([x & opts])`