[#137] Add REPL prompt at startup
This commit is contained in:
parent
8304f41f3f
commit
c2037c0ed8
6 changed files with 113 additions and 57 deletions
|
|
@ -1 +1 @@
|
||||||
0.0.39-SNAPSHOT
|
0.0.39
|
||||||
|
|
|
||||||
54
src/babashka/impl/repl.clj
Normal file
54
src/babashka/impl/repl.clj
Normal file
|
|
@ -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)))
|
||||||
|
|
@ -2,56 +2,12 @@
|
||||||
{:no-doc true}
|
{:no-doc true}
|
||||||
(:require
|
(:require
|
||||||
[babashka.impl.clojure.core.server :as server]
|
[babashka.impl.clojure.core.server :as server]
|
||||||
[babashka.impl.clojure.main :as m]
|
[babashka.impl.repl :as repl]
|
||||||
[clojure.java.io :as io]
|
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.reader.reader-types :as r]
|
[sci.impl.opts :refer [init]]))
|
||||||
[sci.core :as sci]
|
|
||||||
[sci.impl.interpreter :refer [eval-form]]
|
|
||||||
[sci.impl.opts :refer [init]]
|
|
||||||
[sci.impl.parser :as parser]))
|
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(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]
|
(defn start-repl! [host+port sci-opts]
|
||||||
(let [parts (str/split host+port #":")
|
(let [parts (str/split host+port #":")
|
||||||
[host port] (if (= 1 (count parts))
|
[host port] (if (= 1 (count parts))
|
||||||
|
|
@ -64,7 +20,7 @@
|
||||||
{:address host
|
{:address host
|
||||||
:port port
|
:port port
|
||||||
:name "bb"
|
:name "bb"
|
||||||
:accept babashka.impl.socket-repl/repl
|
:accept babashka.impl.repl/repl
|
||||||
:args [sci-ctx]})]
|
:args [sci-ctx]})]
|
||||||
(println "Babashka socket REPL started at" host+port)
|
(println "Babashka socket REPL started at" host+port)
|
||||||
socket))
|
socket))
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
[babashka.impl.conch :refer [conch-namespace]]
|
[babashka.impl.conch :refer [conch-namespace]]
|
||||||
[babashka.impl.csv :as csv]
|
[babashka.impl.csv :as csv]
|
||||||
[babashka.impl.pipe-signal-handler :refer [handle-pipe! pipe-signal-received?]]
|
[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.socket-repl :as socket-repl]
|
||||||
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
|
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
|
||||||
[babashka.impl.utils :refer [eval-string]]
|
[babashka.impl.utils :refer [eval-string]]
|
||||||
|
|
@ -65,6 +66,11 @@
|
||||||
(recur (rest options)
|
(recur (rest options)
|
||||||
(assoc opts-map
|
(assoc opts-map
|
||||||
:file (first options))))
|
:file (first options))))
|
||||||
|
("--repl")
|
||||||
|
(let [options (rest options)]
|
||||||
|
(recur (rest options)
|
||||||
|
(assoc opts-map
|
||||||
|
:repl true)))
|
||||||
("--socket-repl")
|
("--socket-repl")
|
||||||
(let [options (rest options)]
|
(let [options (rest options)]
|
||||||
(recur (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.
|
--stream: stream over lines or EDN values from stdin. Combined with -i or -I *in* becomes a single value per iteration.
|
||||||
-e, --eval <expression>: evaluate an expression
|
-e, --eval <expression>: evaluate an expression
|
||||||
-f, --file <path>: evaluate a file
|
-f, --file <path>: 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).
|
--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.
|
--time: print execution time before exiting.
|
||||||
|
|
||||||
|
|
@ -152,6 +159,13 @@ Everything after that is bound to *command-line-args*."))
|
||||||
(defn eval* [ctx form]
|
(defn eval* [ctx form]
|
||||||
(eval-string (pr-str form) ctx))
|
(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]
|
(defn start-socket-repl! [address ctx read-next]
|
||||||
(let [ctx (update ctx :bindings assoc
|
(let [ctx (update ctx :bindings assoc
|
||||||
(with-meta '*in*
|
(with-meta '*in*
|
||||||
|
|
@ -176,7 +190,9 @@ Everything after that is bound to *command-line-args*."))
|
||||||
(let [t0 (System/currentTimeMillis)
|
(let [t0 (System/currentTimeMillis)
|
||||||
{:keys [:version :shell-in :edn-in :shell-out :edn-out
|
{:keys [:version :shell-in :edn-in :shell-out :edn-out
|
||||||
:help? :file :command-line-args
|
: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)
|
(parse-opts args)
|
||||||
read-next (fn [*in*]
|
read-next (fn [*in*]
|
||||||
(if (pipe-signal-received?)
|
(if (pipe-signal-received?)
|
||||||
|
|
@ -268,6 +284,7 @@ Everything after that is bound to *command-line-args*."))
|
||||||
[(print-version) 0]
|
[(print-version) 0]
|
||||||
help?
|
help?
|
||||||
[(print-help) 0]
|
[(print-help) 0]
|
||||||
|
repl [(start-repl! ctx #(read-next *in*)) 0]
|
||||||
socket-repl [(start-socket-repl! socket-repl ctx #(read-next *in*)) 0]
|
socket-repl [(start-socket-repl! socket-repl ctx #(read-next *in*)) 0]
|
||||||
:else
|
:else
|
||||||
(try
|
(try
|
||||||
|
|
@ -292,7 +309,7 @@ Everything after that is bound to *command-line-args*."))
|
||||||
(if stream?
|
(if stream?
|
||||||
(recur (read-next *in*))
|
(recur (read-next *in*))
|
||||||
res)))))
|
res)))))
|
||||||
[(print-help) 1]))
|
[(start-repl! ctx #(read-next *in*)) 0]))
|
||||||
(catch Throwable e
|
(catch Throwable e
|
||||||
(binding [*out* *err*]
|
(binding [*out* *err*]
|
||||||
(let [d (ex-data e)
|
(let [d (ex-data e)
|
||||||
|
|
|
||||||
35
test/babashka/impl/repl_test.clj
Normal file
35
test/babashka/impl/repl_test.clj
Normal file
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -107,13 +107,7 @@
|
||||||
|
|
||||||
(deftest malformed-command-line-args-test
|
(deftest malformed-command-line-args-test
|
||||||
(is (thrown-with-msg? Exception #"File does not exist: non-existing\n"
|
(is (thrown-with-msg? Exception #"File does not exist: non-existing\n"
|
||||||
(bb nil "-f" "non-existing")))
|
(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:"))))
|
|
||||||
|
|
||||||
(deftest ssl-test
|
(deftest ssl-test
|
||||||
(let [resp (bb nil "(slurp \"https://www.google.com\")")]
|
(let [resp (bb nil "(slurp \"https://www.google.com\")")]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue