--uberscript option (#197)
This commit is contained in:
parent
c446dced0e
commit
ded6dd2355
6 changed files with 198 additions and 130 deletions
71
README.md
71
README.md
|
|
@ -137,25 +137,26 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk
|
|||
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
|
||||
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
|
||||
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
|
||||
[ arg* ]
|
||||
[ --uberscript <file> ] [ arg* ]
|
||||
|
||||
Options:
|
||||
|
||||
--help, -h or -? Print this help text.
|
||||
--version Print the current version of babashka.
|
||||
-i Bind *input* to a lazy seq of lines from stdin.
|
||||
-I Bind *input* to a lazy seq of EDN values from stdin.
|
||||
-o Write lines to stdout.
|
||||
-O Write EDN values to stdout.
|
||||
--verbose Print entire stacktrace in case of exception.
|
||||
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
|
||||
-e, --eval <expr> Evaluate an expression.
|
||||
-f, --file <path> Evaluate a file.
|
||||
-cp, --classpath Classpath to use.
|
||||
-m, --main <ns> Call the -main function from namespace with args.
|
||||
--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.
|
||||
--help, -h or -? Print this help text.
|
||||
--version Print the current version of babashka.
|
||||
-i Bind *input* to a lazy seq of lines from stdin.
|
||||
-I Bind *input* to a lazy seq of EDN values from stdin.
|
||||
-o Write lines to stdout.
|
||||
-O Write EDN values to stdout.
|
||||
--verbose Print entire stacktrace in case of exception.
|
||||
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
|
||||
-e, --eval <expr> Evaluate an expression.
|
||||
-f, --file <path> Evaluate a file.
|
||||
-cp, --classpath Classpath to use.
|
||||
-m, --main <ns> Call the -main function from namespace with args.
|
||||
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable 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.
|
||||
|
||||
If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise.
|
||||
Everything after that is bound to *command-line-args*.
|
||||
|
|
@ -408,10 +409,10 @@ Note that you can use the `clojure` tool to produce classpaths and download depe
|
|||
``` shellsession
|
||||
$ cat deps.edn
|
||||
{:deps
|
||||
{my_gist_script
|
||||
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
|
||||
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}}
|
||||
|
||||
{my_gist_script
|
||||
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
|
||||
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}
|
||||
:aliases {:my-script {:main-opts ["-m" "my-gist-script"]}}}
|
||||
|
||||
$ CLASSPATH=$(clojure -Spath)
|
||||
$ bb --classpath "$CLASSPATH" --main my-gist-script
|
||||
|
|
@ -428,6 +429,36 @@ $ bb "(my-gist-script/-main)"
|
|||
Hello from gist script!
|
||||
```
|
||||
|
||||
Using the [deps.clj](https://github.com/borkdude/deps.clj/) script, you can also
|
||||
pass the classpath and main opts to `bb`:
|
||||
|
||||
``` shell
|
||||
$ deps.clj -A:my-script -Scommand "bb my_script.clj {{main-opts}}"
|
||||
Hello from gist script!
|
||||
```
|
||||
|
||||
## Uberscript
|
||||
|
||||
The `--uberscript <file>` option collects the expressions in
|
||||
`BABASHKA_PRELOADS`, the command line expression or file, the main entrypoint
|
||||
and all required namespaces from the classpath into a single file. This can be
|
||||
convenient for debugging purposes and deployment.
|
||||
|
||||
Given the `deps.edn` from above:
|
||||
|
||||
``` shell
|
||||
$ deps.clj -A:my-script -Scommand "bb {{main-opts}} --uberscript my-script.clj"
|
||||
|
||||
$ cat my-script.clj
|
||||
(ns my-gist-script)
|
||||
(defn -main [& args]
|
||||
(println "Hello from gist script!"))(require '[my-gist-script])
|
||||
(ns user (:require [my-gist-script])) (apply my-gist-script/-main *command-line-args*)%
|
||||
|
||||
$ bb my-script.clj
|
||||
Hello from gist script!
|
||||
```
|
||||
|
||||
## Parsing command line arguments
|
||||
|
||||
Babashka ships with `clojure.tools.cli`:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.0.51-SNAPSHOT
|
||||
0.0.51
|
||||
2
sci
2
sci
|
|
@ -1 +1 @@
|
|||
Subproject commit 79563a0c94daa11e56e0969c963a52af9f7ee00f
|
||||
Subproject commit 92f2251fcc2ca795db05e78605e1c78bb8462884
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
(ns babashka.impl.utils
|
||||
{:no-doc true}
|
||||
(:require
|
||||
[sci.impl.vars :as vars]
|
||||
[sci.core :as sci]))
|
||||
|
||||
(sci.impl.vars/bindRoot sci/in *in*)
|
||||
(sci.impl.vars/bindRoot sci/out *out*)
|
||||
(sci.impl.vars/bindRoot sci/err *err*)
|
||||
|
||||
(defn eval-string [expr ctx]
|
||||
(sci/eval-string expr ctx))
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
[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]]
|
||||
[babashka.wait :as wait]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.java.io :as io]
|
||||
|
|
@ -26,6 +25,10 @@
|
|||
[sci.impl.interpreter :refer [eval-string*]])
|
||||
(:gen-class))
|
||||
|
||||
(sci.impl.vars/bindRoot sci/in *in*)
|
||||
(sci.impl.vars/bindRoot sci/out *out*)
|
||||
(sci.impl.vars/bindRoot sci/err *err*)
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
;; To detect problems when generating the image, run:
|
||||
;; echo '1' | java -agentlib:native-image-agent=config-output-dir=/tmp -jar target/babashka-xxx-standalone.jar '...'
|
||||
|
|
@ -68,6 +71,15 @@
|
|||
(assoc opts-map
|
||||
:edn-in true
|
||||
:edn-out true))
|
||||
("--classpath", "-cp")
|
||||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
(assoc opts-map :classpath (first options))))
|
||||
("--uberscript")
|
||||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
(assoc opts-map
|
||||
:uberscript (first options))))
|
||||
("-f" "--file")
|
||||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
|
|
@ -87,10 +99,6 @@
|
|||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
(assoc opts-map :expression (first options))))
|
||||
("--classpath", "-cp")
|
||||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
(assoc opts-map :classpath (first options))))
|
||||
("--main", "-m")
|
||||
(let [options (next options)]
|
||||
(recur (next options)
|
||||
|
|
@ -128,7 +136,7 @@
|
|||
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
|
||||
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
|
||||
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
|
||||
[ arg* ]")
|
||||
[ --uberscript <file> ] [ arg* ]")
|
||||
(defn print-usage []
|
||||
(println usage-string))
|
||||
|
||||
|
|
@ -140,21 +148,22 @@
|
|||
(println)
|
||||
(println "Options:")
|
||||
(println "
|
||||
--help, -h or -? Print this help text.
|
||||
--version Print the current version of babashka.
|
||||
-i Bind *input* to a lazy seq of lines from stdin.
|
||||
-I Bind *input* to a lazy seq of EDN values from stdin.
|
||||
-o Write lines to stdout.
|
||||
-O Write EDN values to stdout.
|
||||
--verbose Print entire stacktrace in case of exception.
|
||||
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
|
||||
-e, --eval <expr> Evaluate an expression.
|
||||
-f, --file <path> Evaluate a file.
|
||||
-cp, --classpath Classpath to use.
|
||||
-m, --main <ns> Call the -main function from namespace with args.
|
||||
--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.
|
||||
--help, -h or -? Print this help text.
|
||||
--version Print the current version of babashka.
|
||||
-i Bind *input* to a lazy seq of lines from stdin.
|
||||
-I Bind *input* to a lazy seq of EDN values from stdin.
|
||||
-o Write lines to stdout.
|
||||
-O Write EDN values to stdout.
|
||||
--verbose Print entire stacktrace in case of exception.
|
||||
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
|
||||
-e, --eval <expr> Evaluate an expression.
|
||||
-f, --file <path> Evaluate a file.
|
||||
-cp, --classpath Classpath to use.
|
||||
-m, --main <ns> Call the -main function from namespace with args.
|
||||
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable 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.
|
||||
|
||||
If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise.
|
||||
Everything after that is bound to *command-line-args*."))
|
||||
|
|
@ -173,14 +182,14 @@ Everything after that is bound to *command-line-args*."))
|
|||
|
||||
(def reflection-var (sci/new-dynamic-var '*warn-on-reflection* false))
|
||||
|
||||
(defn load-file* [ctx f]
|
||||
(defn load-file* [sci-ctx f]
|
||||
(let [f (io/file f)
|
||||
s (slurp f)]
|
||||
(sci/with-bindings {vars/file-var (.getCanonicalPath f)}
|
||||
(eval-string s ctx))))
|
||||
(eval-string* sci-ctx s))))
|
||||
|
||||
(defn eval* [ctx form]
|
||||
(eval-string (pr-str form) ctx))
|
||||
(defn eval* [sci-ctx form]
|
||||
(eval-string* sci-ctx (pr-str form)))
|
||||
|
||||
(defn start-socket-repl! [address ctx]
|
||||
(socket-repl/start-repl! address ctx)
|
||||
|
|
@ -194,6 +203,45 @@ Everything after that is bound to *command-line-args*."))
|
|||
;; (sci/set-var-root! sci/*out* *out*)
|
||||
;; (sci/set-var-root! sci/*err* *err*)
|
||||
|
||||
(def aliases
|
||||
'{tools.cli 'clojure.tools.cli
|
||||
edn clojure.edn
|
||||
wait babashka.wait
|
||||
sig babashka.signal
|
||||
shell clojure.java.shell
|
||||
io clojure.java.io
|
||||
async clojure.core.async
|
||||
csv clojure.data.csv
|
||||
json cheshire.core})
|
||||
|
||||
(def namespaces
|
||||
{'clojure.tools.cli tools-cli-namespace
|
||||
'clojure.edn {'read edn/read
|
||||
'read-string edn/read-string}
|
||||
'clojure.java.shell {'sh shell/sh}
|
||||
'babashka.wait {'wait-for-port wait/wait-for-port
|
||||
'wait-for-path wait/wait-for-path}
|
||||
'babashka.signal {'pipe-signal-received? pipe-signal-received?}
|
||||
'clojure.java.io io-namespace
|
||||
'clojure.core.async async-namespace
|
||||
'clojure.data.csv csv/csv-namespace
|
||||
'cheshire.core cheshire-core-namespace})
|
||||
|
||||
(def bindings
|
||||
{'java.lang.System/exit exit ;; override exit, so we have more control
|
||||
'System/exit exit})
|
||||
|
||||
(defn error-handler* [^Exception e verbose?]
|
||||
(binding [*out* *err*]
|
||||
(let [d (ex-data e)
|
||||
exit-code (:bb/exit-code d)]
|
||||
(if exit-code [nil exit-code]
|
||||
(do (if verbose?
|
||||
(print-stack-trace e)
|
||||
(println (.getMessage e)))
|
||||
(flush)
|
||||
[nil 1])))))
|
||||
|
||||
(defn main
|
||||
[& args]
|
||||
(handle-pipe!)
|
||||
|
|
@ -205,7 +253,7 @@ Everything after that is bound to *command-line-args*."))
|
|||
:expression :stream? :time?
|
||||
:repl :socket-repl
|
||||
:verbose? :classpath
|
||||
:main] :as _opts}
|
||||
:main :uberscript] :as _opts}
|
||||
(parse-opts args)
|
||||
read-next (fn [*in*]
|
||||
(if (pipe-signal-received?)
|
||||
|
|
@ -219,6 +267,7 @@ Everything after that is bound to *command-line-args*."))
|
|||
(edn-seq *in*)
|
||||
:else
|
||||
(edn/read *in*))))))
|
||||
uberscript-sources (atom ())
|
||||
env (atom {})
|
||||
classpath (or classpath
|
||||
(System/getenv "BABASHKA_CLASSPATH"))
|
||||
|
|
@ -226,35 +275,18 @@ Everything after that is bound to *command-line-args*."))
|
|||
(cp/loader classpath))
|
||||
load-fn (when classpath
|
||||
(fn [{:keys [:namespace]}]
|
||||
(cp/source-for-namespace loader namespace)))
|
||||
(let [res (cp/source-for-namespace loader namespace)]
|
||||
(when uberscript (swap! uberscript-sources conj (:source res)))
|
||||
res)))
|
||||
_ (when file (vars/bindRoot vars/file-var (.getCanonicalPath (io/file file))))
|
||||
ctx {:aliases '{tools.cli 'clojure.tools.cli
|
||||
edn clojure.edn
|
||||
wait babashka.wait
|
||||
sig babashka.signal
|
||||
shell clojure.java.shell
|
||||
io clojure.java.io
|
||||
async clojure.core.async
|
||||
csv clojure.data.csv
|
||||
json cheshire.core}
|
||||
:namespaces {'clojure.core (assoc core-extras
|
||||
'*command-line-args*
|
||||
(sci/new-dynamic-var '*command-line-args* command-line-args)
|
||||
'*file* vars/file-var
|
||||
'*warn-on-reflection* reflection-var)
|
||||
'clojure.tools.cli tools-cli-namespace
|
||||
'clojure.edn {'read edn/read
|
||||
'read-string edn/read-string}
|
||||
'clojure.java.shell {'sh shell/sh}
|
||||
'babashka.wait {'wait-for-port wait/wait-for-port
|
||||
'wait-for-path wait/wait-for-path}
|
||||
'babashka.signal {'pipe-signal-received? pipe-signal-received?}
|
||||
'clojure.java.io io-namespace
|
||||
'clojure.core.async async-namespace
|
||||
'clojure.data.csv csv/csv-namespace
|
||||
'cheshire.core cheshire-core-namespace}
|
||||
:bindings {'java.lang.System/exit exit ;; override exit, so we have more control
|
||||
'System/exit exit}
|
||||
ctx {:aliases aliases
|
||||
:namespaces (assoc namespaces 'clojure.core
|
||||
(assoc core-extras
|
||||
'*command-line-args*
|
||||
(sci/new-dynamic-var '*command-line-args* command-line-args)
|
||||
'*file* vars/file-var
|
||||
'*warn-on-reflection* reflection-var))
|
||||
:bindings bindings
|
||||
:env env
|
||||
:features #{:bb}
|
||||
:classes classes/class-map
|
||||
|
|
@ -271,37 +303,47 @@ Everything after that is bound to *command-line-args*."))
|
|||
String java.lang.String
|
||||
System java.lang.System
|
||||
Thread java.lang.Thread}
|
||||
:load-fn load-fn}
|
||||
ctx (update-in ctx [:namespaces 'clojure.core] assoc
|
||||
'eval #(eval* ctx %)
|
||||
'load-file #(load-file* ctx %))
|
||||
ctx (assoc-in ctx [:namespaces 'clojure.main 'repl]
|
||||
(fn [& opts]
|
||||
(let [opts (apply hash-map opts)]
|
||||
(repl/start-repl! ctx opts))))
|
||||
:load-fn load-fn
|
||||
:dry-run uberscript}
|
||||
ctx (addons/future ctx)
|
||||
_preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (eval-string ctx))
|
||||
expression (if main
|
||||
(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
|
||||
main)
|
||||
expression)
|
||||
sci-ctx (sci-opts/init ctx)
|
||||
_ (swap! (:env sci-ctx)
|
||||
(fn [env]
|
||||
(update-in env [:namespaces 'clojure.core] assoc
|
||||
'eval #(eval* sci-ctx %)
|
||||
'load-file #(load-file* sci-ctx %))))
|
||||
_ (swap! (:env sci-ctx)
|
||||
(fn [env]
|
||||
(assoc-in env [:namespaces 'clojure.main 'repl]
|
||||
(fn [& opts]
|
||||
(let [opts (apply hash-map opts)]
|
||||
(repl/start-repl! sci-ctx opts))))))
|
||||
preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim))
|
||||
[expression exit-code]
|
||||
(cond expression [expression nil]
|
||||
main [(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
|
||||
main) nil]
|
||||
file (try [(read-file file) nil]
|
||||
(catch Exception e
|
||||
(error-handler* e verbose?))))
|
||||
expression (str (when preloads
|
||||
(str preloads "\n"))
|
||||
expression)
|
||||
exit-code
|
||||
(sci/with-bindings {reflection-var false}
|
||||
(or
|
||||
#_(binding [*out* *err*]
|
||||
(prn ">>" _opts))
|
||||
(second
|
||||
(cond version
|
||||
[(print-version) 0]
|
||||
help?
|
||||
[(print-help) 0]
|
||||
repl [(repl/start-repl! sci-ctx) 0]
|
||||
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
|
||||
:else
|
||||
(try
|
||||
(let [ expr (if file (read-file file) expression)]
|
||||
(if expr
|
||||
(or exit-code
|
||||
(sci/with-bindings {reflection-var false}
|
||||
(or
|
||||
#_(binding [*out* *err*]
|
||||
(prn ">>" _opts))
|
||||
(second
|
||||
(cond version
|
||||
[(print-version) 0]
|
||||
help?
|
||||
[(print-help) 0]
|
||||
repl [(repl/start-repl! sci-ctx) 0]
|
||||
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
|
||||
expression
|
||||
(try
|
||||
(loop [in (read-next *in*)]
|
||||
(let [_ (swap! env update-in [:namespaces 'user]
|
||||
assoc (with-meta '*input*
|
||||
|
|
@ -310,7 +352,7 @@ Everything after that is bound to *command-line-args*."))
|
|||
(sci/new-dynamic-var '*input* in))]
|
||||
(if (identical? ::EOF in)
|
||||
[nil 0] ;; done streaming
|
||||
(let [res [(let [res (eval-string* sci-ctx expr)]
|
||||
(let [res [(let [res (eval-string* sci-ctx expression)]
|
||||
(when (some? res)
|
||||
(if-let [pr-f (cond shell-out println
|
||||
edn-out prn)]
|
||||
|
|
@ -323,22 +365,22 @@ Everything after that is bound to *command-line-args*."))
|
|||
(if stream?
|
||||
(recur (read-next *in*))
|
||||
res)))))
|
||||
[(repl/start-repl! sci-ctx) 0]))
|
||||
(catch Throwable e
|
||||
(binding [*out* *err*]
|
||||
(let [d (ex-data e)
|
||||
exit-code (:bb/exit-code d)]
|
||||
(if exit-code [nil exit-code]
|
||||
(do (if verbose?
|
||||
(print-stack-trace e)
|
||||
(println (.getMessage e)))
|
||||
(flush)
|
||||
[nil 1]))))))))
|
||||
1))
|
||||
(catch Throwable e
|
||||
(error-handler* e verbose?)))
|
||||
:else [(repl/start-repl! sci-ctx) 0]))
|
||||
1)))
|
||||
t1 (System/currentTimeMillis)]
|
||||
(flush)
|
||||
(when uberscript
|
||||
uberscript
|
||||
(let [uberscript-out uberscript]
|
||||
(spit uberscript-out "") ;; reset file
|
||||
(doseq [s @uberscript-sources]
|
||||
(spit uberscript-out s :append true))
|
||||
(spit uberscript-out expression :append true)
|
||||
(spit uberscript-out file :append true)))
|
||||
(when time? (binding [*out* *err*]
|
||||
(println "bb took" (str (- t1 t0) "ms."))))
|
||||
(flush)
|
||||
exit-code))
|
||||
|
||||
(defn -main
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@
|
|||
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
|
||||
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "1" "2" "3" "4"))))
|
||||
|
||||
(deftest uberscript-test
|
||||
(let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
|
||||
(.deleteOnExit tmp-file)
|
||||
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "--uberscript" (.getPath tmp-file))
|
||||
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
|
||||
(tu/bb nil "--file" (.getPath tmp-file) "1" "2" "3" "4")))))
|
||||
|
||||
(deftest error-while-loading-test
|
||||
(is (true?
|
||||
(bb nil "--classpath" "test-resources/babashka/src_for_classpath_test"
|
||||
|
|
|
|||
Loading…
Reference in a new issue