From ea075217cac1ed1a21ca3af85ede26a867ae8157 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Fri, 9 Feb 2024 07:43:11 +0000 Subject: [PATCH] *flush-on-newline* support --- CHANGELOG.md | 1 + src/basilisp/core.lpy | 25 ++++++++++- tests/basilisp/test_core_fns.lpy | 71 +++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d94534f..602914f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added a compile-time warning for attempting to call a function with an unsupported number of arguments (#671) * Added support for explicit cause exception chaining to the `throw` special form (#862) * Added `basilisp.stacktrace` namespace (#721) + * Added support for `*flush-on-newline*` to flush the `prn` and `println` output stream after the last newline (#865) ### Changed * Cause exceptions arising from compilation issues during macroexpansion will no longer be nested for each level of macroexpansion (#852) diff --git a/src/basilisp/core.lpy b/src/basilisp/core.lpy index e3c2ea045..29617165c 100644 --- a/src/basilisp/core.lpy +++ b/src/basilisp/core.lpy @@ -4098,6 +4098,13 @@ (def ^:dynamic *print-sep* " ") +(def ^:dynamic *flush-on-newline* + "Indicates whether the `:lpy:fn:prn` and `:lpy:fn:println` functions + should flush the output stream after the last newline is written. + + Defaults to true." + true) + (defn ^:inline repr "Return the reader representation of an object." [x] @@ -4153,14 +4160,20 @@ nil))) (defn prn - "Same as :lpy:fn:`pr`, but appending a newline afterwards." + "Same as :lpy:fn:`pr`, but appending a newline afterwards. + + Observes :lpy:var:`*flush-on-newline*`." ([] (.write *out* os/linesep) + (when *flush-on-newline* + (.flush *out*)) nil) ([x] (let [stdout *out*] (.write stdout (repr x)) (.write stdout os/linesep) + (when *flush-on-newline* + (.flush stdout)) nil)) ([x & args] (let [stdout *out* @@ -4170,6 +4183,8 @@ (.write stdout sep) (.write stdout (apply str repr-args)) (.write stdout os/linesep) + (when *flush-on-newline* + (.flush *out*)) nil))) (defn pr-str @@ -4205,12 +4220,16 @@ "Print the arguments to the stream bound to :lpy:var:`*out*` in a format which is readable by humans. ``println`` always prints a trailing newline. Multiple arguments will be separated by the string value bound to :lpy:var:`*print-sep*` (default is an - ASCII space)." + ASCII space). + + Observes :lpy:var:`*flush-on-newline*`." ([] (println "")) ([x] (let [stdout *out*] (.write stdout (basilisp.lang.runtime/lstr x)) (.write stdout os/linesep) + (when *flush-on-newline* + (.flush stdout)) nil)) ([x & args] (let [stdout *out* @@ -4220,6 +4239,8 @@ (.write stdout sep) (.write stdout (apply str repr-args)) (.write stdout os/linesep) + (when *flush-on-newline* + (.flush stdout)) nil))) (defn print-str diff --git a/tests/basilisp/test_core_fns.lpy b/tests/basilisp/test_core_fns.lpy index 15e057ba5..a20912dd5 100644 --- a/tests/basilisp/test_core_fns.lpy +++ b/tests/basilisp/test_core_fns.lpy @@ -1,5 +1,6 @@ (ns tests.basilisp.test-core-fns - (:import shutil + (:import os + shutil time) (:require [basilisp.io :as bio] @@ -2142,3 +2143,71 @@ (deftest pr-test (testing "is dynamic" (is (= '(1) (binding [pr (fn [& more] more)] (pr 1)))))) + +(defn- bio-write + "Helper fn to write the ``strings`` to the ByteIO buffer ``bio`` + and return the contents of the buffer." + [bio & strings] + (.write bio (python/bytes (apply str strings) "UTF-8")) + (.getvalue bio)) + +(deftest flush-on-newline-test + ;; :line-buffering false :newline "" + ;; + ;; the above options instruct the TextIOWrapper to internally + ;; disable (1) flushing on newlines and (2) universal newlines + ;; mode. + + (testing "prn newline flushing" + (let [bio (io/BytesIO) + expected (io/BytesIO)] + (binding [*out* (io/TextIOWrapper bio ** :line-buffering false :newline "") + *flush-on-newline* true] + (prn) + (is (= (bio-write expected os/linesep) (.getvalue bio))) + (prn "ab") + (is (= (bio-write expected "\"ab\"" os/linesep) (.getvalue bio))) + (prn "ab" 1) + (is (= (bio-write expected "\"ab\"" " " 1 os/linesep) (.getvalue bio)))))) + + (testing "prn newline not flushing" + (let [bio (io/BytesIO) + expected (io/BytesIO)] + (binding [*out* (io/TextIOWrapper bio ** :line-buffering false :newline "") + *flush-on-newline* false] + (prn) + (is (= #b "" (.getvalue bio))) + (prn "ab") + (is (= #b "" (.getvalue bio))) + (prn "ab" 1) + (is (= #b "" (.getvalue bio))) + (.flush *out*) + (is (= (bio-write expected os/linesep "\"ab\"" os/linesep "\"ab\"" " " 1 os/linesep) + (.getvalue bio)))))) + + (testing "println newline flushing" + (let [bio (io/BytesIO) + expected (io/BytesIO)] + (binding [*out* (io/TextIOWrapper bio ** :line-buffering false :newline "") + *flush-on-newline* true] + (println) + (is (= (bio-write expected os/linesep) (.getvalue bio))) + (println "ab") + (is (= (bio-write expected "ab" os/linesep) (.getvalue bio))) + (println "ab" 1) + (is (= (bio-write expected "ab" " " 1 os/linesep) (.getvalue bio)))))) + + (testing "println newline not flushing" + (let [bio (io/BytesIO) + expected (io/BytesIO)] + (binding [*out* (io/TextIOWrapper bio ** :line-buffering false :newline "") + *flush-on-newline* false] + (println) + (is (= #b "" (.getvalue bio))) + (println "ab") + (is (= #b "" (.getvalue bio))) + (println "ab" 1) + (is (= #b "" (.getvalue bio))) + (.flush *out*) + (is (= (bio-write expected os/linesep "ab" os/linesep "ab" " " 1 os/linesep) + (.getvalue bio)))))))