[#41] socket REPL
This commit is contained in:
parent
6d1a8c0791
commit
2dbb749e35
10 changed files with 375 additions and 18 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,3 +12,4 @@ pom.xml.asc
|
||||||
.hg/
|
.hg/
|
||||||
/bb
|
/bb
|
||||||
.clj-kondo/.cache
|
.clj-kondo/.cache
|
||||||
|
!java/src/babashka/impl/LockFix.class
|
||||||
|
|
|
||||||
29
README.md
29
README.md
|
|
@ -249,6 +249,32 @@ export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'
|
||||||
|
|
||||||
Note that `*in*` is not available in preloads.
|
Note that `*in*` is not available in preloads.
|
||||||
|
|
||||||
|
## Socket REPL
|
||||||
|
|
||||||
|
Start the socket REPL like this:
|
||||||
|
|
||||||
|
``` shellsession
|
||||||
|
$ bb --socket-repl 1666
|
||||||
|
Babashka socket REPL started at localhost:1666
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can connect with your favorite socket REPL client:
|
||||||
|
|
||||||
|
``` shellsession
|
||||||
|
$ rlwrap nc 127.0.0.1 1666
|
||||||
|
Babashka v0.0.14-SNAPSHOT REPL.
|
||||||
|
Use :repl/quit or :repl/exit to quit the REPL.
|
||||||
|
Clojure rocks, Bash reaches.
|
||||||
|
|
||||||
|
bb=> (+ 1 2 3)
|
||||||
|
6
|
||||||
|
bb=> :repl/quit
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
A socket REPL client for Emacs is
|
||||||
|
[inf-clojure](https://github.com/clojure-emacs/inf-clojure).
|
||||||
|
|
||||||
## Enabling SSL
|
## Enabling SSL
|
||||||
|
|
||||||
If you want to be able to use SSL to e.g. run `(slurp
|
If you want to be able to use SSL to e.g. run `(slurp
|
||||||
|
|
@ -360,4 +386,5 @@ beverage](https://ko-fi.com/borkdude).
|
||||||
|
|
||||||
Copyright © 2019 Michiel Borkent
|
Copyright © 2019 Michiel Borkent
|
||||||
|
|
||||||
Distributed under the EPL License, same as Clojure. See LICENSE.
|
Distributed under the EPL License. This project contains modified Clojure code
|
||||||
|
which is licensed under the same EPL License. See LICENSE.
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@
|
||||||
:url "http://opensource.org/licenses/eclipse-1.0.php"}
|
:url "http://opensource.org/licenses/eclipse-1.0.php"}
|
||||||
:source-paths ["src" "sci/src" "sci/inlined"]
|
:source-paths ["src" "sci/src" "sci/inlined"]
|
||||||
:resource-paths ["resources" "sci/resources"]
|
:resource-paths ["resources" "sci/resources"]
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]]
|
:dependencies [[org.clojure/clojure "1.10.1"]]
|
||||||
:profiles {:clojure-1.9.0 {:dependencies [[org.clojure/clojure "1.9.0"]]}
|
:profiles {:test {:dependencies [[clj-commons/conch "0.9.2"]]}
|
||||||
:clojure-1.10.1 {:dependencies [[org.clojure/clojure "1.10.1"]]}
|
|
||||||
:test {:dependencies [[clj-commons/conch "0.9.2"]]}
|
|
||||||
:uberjar {:global-vars {*assert* false}
|
:uberjar {:global-vars {*assert* false}
|
||||||
:jvm-opts ["-Dclojure.compiler.direct-linking=true"
|
:jvm-opts ["-Dclojure.compiler.direct-linking=true"
|
||||||
"-Dclojure.spec.skip-macros=true"]
|
"-Dclojure.spec.skip-macros=true"]
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
|
||||||
# mkdir -p src/sci
|
# mkdir -p src/sci
|
||||||
# cp -R /tmp/sci/src/* src
|
# cp -R /tmp/sci/src/* src
|
||||||
|
|
||||||
lein with-profiles +clojure-1.10.1 do clean, uberjar
|
lein do clean, uberjar
|
||||||
$NATIVE_IMAGE \
|
$NATIVE_IMAGE \
|
||||||
-jar target/babashka-$BABASHKA_VERSION-standalone.jar \
|
-jar target/babashka-$BABASHKA_VERSION-standalone.jar \
|
||||||
-H:Name=bb \
|
-H:Name=bb \
|
||||||
|
|
|
||||||
10
script/test
10
script/test
|
|
@ -3,12 +3,4 @@
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
export BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
|
export BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
|
||||||
|
|
||||||
if [ "$BABASHKA_TEST_ENV" = "native" ]; then
|
lein test
|
||||||
lein test
|
|
||||||
else
|
|
||||||
echo "Testing with Clojure 1.9.0"
|
|
||||||
lein with-profiles +clojure-1.9.0 test
|
|
||||||
|
|
||||||
echo "Testing with Clojure 1.10.1"
|
|
||||||
lein with-profiles +clojure-1.10.1 test
|
|
||||||
fi
|
|
||||||
|
|
|
||||||
100
src/babashka/impl/clojure/core/server.clj
Normal file
100
src/babashka/impl/clojure/core/server.clj
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
;; Modified / stripped version of clojure.core.server for use with babashka on
|
||||||
|
;; GraalVM.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) Rich Hickey. All rights reserved.
|
||||||
|
;; The use and distribution terms for this software are covered by the
|
||||||
|
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
||||||
|
;; which can be found in the file epl-v10.html at the root of this distribution.
|
||||||
|
;; By using this software in any fashion, you are agreeing to be bound by
|
||||||
|
;; the terms of this license.
|
||||||
|
;; You must not remove this notice, or any other, from this software.
|
||||||
|
|
||||||
|
(ns ^{:doc "Socket server support"
|
||||||
|
:author "Alex Miller"
|
||||||
|
:no-doc true}
|
||||||
|
babashka.impl.clojure.core.server
|
||||||
|
(:refer-clojure :exclude [locking])
|
||||||
|
(:import
|
||||||
|
[clojure.lang LineNumberingPushbackReader]
|
||||||
|
[java.net InetAddress Socket ServerSocket SocketException]
|
||||||
|
[java.io Reader Writer PrintWriter BufferedWriter BufferedReader InputStreamReader OutputStreamWriter]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(def server (atom nil))
|
||||||
|
|
||||||
|
(defmacro ^:private thread
|
||||||
|
[^String name daemon & body]
|
||||||
|
`(doto (Thread. (fn [] ~@body) ~name)
|
||||||
|
(.setDaemon ~daemon)
|
||||||
|
(.start)))
|
||||||
|
|
||||||
|
(defn- accept-connection
|
||||||
|
"Start accept function, to be invoked on a client thread, given:
|
||||||
|
conn - client socket
|
||||||
|
name - server name
|
||||||
|
client-id - client identifier
|
||||||
|
in - in stream
|
||||||
|
out - out stream
|
||||||
|
err - err stream
|
||||||
|
accept - accept fn symbol to invoke
|
||||||
|
args - to pass to accept-fn"
|
||||||
|
[^Socket conn client-id in out err accept args]
|
||||||
|
(try
|
||||||
|
(binding [*in* in
|
||||||
|
*out* out
|
||||||
|
*err* err]
|
||||||
|
(swap! server assoc-in [:sessions client-id] {})
|
||||||
|
(apply accept args))
|
||||||
|
(catch SocketException _disconnect)
|
||||||
|
(finally
|
||||||
|
(swap! server update-in [:sessions] dissoc client-id)
|
||||||
|
(.close conn))))
|
||||||
|
|
||||||
|
(defn start-server
|
||||||
|
"Start a socket server given the specified opts:
|
||||||
|
:address Host or address, string, defaults to loopback address
|
||||||
|
:port Port, integer, required
|
||||||
|
:name Name, required
|
||||||
|
:accept Namespaced symbol of the accept function to invoke, required
|
||||||
|
:args Vector of args to pass to accept function
|
||||||
|
:bind-err Bind *err* to socket out stream?, defaults to true
|
||||||
|
:server-daemon Is server thread a daemon?, defaults to true
|
||||||
|
:client-daemon Are client threads daemons?, defaults to true
|
||||||
|
Returns server socket."
|
||||||
|
[opts]
|
||||||
|
(let [{:keys [address port name accept args bind-err server-daemon client-daemon]
|
||||||
|
:or {bind-err true
|
||||||
|
server-daemon true
|
||||||
|
client-daemon true}} opts
|
||||||
|
address (InetAddress/getByName address) ;; nil returns loopback
|
||||||
|
socket (ServerSocket. port 0 address)]
|
||||||
|
(reset! server {:name name, :socket socket, :sessions {}})
|
||||||
|
(thread
|
||||||
|
(str "Clojure Server " name) server-daemon
|
||||||
|
(try
|
||||||
|
(loop [client-counter 1]
|
||||||
|
(when (not (.isClosed socket))
|
||||||
|
(try
|
||||||
|
(let [conn (.accept socket)
|
||||||
|
in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream conn)))
|
||||||
|
out (BufferedWriter. (OutputStreamWriter. (.getOutputStream conn)))
|
||||||
|
client-id (str client-counter)]
|
||||||
|
(thread
|
||||||
|
(str "Clojure Connection " name " " client-id) client-daemon
|
||||||
|
(accept-connection conn client-id in out (if bind-err out *err*) accept args)))
|
||||||
|
(catch SocketException _disconnect))
|
||||||
|
(recur (inc client-counter))))
|
||||||
|
(finally
|
||||||
|
(reset! server nil))))
|
||||||
|
socket))
|
||||||
|
|
||||||
|
(defn stop-server
|
||||||
|
"Stop server with name or use the server-name from *session* if none supplied.
|
||||||
|
Returns true if server stopped successfully, nil if not found, or throws if
|
||||||
|
there is an error closing the socket."
|
||||||
|
[]
|
||||||
|
(when-let [s @server]
|
||||||
|
(when-let [server-socket ^ServerSocket (get s :socket)]
|
||||||
|
(.close server-socket)))
|
||||||
|
(reset! server nil))
|
||||||
143
src/babashka/impl/clojure/main.clj
Normal file
143
src/babashka/impl/clojure/main.clj
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
;; Modified / stripped version of clojure.main for use with babashka on
|
||||||
|
;; GraalVM.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) Rich Hickey All rights reserved. The use and
|
||||||
|
;; distribution terms for this software are covered by the Eclipse Public
|
||||||
|
;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found
|
||||||
|
;; in the file epl-v10.html at the root of this distribution. By using this
|
||||||
|
;; software in any fashion, you are agreeing to be bound by the terms of
|
||||||
|
;; this license. You must not remove this notice, or any other, from this
|
||||||
|
;; software.
|
||||||
|
|
||||||
|
;; Originally contributed by Stephen C. Gilardi
|
||||||
|
|
||||||
|
(ns ^{:doc "Top-level main function for Clojure REPL and scripts."
|
||||||
|
:author "Stephen C. Gilardi and Rich Hickey"
|
||||||
|
:no-doc true}
|
||||||
|
babashka.impl.clojure.main
|
||||||
|
(:refer-clojure :exclude [with-bindings])
|
||||||
|
(:import (java.io StringReader)
|
||||||
|
(clojure.lang LineNumberingPushbackReader LispReader$ReaderException)))
|
||||||
|
|
||||||
|
(defmacro with-bindings
|
||||||
|
"Executes body in the context of thread-local bindings for several vars
|
||||||
|
that often need to be set!: *ns* *warn-on-reflection* *math-context*
|
||||||
|
*print-meta* *print-length* *print-level* *compile-path*
|
||||||
|
*command-line-args* *1 *2 *3 *e"
|
||||||
|
[& body]
|
||||||
|
`(binding [*ns* *ns*
|
||||||
|
*warn-on-reflection* *warn-on-reflection*
|
||||||
|
*math-context* *math-context*
|
||||||
|
*print-meta* *print-meta*
|
||||||
|
*print-length* *print-length*
|
||||||
|
*print-level* *print-level*
|
||||||
|
*print-namespace-maps* true
|
||||||
|
*data-readers* *data-readers*
|
||||||
|
*default-data-reader-fn* *default-data-reader-fn*
|
||||||
|
*command-line-args* *command-line-args*
|
||||||
|
*unchecked-math* *unchecked-math*
|
||||||
|
*assert* *assert*
|
||||||
|
;; clojure.spec.alpha/*explain-out* clojure.spec.alpha/*explain-out*
|
||||||
|
*1 nil
|
||||||
|
*2 nil
|
||||||
|
*3 nil
|
||||||
|
*e nil]
|
||||||
|
~@body))
|
||||||
|
|
||||||
|
(defn repl-prompt
|
||||||
|
"Default :prompt hook for repl"
|
||||||
|
[]
|
||||||
|
(print "bb=> " ))
|
||||||
|
|
||||||
|
(defn repl-caught
|
||||||
|
"Default :caught hook for repl"
|
||||||
|
[e]
|
||||||
|
(binding [*out* *err*]
|
||||||
|
(println (.getMessage ^Exception e))
|
||||||
|
(flush)))
|
||||||
|
|
||||||
|
(defn repl
|
||||||
|
"Generic, reusable, read-eval-print loop. By default, reads from *in*,
|
||||||
|
writes to *out*, and prints exception summaries to *err*. If you use the
|
||||||
|
default :read hook, *in* must either be an instance of
|
||||||
|
LineNumberingPushbackReader or duplicate its behavior of both supporting
|
||||||
|
.unread and collapsing CR, LF, and CRLF into a single \\newline. Options
|
||||||
|
are sequential keyword-value pairs. Available options and their defaults:
|
||||||
|
- :init, function of no arguments, initialization hook called with
|
||||||
|
bindings for set!-able vars in place.
|
||||||
|
default: #()
|
||||||
|
- :need-prompt, function of no arguments, called before each
|
||||||
|
read-eval-print except the first, the user will be prompted if it
|
||||||
|
returns true.
|
||||||
|
default: (if (instance? LineNumberingPushbackReader *in*)
|
||||||
|
#(.atLineStart *in*)
|
||||||
|
#(identity true))
|
||||||
|
- :prompt, function of no arguments, prompts for more input.
|
||||||
|
default: repl-prompt
|
||||||
|
- :flush, function of no arguments, flushes output
|
||||||
|
default: flush
|
||||||
|
- :read, function of two arguments, reads from *in*:
|
||||||
|
- returns its first argument to request a fresh prompt
|
||||||
|
- depending on need-prompt, this may cause the repl to prompt
|
||||||
|
before reading again
|
||||||
|
- returns its second argument to request an exit from the repl
|
||||||
|
- else returns the next object read from the input stream
|
||||||
|
default: repl-read
|
||||||
|
- :eval, function of one argument, returns the evaluation of its
|
||||||
|
argument
|
||||||
|
default: eval
|
||||||
|
- :print, function of one argument, prints its argument to the output
|
||||||
|
default: prn
|
||||||
|
- :caught, function of one argument, a throwable, called when
|
||||||
|
read, eval, or print throws an exception or error
|
||||||
|
default: repl-caught"
|
||||||
|
[& options]
|
||||||
|
(let [{:keys [init need-prompt prompt flush read eval print caught]
|
||||||
|
:or {need-prompt (if (instance? LineNumberingPushbackReader *in*)
|
||||||
|
#(.atLineStart ^LineNumberingPushbackReader *in*)
|
||||||
|
#(identity true))
|
||||||
|
prompt repl-prompt
|
||||||
|
flush flush
|
||||||
|
print prn
|
||||||
|
caught repl-caught}}
|
||||||
|
(apply hash-map options)
|
||||||
|
request-prompt (Object.)
|
||||||
|
request-exit (Object.)
|
||||||
|
read-eval-print
|
||||||
|
(fn []
|
||||||
|
(try
|
||||||
|
(let [input (try
|
||||||
|
(read request-prompt request-exit)
|
||||||
|
(catch LispReader$ReaderException e
|
||||||
|
(throw (ex-info nil {:clojure.error/phase :read-source} e))))]
|
||||||
|
(or (#{request-prompt request-exit} input)
|
||||||
|
(let [value (eval input)]
|
||||||
|
(set! *3 *2)
|
||||||
|
(set! *2 *1)
|
||||||
|
(set! *1 value)
|
||||||
|
(try
|
||||||
|
(print value)
|
||||||
|
(catch Throwable e
|
||||||
|
(throw (ex-info nil {:clojure.error/phase :print-eval-result} e)))))))
|
||||||
|
(catch Throwable e
|
||||||
|
(caught e)
|
||||||
|
(set! *e e))))]
|
||||||
|
(with-bindings
|
||||||
|
(try
|
||||||
|
(init)
|
||||||
|
(catch Throwable e
|
||||||
|
(caught e)
|
||||||
|
(set! *e e)))
|
||||||
|
(prompt)
|
||||||
|
(flush)
|
||||||
|
(loop []
|
||||||
|
(when-not
|
||||||
|
(try (identical? (read-eval-print) request-exit)
|
||||||
|
(catch Throwable e
|
||||||
|
(caught e)
|
||||||
|
(set! *e e)
|
||||||
|
nil))
|
||||||
|
(when (need-prompt)
|
||||||
|
(prompt)
|
||||||
|
(flush))
|
||||||
|
(recur))))))
|
||||||
66
src/babashka/impl/socket_repl.clj
Normal file
66
src/babashka/impl/socket_repl.clj
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
(ns babashka.impl.socket-repl
|
||||||
|
{:no-doc true}
|
||||||
|
(:require [babashka.impl.clojure.core.server :as server]
|
||||||
|
[babashka.impl.clojure.main :as m]
|
||||||
|
[sci.core :refer [eval-string]]
|
||||||
|
[sci.impl.parser :as parser]
|
||||||
|
[sci.impl.toolsreader.v1v3v2.clojure.tools.reader.reader-types :as r]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[clojure.java.io :as io]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(defn repl
|
||||||
|
"REPL with predefined hooks for attachable socket server."
|
||||||
|
[sci-opts]
|
||||||
|
(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]
|
||||||
|
(let [in (r/indexing-push-back-reader (r/push-back-reader *in*))
|
||||||
|
p (r/peek-char in)]
|
||||||
|
(if (= \newline p)
|
||||||
|
(do (r/read-char in) request-prompt)
|
||||||
|
(let [v (parser/parse-next {} in)]
|
||||||
|
(if (or (identical? :repl/quit v)
|
||||||
|
(identical? :repl/exit v))
|
||||||
|
request-exit
|
||||||
|
v)))))
|
||||||
|
:eval (fn [expr]
|
||||||
|
(eval-string (str expr)
|
||||||
|
(update sci-opts
|
||||||
|
:bindings
|
||||||
|
merge {'*1 *1
|
||||||
|
'*2 *2
|
||||||
|
'*3 *3
|
||||||
|
'*e *e})))))
|
||||||
|
|
||||||
|
(defn start-repl! [host+port sci-opts]
|
||||||
|
(let [parts (str/split host+port #":")
|
||||||
|
[host port] (if (= 1 (count parts))
|
||||||
|
[nil (Integer. ^String (first parts))]
|
||||||
|
[(first parts) (Integer. ^String (second parts))])
|
||||||
|
host+port (if-not host (str "localhost:" port)
|
||||||
|
host+port)
|
||||||
|
socket (server/start-server
|
||||||
|
{:address host
|
||||||
|
:port port
|
||||||
|
:name "bb"
|
||||||
|
:accept babashka.impl.socket-repl/repl
|
||||||
|
:args [sci-opts]})]
|
||||||
|
(println "Babashka socket REPL started at" host+port)
|
||||||
|
socket))
|
||||||
|
|
||||||
|
(defn stop-repl! []
|
||||||
|
(server/stop-server))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(def sock (start-repl! "0.0.0.0:1666" {:env (atom {})}))
|
||||||
|
(.accept sock)
|
||||||
|
@#'server/servers
|
||||||
|
(stop-repl!)
|
||||||
|
)
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.java.shell :as shell]
|
[clojure.java.shell :as shell]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
|
[babashka.impl.socket-repl :as socket-repl]
|
||||||
[sci.core :as sci])
|
[sci.core :as sci])
|
||||||
(:import [sun.misc Signal]
|
(:import [sun.misc Signal]
|
||||||
[sun.misc SignalHandler])
|
[sun.misc SignalHandler])
|
||||||
|
|
@ -55,6 +56,11 @@
|
||||||
(recur (rest options)
|
(recur (rest options)
|
||||||
(assoc opts-map
|
(assoc opts-map
|
||||||
:file (first options))))
|
:file (first options))))
|
||||||
|
("--socket-repl")
|
||||||
|
(let [options (rest options)]
|
||||||
|
(recur (rest options)
|
||||||
|
(assoc opts-map
|
||||||
|
:socket-repl (first options))))
|
||||||
(if (not (:file opts-map))
|
(if (not (:file opts-map))
|
||||||
(assoc opts-map
|
(assoc opts-map
|
||||||
:expression opt
|
:expression opt
|
||||||
|
|
@ -80,7 +86,7 @@
|
||||||
(defn print-version []
|
(defn print-version []
|
||||||
(println (str "babashka v"(str/trim (slurp (io/resource "BABASHKA_VERSION"))))))
|
(println (str "babashka v"(str/trim (slurp (io/resource "BABASHKA_VERSION"))))))
|
||||||
|
|
||||||
(def usage-string "Usage: bb [ --help ] | [ --version ] | [ -i | -I ] [ -o | -O ] [ --stream ] ( expression | -f <file> )")
|
(def usage-string "Usage: bb [ --help ] | [ --version ] | [ -i | -I ] [ -o | -O ] [ --stream ] ( expression | -f <file> | --socket-repl [host:]port )")
|
||||||
(defn print-usage []
|
(defn print-usage []
|
||||||
(println usage-string))
|
(println usage-string))
|
||||||
|
|
||||||
|
|
@ -101,6 +107,7 @@
|
||||||
-O: write EDN values to stdout.
|
-O: write EDN values to stdout.
|
||||||
--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.
|
||||||
--file or -f: read expressions from file instead of argument wrapped in an implicit do.
|
--file or -f: read expressions from file instead of argument wrapped in an implicit do.
|
||||||
|
--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.
|
||||||
"))
|
"))
|
||||||
|
|
||||||
|
|
@ -178,7 +185,7 @@
|
||||||
(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?] :as _opts}
|
:expression :stream? :time? :socket-repl] :as _opts}
|
||||||
(parse-opts args)
|
(parse-opts args)
|
||||||
read-next #(if (pipe-signal-received?)
|
read-next #(if (pipe-signal-received?)
|
||||||
::EOF
|
::EOF
|
||||||
|
|
@ -205,12 +212,15 @@
|
||||||
[(print-version) 0]
|
[(print-version) 0]
|
||||||
help?
|
help?
|
||||||
[(print-help) 0]
|
[(print-help) 0]
|
||||||
|
socket-repl [(do (socket-repl/start-repl! socket-repl ctx)
|
||||||
|
@(promise)) 0]
|
||||||
:else
|
:else
|
||||||
(try
|
(try
|
||||||
(let [expr (if file (read-file file) expression)]
|
(let [expr (if file (read-file file) expression)]
|
||||||
(loop [in (read-next)]
|
(loop [in (read-next)]
|
||||||
(let [ctx (update ctx :bindings assoc (with-meta '*in*
|
(let [ctx (update ctx :bindings assoc (with-meta '*in*
|
||||||
(when-not stream? {:sci/deref! true})) in)]
|
(when-not stream?
|
||||||
|
{:sci/deref! true})) in)]
|
||||||
(if (identical? ::EOF in)
|
(if (identical? ::EOF in)
|
||||||
[nil 0] ;; done streaming
|
[nil 0] ;; done streaming
|
||||||
(let [res [(do (when-not (or expression file)
|
(let [res [(do (when-not (or expression file)
|
||||||
|
|
|
||||||
20
test/babashka/impl/socket_repl_test.clj
Normal file
20
test/babashka/impl/socket_repl_test.clj
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
(ns babashka.impl.socket-repl-test
|
||||||
|
(:require
|
||||||
|
[babashka.impl.socket-repl :refer [start-repl! stop-repl!]]
|
||||||
|
[babashka.test-utils :as tu]
|
||||||
|
[clojure.java.shell :refer [sh]]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[clojure.test :as t :refer [deftest is]]))
|
||||||
|
|
||||||
|
(deftest socket-repl-test
|
||||||
|
(when tu/jvm?
|
||||||
|
(start-repl! "0.0.0.0:1666" {:env (atom {})})
|
||||||
|
(is (str/includes? (:out (sh "bash" "-c"
|
||||||
|
"echo \"(+ 1 2 3)\n:repl/exit\" | nc 127.0.0.1 1666"))
|
||||||
|
"bb=> 6"))
|
||||||
|
(stop-repl!)))
|
||||||
|
|
||||||
|
;;;; Scratch
|
||||||
|
|
||||||
|
(comment
|
||||||
|
)
|
||||||
Loading…
Reference in a new issue