From 463bc58ea69c159827787479185d35fad3e99546 Mon Sep 17 00:00:00 2001 From: arbscht Date: Fri, 12 Apr 2019 20:30:52 +1200 Subject: [PATCH 1/7] Add tty? function --- planck-c/engine.c | 2 ++ planck-c/functions.c | 10 ++++++++++ planck-c/functions.h | 3 +++ planck-cljs/src/planck/core.cljs | 8 ++++++++ 4 files changed, 23 insertions(+) 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..199b3709 100644 --- a/planck-c/functions.c +++ b/planck-c/functions.c @@ -1522,3 +1522,13 @@ 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); + int result = isatty(fd); + return JSValueMakeBoolean(ctx, (result != 0)); + } + 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/core.cljs b/planck-cljs/src/planck/core.cljs index be87345b..2184c147 100644 --- a/planck-cljs/src/planck/core.cljs +++ b/planck-cljs/src/planck/core.cljs @@ -495,6 +495,14 @@ :args (s/cat :s string?) :ret any?) +(defn tty? + "Return true if the file descriptor numbered with fd-num is a tty" + [fd-num] + (js/PLANCK_ISATTY fd-num)) + +(s/fdef tty? + :args (s/cat :fd-num (s/and integer? pos?))) + ;; Ensure planck.io and planck.http are loaded so that their ;; facilities are available (#'repl/side-load-ns 'planck.http) From a9de737ba76764acff19b3887ecf192e3ef3f4e2 Mon Sep 17 00:00:00 2001 From: arbscht Date: Sun, 14 Apr 2019 00:00:02 +1200 Subject: [PATCH 2/7] Move tty? into planck.io and add some tests --- int-test/expected/PLANCK-OUT.txt | 8 ++++++++ int-test/script/gen-actual | 19 +++++++++++++++++++ int-test/src/test_tty/stderr.cljs | 3 +++ int-test/src/test_tty/stdin.cljs | 3 +++ int-test/src/test_tty/stdio.cljs | 5 +++++ int-test/src/test_tty/stdout.cljs | 3 +++ planck-c/functions.c | 9 +++++++-- planck-cljs/src/planck/core.cljs | 8 -------- planck-cljs/src/planck/io.cljs | 24 ++++++++++++++++++++++++ planck-cljs/test/planck/io_test.cljs | 12 +++++++++++- 10 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 int-test/src/test_tty/stderr.cljs create mode 100644 int-test/src/test_tty/stdin.cljs create mode 100644 int-test/src/test_tty/stdio.cljs create mode 100644 int-test/src/test_tty/stdout.cljs diff --git a/int-test/expected/PLANCK-OUT.txt b/int-test/expected/PLANCK-OUT.txt index 0937b906..89da571c 100644 --- a/int-test/expected/PLANCK-OUT.txt +++ b/int-test/expected/PLANCK-OUT.txt @@ -611,3 +611,11 @@ 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] diff --git a/int-test/script/gen-actual b/int-test/script/gen-actual index fa925994..87a6738a 100755 --- a/int-test/script/gen-actual +++ b/int-test/script/gen-actual @@ -795,3 +795,22 @@ echo "(def x 3)" > /tmp/PLANCK_SRC/user.cljs $PLANCK -c /tmp/PLANCK_SRC </dev/null +echo 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/functions.c b/planck-c/functions.c index 199b3709..21b17e04 100644 --- a/planck-c/functions.c +++ b/planck-c/functions.c @@ -1527,8 +1527,13 @@ JSValueRef function_isatty(JSContextRef ctx, JSObjectRef function, JSObjectRef t size_t argc, const JSValueRef args[], JSValueRef *exception) { if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeNumber) { int fd = (int) JSValueToNumber(ctx, args[0], NULL); - int result = isatty(fd); - return JSValueMakeBoolean(ctx, (result != 0)); + if (fd >= 0) { + errno = 0; + bool result = isatty(fd); + if (errno != EBADF) { + return JSValueMakeBoolean(ctx, result); + } + } } return JSValueMakeNull(ctx); } diff --git a/planck-cljs/src/planck/core.cljs b/planck-cljs/src/planck/core.cljs index 2184c147..be87345b 100644 --- a/planck-cljs/src/planck/core.cljs +++ b/planck-cljs/src/planck/core.cljs @@ -495,14 +495,6 @@ :args (s/cat :s string?) :ret any?) -(defn tty? - "Return true if the file descriptor numbered with fd-num is a tty" - [fd-num] - (js/PLANCK_ISATTY fd-num)) - -(s/fdef tty? - :args (s/cat :fd-num (s/and integer? pos?))) - ;; Ensure planck.io and planck.http are loaded so that their ;; facilities are available (#'repl/side-load-ns 'planck.http) diff --git a/planck-cljs/src/planck/io.cljs b/planck-cljs/src/planck/io.cljs index 688678c1..d721e0bb 100644 --- a/planck-cljs/src/planck/io.cljs +++ b/planck-cljs/src/planck/io.cljs @@ -552,6 +552,30 @@ :args (s/cat :input any? :output any? :opts (s/* any?)) :ret nil?) +(defn 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. + + Returns nil otherwise." + [x] + (when-let [fd (cond-> x + (not (and (integer? x) (>= x 0))) + {planck.core/*in* 0 + cljs.core/*out* 1 + planck.core/*err* 2})] + (js/PLANCK_ISATTY fd))) + +(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 (s/or :tty? boolean? + :invalid nil?)) + ;; 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..bce551d4 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 (nil? (planck.io/tty? nil))) + (is (nil? (planck.io/tty? -1))) + (is (nil? (planck.io/tty? 99999999999999999))) + (is (nil? (planck.io/tty? (planck.io/reader regular-file))))))) (deftest coercions (testing "as-file coerceions" From 1f6990555474ef02513f108a7afd78b11d382dca Mon Sep 17 00:00:00 2001 From: arbscht Date: Sun, 14 Apr 2019 00:37:45 +1200 Subject: [PATCH 3/7] Return boolean for tty? predicate --- planck-cljs/src/planck/io.cljs | 22 ++++++++++------------ planck-cljs/test/planck/io_test.cljs | 8 ++++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/planck-cljs/src/planck/io.cljs b/planck-cljs/src/planck/io.cljs index d721e0bb..5f6f560c 100644 --- a/planck-cljs/src/planck/io.cljs +++ b/planck-cljs/src/planck/io.cljs @@ -552,29 +552,27 @@ :args (s/cat :input any? :output any? :opts (s/* any?)) :ret nil?) -(defn tty? +(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. - - Returns nil otherwise." + not associated with a terminal, or an invalid file descriptor." [x] - (when-let [fd (cond-> x - (not (and (integer? x) (>= x 0))) - {planck.core/*in* 0 - cljs.core/*out* 1 - planck.core/*err* 2})] - (js/PLANCK_ISATTY fd))) + (boolean + (when-let [fd (cond-> x + (not (and (integer? x) (>= x 0))) + {planck.core/*in* 0 + cljs.core/*out* 1 + planck.core/*err* 2})] + (js/PLANCK_ISATTY fd)))) (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 (s/or :tty? boolean? - :invalid nil?)) + :ret boolean?) ;; These have been moved (def ^:deprecated read-line planck.core/read-line) diff --git a/planck-cljs/test/planck/io_test.cljs b/planck-cljs/test/planck/io_test.cljs index bce551d4..92b86355 100644 --- a/planck-cljs/test/planck/io_test.cljs +++ b/planck-cljs/test/planck/io_test.cljs @@ -78,10 +78,10 @@ (is (boolean? (planck.io/tty? planck.core/*in*))) (is (boolean? (planck.io/tty? *out*))) (is (boolean? (planck.io/tty? planck.core/*err*))) - (is (nil? (planck.io/tty? nil))) - (is (nil? (planck.io/tty? -1))) - (is (nil? (planck.io/tty? 99999999999999999))) - (is (nil? (planck.io/tty? (planck.io/reader regular-file))))))) + (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" From 783ba1bac9a7bef2e512c9a2d10d9bcd7deb8b2a Mon Sep 17 00:00:00 2001 From: arbscht Date: Sun, 14 Apr 2019 01:42:05 +1200 Subject: [PATCH 4/7] Integration test fixes for tty? --- int-test/script/gen-actual | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/int-test/script/gen-actual b/int-test/script/gen-actual index 87a6738a..870a8e29 100755 --- a/int-test/script/gen-actual +++ b/int-test/script/gen-actual @@ -797,7 +797,11 @@ x REPL_INPUT faketty () { - script -qfec "$(printf "%q " "$@")" + if [[ "$OSTYPE" == "darwin"* ]]; then + script -qt 0 /dev/null $@ + else + script -qfec "$(printf "%q " "$@")" /dev/null + fi } echo "stdio TTY is detected (in a faketty)" faketty $PLANCK -i $SRC/test_tty/stdio.cljs From f71caf7a66acd774a1678be414e1018697469806 Mon Sep 17 00:00:00 2001 From: arbscht Date: Sun, 14 Apr 2019 02:53:31 +1200 Subject: [PATCH 5/7] Add entries in changelog and site docs for tty? --- CHANGELOG.md | 2 ++ site/src/planck-io.md | 10 ++++++++++ 2 files changed, 12 insertions(+) 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/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])` From 184dae39e2e9b18453d81e7c4f0f800ec89a41ab Mon Sep 17 00:00:00 2001 From: arbscht Date: Sun, 14 Apr 2019 12:48:56 +1200 Subject: [PATCH 6/7] Tidy up tty? code a bit --- planck-cljs/src/planck/io.cljs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/planck-cljs/src/planck/io.cljs b/planck-cljs/src/planck/io.cljs index 5f6f560c..badd82ae 100644 --- a/planck-cljs/src/planck/io.cljs +++ b/planck-cljs/src/planck/io.cljs @@ -560,13 +560,12 @@ Returns false if x is a file descriptor, *in*, *out*, or *err* and not associated with a terminal, or an invalid file descriptor." [x] - (boolean - (when-let [fd (cond-> x - (not (and (integer? x) (>= x 0))) - {planck.core/*in* 0 - cljs.core/*out* 1 - planck.core/*err* 2})] - (js/PLANCK_ISATTY fd)))) + (let [stdio->fd {planck.core/*in* 0 + cljs.core/*out* 1 + planck.core/*err* 2}] + (-> (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?)) From 3b69ee5b1736d64d279105cb820c90cc569f1d7f Mon Sep 17 00:00:00 2001 From: arbscht Date: Sun, 14 Apr 2019 15:57:26 +1200 Subject: [PATCH 7/7] Fix potential rebinding collision bug in tty? --- int-test/expected/PLANCK-OUT.txt | 2 ++ int-test/script/gen-actual | 9 +++++++++ int-test/src/test_tty/rebinding_err_out.cljs | 5 +++++ planck-cljs/src/planck/io.cljs | 14 ++++++++------ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 int-test/src/test_tty/rebinding_err_out.cljs diff --git a/int-test/expected/PLANCK-OUT.txt b/int-test/expected/PLANCK-OUT.txt index 89da571c..3b75a84b 100644 --- a/int-test/expected/PLANCK-OUT.txt +++ b/int-test/expected/PLANCK-OUT.txt @@ -619,3 +619,5 @@ 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 870a8e29..ed7e9b84 100755 --- a/int-test/script/gen-actual +++ b/int-test/script/gen-actual @@ -818,3 +818,12 @@ echo echo "stderr non-TTY is detected (redirected to /dev/null)" $PLANCK -i $SRC/test_tty/stderr.cljs 2>/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/planck-cljs/src/planck/io.cljs b/planck-cljs/src/planck/io.cljs index badd82ae..64509286 100644 --- a/planck-cljs/src/planck/io.cljs +++ b/planck-cljs/src/planck/io.cljs @@ -552,6 +552,11 @@ :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 @@ -560,12 +565,9 @@ Returns false if x is a file descriptor, *in*, *out*, or *err* and not associated with a terminal, or an invalid file descriptor." [x] - (let [stdio->fd {planck.core/*in* 0 - cljs.core/*out* 1 - planck.core/*err* 2}] - (-> (if-let [fd (stdio->fd x)] fd x) - js/PLANCK_ISATTY - boolean))) + (-> (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?))