From c2037c0ed852766da28133c5aad2447b38a5981a Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Sun, 8 Dec 2019 12:46:06 +0100 Subject: [PATCH] [#137] Add REPL prompt at startup --- resources/BABASHKA_VERSION | 2 +- src/babashka/impl/repl.clj | 54 +++++++++++++++++++++++++++++++ src/babashka/impl/socket_repl.clj | 50 ++-------------------------- src/babashka/main.clj | 21 ++++++++++-- test/babashka/impl/repl_test.clj | 35 ++++++++++++++++++++ test/babashka/main_test.clj | 8 +---- 6 files changed, 113 insertions(+), 57 deletions(-) create mode 100644 src/babashka/impl/repl.clj create mode 100644 test/babashka/impl/repl_test.clj diff --git a/resources/BABASHKA_VERSION b/resources/BABASHKA_VERSION index 3dceb212..12a74d75 100644 --- a/resources/BABASHKA_VERSION +++ b/resources/BABASHKA_VERSION @@ -1 +1 @@ -0.0.39-SNAPSHOT +0.0.39 diff --git a/src/babashka/impl/repl.clj b/src/babashka/impl/repl.clj new file mode 100644 index 00000000..68b461fb --- /dev/null +++ b/src/babashka/impl/repl.clj @@ -0,0 +1,54 @@ +(ns babashka.impl.repl + {:no-doc true} + (:require + [babashka.impl.clojure.main :as m] + [clojure.java.io :as io] + [clojure.string :as str] + [clojure.tools.reader.reader-types :as r] + [sci.core :as sci] + [sci.impl.interpreter :refer [eval-form]] + [sci.impl.opts :refer [init]] + [sci.impl.parser :as parser])) + +(defn repl + "REPL with predefined hooks for attachable socket server." + [sci-ctx] + (let [in (r/indexing-push-back-reader (r/push-back-reader *in*))] + (m/repl + :init #(do (println "Babashka" + (str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION")))) + "REPL.") + (println "Use :repl/quit or :repl/exit to quit the REPL.") + (println "Clojure rocks, Bash reaches.") + (println)) + :read (fn [_request-prompt request-exit] + (if (r/peek-char in) ;; if this is nil, we reached EOF + (let [v (parser/parse-next in #{:bb} {:current (-> sci-ctx :env deref :current-ns)})] + (if (or (identical? :repl/quit v) + (identical? :repl/exit v) + (identical? :edamame.impl.parser/eof v)) + request-exit + v)) + request-exit)) + :eval (fn [expr] + (let [ret (sci/with-bindings {sci/in *in* + sci/out *out* + sci/err *err*} + (eval-form (update sci-ctx + :env + (fn [env] + (swap! env update-in [:namespaces 'clojure.core] + assoc + '*1 *1 + '*2 *2 + '*3 *3 + '*e *e) + env)) + expr))] + ret)) + :need-prompt (fn [] true) + :prompt #(printf "%s=> " (-> sci-ctx :env deref :current-ns))))) + +(defn start-repl! [ctx] + (let [sci-ctx (init ctx)] + (repl sci-ctx))) diff --git a/src/babashka/impl/socket_repl.clj b/src/babashka/impl/socket_repl.clj index fe713acf..13195287 100644 --- a/src/babashka/impl/socket_repl.clj +++ b/src/babashka/impl/socket_repl.clj @@ -2,56 +2,12 @@ {:no-doc true} (:require [babashka.impl.clojure.core.server :as server] - [babashka.impl.clojure.main :as m] - [clojure.java.io :as io] + [babashka.impl.repl :as repl] [clojure.string :as str] - [clojure.tools.reader.reader-types :as r] - [sci.core :as sci] - [sci.impl.interpreter :refer [eval-form]] - [sci.impl.opts :refer [init]] - [sci.impl.parser :as parser])) + [sci.impl.opts :refer [init]])) (set! *warn-on-reflection* true) -(defn repl - "REPL with predefined hooks for attachable socket server." - [sci-ctx] - (let [in (r/indexing-push-back-reader (r/push-back-reader *in*))] - (m/repl - :init #(do (println "Babashka" - (str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION")))) - "REPL.") - (println "Use :repl/quit or :repl/exit to quit the REPL.") - (println "Clojure rocks, Bash reaches.") - (println)) - :read (fn [_request-prompt request-exit] - (if (r/peek-char in) ;; if this is nil, we reached EOF - (let [v (parser/parse-next in #{:bb} {:current (-> sci-ctx :env deref :current-ns)})] - (if (or (identical? :repl/quit v) - (identical? :repl/exit v) - (identical? :edamame.impl.parser/eof v)) - request-exit - v)) - request-exit)) - :eval (fn [expr] - (let [ret (sci/with-bindings {sci/in *in* - sci/out *out* - sci/err *err*} - (eval-form (update sci-ctx - :env - (fn [env] - (swap! env update-in [:namespaces 'clojure.core] - assoc - '*1 *1 - '*2 *2 - '*3 *3 - '*e *e) - env)) - expr))] - ret)) - :need-prompt (fn [] true) - :prompt #(printf "%s=> " (-> sci-ctx :env deref :current-ns))))) - (defn start-repl! [host+port sci-opts] (let [parts (str/split host+port #":") [host port] (if (= 1 (count parts)) @@ -64,7 +20,7 @@ {:address host :port port :name "bb" - :accept babashka.impl.socket-repl/repl + :accept babashka.impl.repl/repl :args [sci-ctx]})] (println "Babashka socket REPL started at" host+port) socket)) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index e8b8010a..23f7d41c 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -8,6 +8,7 @@ [babashka.impl.conch :refer [conch-namespace]] [babashka.impl.csv :as csv] [babashka.impl.pipe-signal-handler :refer [handle-pipe! pipe-signal-received?]] + [babashka.impl.repl :as repl] [babashka.impl.socket-repl :as socket-repl] [babashka.impl.tools.cli :refer [tools-cli-namespace]] [babashka.impl.utils :refer [eval-string]] @@ -65,6 +66,11 @@ (recur (rest options) (assoc opts-map :file (first options)))) + ("--repl") + (let [options (rest options)] + (recur (rest options) + (assoc opts-map + :repl true))) ("--socket-repl") (let [options (rest options)] (recur (rest options) @@ -127,6 +133,7 @@ --stream: stream over lines or EDN values from stdin. Combined with -i or -I *in* becomes a single value per iteration. -e, --eval : evaluate an expression -f, --file : evaluate a file + --repl: start REPL --socket-repl: start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666). --time: print execution time before exiting. @@ -152,6 +159,13 @@ Everything after that is bound to *command-line-args*.")) (defn eval* [ctx form] (eval-string (pr-str form) ctx)) +(defn start-repl! [ctx read-next] + (let [ctx (update ctx :bindings assoc + (with-meta '*in* + {:sci/deref! true}) + (read-next))] + (repl/start-repl! ctx))) + (defn start-socket-repl! [address ctx read-next] (let [ctx (update ctx :bindings assoc (with-meta '*in* @@ -176,7 +190,9 @@ Everything after that is bound to *command-line-args*.")) (let [t0 (System/currentTimeMillis) {:keys [:version :shell-in :edn-in :shell-out :edn-out :help? :file :command-line-args - :expression :stream? :time? :socket-repl :verbose?] :as _opts} + :expression :stream? :time? + :repl :socket-repl + :verbose?] :as _opts} (parse-opts args) read-next (fn [*in*] (if (pipe-signal-received?) @@ -268,6 +284,7 @@ Everything after that is bound to *command-line-args*.")) [(print-version) 0] help? [(print-help) 0] + repl [(start-repl! ctx #(read-next *in*)) 0] socket-repl [(start-socket-repl! socket-repl ctx #(read-next *in*)) 0] :else (try @@ -292,7 +309,7 @@ Everything after that is bound to *command-line-args*.")) (if stream? (recur (read-next *in*)) res))))) - [(print-help) 1])) + [(start-repl! ctx #(read-next *in*)) 0])) (catch Throwable e (binding [*out* *err*] (let [d (ex-data e) diff --git a/test/babashka/impl/repl_test.clj b/test/babashka/impl/repl_test.clj new file mode 100644 index 00000000..8adbde77 --- /dev/null +++ b/test/babashka/impl/repl_test.clj @@ -0,0 +1,35 @@ +(ns babashka.impl.repl-test + (:require + [babashka.impl.repl :refer [start-repl!]] + [clojure.string :as str] + [clojure.test :as t :refer [deftest is]])) + +(set! *warn-on-reflection* true) + +(defn repl! [] + (start-repl! {:bindings {(with-meta '*in* + {:sci/deref! true}) + (delay [1 2 3]) + '*command-line-args* + ["a" "b" "c"]} + :env (atom {})})) + +(defn assert-repl [expr expected] + (is (str/includes? (with-out-str + (with-in-str (str expr "\n:repl/quit") + (repl!))) expected))) + +(deftest repl-test + (assert-repl "(+ 1 2 3)" "6") + (assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "6") + (assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "6") + (assert-repl "1\n(inc *1)" "2") + (assert-repl "1\n(dec *1)(+ *2 *2)" "2") + (assert-repl "1\n(dec *1)(+ *2 *2)" "2") + (assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]") + (assert-repl "*in*" "[1 2 3]")) + +;;;; Scratch + +(comment + ) diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index 92d3807b..f8251035 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -107,13 +107,7 @@ (deftest malformed-command-line-args-test (is (thrown-with-msg? Exception #"File does not exist: non-existing\n" - (bb nil "-f" "non-existing"))) - (testing "no arguments prints help" - (is (str/includes? - (try (test-utils/bb nil) - (catch clojure.lang.ExceptionInfo e - (:stdout (ex-data e)))) - "Usage:")))) + (bb nil "-f" "non-existing")))) (deftest ssl-test (let [resp (bb nil "(slurp \"https://www.google.com\")")]