[#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}
|
||||
(: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))
|
||||
|
|
|
|||
|
|
@ -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 <expression>: evaluate an expression
|
||||
-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).
|
||||
--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)
|
||||
|
|
|
|||
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
|
||||
(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\")")]
|
||||
|
|
|
|||
Loading…
Reference in a new issue