[#137] Add REPL prompt at startup

This commit is contained in:
Michiel Borkent 2019-12-08 12:46:06 +01:00 committed by GitHub
parent 8304f41f3f
commit c2037c0ed8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 57 deletions

View file

@ -1 +1 @@
0.0.39-SNAPSHOT 0.0.39

View 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)))

View file

@ -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))

View file

@ -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)

View 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
)

View file

@ -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\")")]