babashka/src/babashka/main.clj
2021-03-24 17:04:01 +01:00

837 lines
38 KiB
Clojure

(ns babashka.main
{:no-doc true}
(:refer-clojure :exclude [error-handler])
(:require
[babashka.fs :as fs]
[babashka.impl.bencode :refer [bencode-namespace]]
[babashka.impl.cheshire :refer [cheshire-core-namespace]]
[babashka.impl.classes :as classes]
[babashka.impl.classpath :as cp :refer [classpath-namespace]]
[babashka.impl.clojure.core :as core :refer [core-extras]]
[babashka.impl.clojure.core.server :as server]
[babashka.impl.clojure.java.browse :refer [browse-namespace]]
[babashka.impl.clojure.java.io :refer [io-namespace]]
[babashka.impl.clojure.java.shell :refer [shell-namespace]]
[babashka.impl.clojure.main :as clojure-main :refer [demunge]]
[babashka.impl.clojure.stacktrace :refer [stacktrace-namespace]]
[babashka.impl.clojure.zip :refer [zip-namespace]]
[babashka.impl.common :as common]
[babashka.impl.curl :refer [curl-namespace]]
[babashka.impl.data :as data]
[babashka.impl.datafy :refer [datafy-namespace]]
[babashka.impl.deps :as deps :refer [deps-namespace]]
[babashka.impl.error-handler :refer [error-handler]]
[babashka.impl.features :as features]
[babashka.impl.fs :refer [fs-namespace]]
[babashka.impl.pods :as pods]
[babashka.impl.pprint :refer [pprint-namespace]]
[babashka.impl.process :refer [process-namespace]]
[babashka.impl.protocols :refer [protocols-namespace]]
[babashka.impl.proxy :refer [proxy-fn]]
[babashka.impl.reify :refer [reify-fn]]
[babashka.impl.repl :as repl]
[babashka.impl.socket-repl :as socket-repl]
[babashka.impl.test :as t]
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
[babashka.nrepl.server :as nrepl-server]
[babashka.process :as p]
[babashka.wait :as wait]
[clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.string :as str]
[hf.depstar.uberjar :as uberjar]
[sci.addons :as addons]
[sci.core :as sci]
[sci.impl.namespaces :as sci-namespaces]
[sci.impl.unrestrict :refer [*unrestricted*]]
[sci.impl.vars :as vars])
(:gen-class))
(def windows?
(some-> (System/getProperty "os.name")
(str/lower-case)
(str/index-of "win")))
(if-not windows?
(do ;; see https://github.com/oracle/graal/issues/1784
(require 'babashka.impl.pipe-signal-handler)
(let [handle-pipe! (resolve 'babashka.impl.pipe-signal-handler/handle-pipe!)]
(def handle-pipe! @handle-pipe!))
(let [pipe-signal-received? (resolve 'babashka.impl.pipe-signal-handler/pipe-signal-received?)]
(def pipe-signal-received? @pipe-signal-received?))
;; JVM_FindSignal called: Unimplemented
(require 'babashka.impl.sigint-handler)
(def handle-sigint! @(resolve 'babashka.impl.sigint-handler/handle-sigint!)))
(do
(def handle-pipe! (constantly nil))
(def pipe-signal-received? (constantly false))
(def handle-sigint! (constantly nil))))
(sci/alter-var-root sci/in (constantly *in*))
(sci/alter-var-root sci/out (constantly *out*))
(sci/alter-var-root sci/err (constantly *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 '...'
;; with the java provided by GraalVM.
(def version (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
(defn print-version []
(println (str "babashka v" version)))
(def bb-edn
(atom nil))
(defn print-help [ctx command-line-args]
(if (empty? command-line-args)
(do
(println (str "Babashka v" version))
;; (println (str "sci v" (str/trim (slurp (io/resource "SCI_VERSION")))))
(println)
(println "Options must appear in the order of groups mentioned below.")
(println "
Help:
--help, -h or -? Print this help text.
--version Print the current version of babashka.
--describe Print an EDN map with information about this version of babashka.
Evaluation:
-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.
--verbose Print debug information and entire stacktrace in case of exception.
REPL:
--repl Start REPL. Use rlwrap for history.
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--nrepl-server Start nREPL server. Specify port (e.g. 1667) or host and port separated by colon (e.g. 127.0.0.1:1667).
In- and output flags:
-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.
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
Uberscript:
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single file.
Uberjar:
--uberjar <jar> Similar to --uberscript but creates jar file.
Clojure:
--clojure [args...] Invokes clojure. Takes same args as the official clojure CLI.
If the first argument is not any of the above options, then it treated as a file if it exists, or as an expression otherwise.
Everything after that is bound to *command-line-args*.
Use -- to separate script command line args from bb command line args.
")
[nil 0]) ;; end do
(let [k (first command-line-args)
k (keyword (subs k 1))
task (get-in @bb-edn [:tasks k])
main (:main task)
help-text (:task/help task)]
(if help-text
[(println help-text) 0]
(if main
(let [main (if (simple-symbol? main)
(symbol (str main) "-main")
main)]
(if-let [doc (sci/eval-string* ctx (format "(some-> (requiring-resolve '%s) meta :doc)" main))]
[(println doc) 0]
[(println "No help found for task:" k) 1]))
[(println "No help found for task:" k) 1]))
,)) ;; end if
,) ;; end defn
(defn print-tasks [tasks]
(let [tasks (into (sorted-map) tasks)]
(println "The following tasks are available:")
(println)
(doseq [[k v] tasks]
(println k (:task/description v)))
(println)
(println "Run bb :help <task> to view help of a specific task.")
[nil 0]))
(defn print-describe []
(println
(format
(str/trim "
{:babashka/version \"%s\"
:feature/core-async %s
:feature/csv %s
:feature/java-nio %s
:feature/java-time %s
:feature/xml %s
:feature/yaml %s
:feature/jdbc %s
:feature/postgresql %s
:feature/hsqldb %s
:feature/oracledb %s
:feature/httpkit-client %s
:feature/lanterna %s
:feature/core-match %s
:feature/hiccup %s
:feature/test-check %s
:feature/spec-alpha %s}")
version
features/core-async?
features/csv?
features/java-nio?
features/java-time?
features/xml?
features/yaml?
features/jdbc?
features/postgresql?
features/hsqldb?
features/oracledb?
features/httpkit-client?
features/lanterna?
features/core-match?
features/hiccup?
features/test-check?
features/spec-alpha?)))
(defn read-file [file]
(let [f (io/file file)]
(if (.exists f)
(as-> (slurp file) x
;; remove shebang
(str/replace x #"^#!.*" ""))
(throw (Exception. (str "File does not exist: " file))))))
(defn load-file* [f]
(let [f (io/file f)
s (slurp f)]
(sci/with-bindings {sci/ns @sci/ns
sci/file (.getAbsolutePath f)}
(sci/eval-string* @common/ctx s))))
(defn start-socket-repl! [address ctx]
(socket-repl/start-repl! address ctx))
(defn start-nrepl! [address ctx]
(let [dev? (= "true" (System/getenv "BABASHKA_DEV"))
nrepl-opts (nrepl-server/parse-opt address)
nrepl-opts (assoc nrepl-opts
:debug dev?
:describe {"versions" {"babashka" version}}
:thread-bind [core/warn-on-reflection])]
(nrepl-server/start-server! ctx nrepl-opts)
(binding [*out* *err*]
(println "For more info visit: https://book.babashka.org/#_nrepl")))
;; hang until SIGINT
@(promise))
(def aliases
(cond->
'{tools.cli clojure.tools.cli
edn clojure.edn
wait babashka.wait
signal babashka.signal
shell clojure.java.shell
io clojure.java.io
json cheshire.core
curl babashka.curl
fs babashka.fs
bencode bencode.core
deps babashka.deps}
features/xml? (assoc 'xml 'clojure.data.xml)
features/yaml? (assoc 'yaml 'clj-yaml.core)
features/jdbc? (assoc 'jdbc 'next.jdbc)
features/core-async? (assoc 'async 'clojure.core.async)
features/csv? (assoc 'csv 'clojure.data.csv)
features/transit? (assoc 'transit 'cognitect.transit)))
;;(def ^:private server-ns-obj (sci/create-ns 'clojure.core.server nil))
(def clojure-core-server
{'repl socket-repl/repl
'prepl (fn [& args]
(apply server/prepl @common/ctx args))
'io-prepl (fn [& args]
(apply server/io-prepl @common/ctx args))
'start-server (fn [& args]
(apply server/start-server @common/ctx args))})
(def namespaces
(cond->
{'clojure.tools.cli tools-cli-namespace
'clojure.java.shell shell-namespace
'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
'cheshire.core cheshire-core-namespace
'clojure.data data/data-namespace
'clojure.stacktrace stacktrace-namespace
'clojure.zip zip-namespace
'clojure.main {'demunge demunge
'repl-requires clojure-main/repl-requires
'repl (fn [& opts]
(let [opts (apply hash-map opts)]
(repl/start-repl! @common/ctx opts)))}
'clojure.test t/clojure-test-namespace
'babashka.classpath classpath-namespace
'clojure.pprint pprint-namespace
'babashka.curl curl-namespace
'babashka.fs fs-namespace
'babashka.pods pods/pods-namespace
'bencode.core bencode-namespace
'clojure.java.browse browse-namespace
'clojure.datafy datafy-namespace
'clojure.core.protocols protocols-namespace
'babashka.process process-namespace
'clojure.core.server clojure-core-server
'babashka.deps deps-namespace}
features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace))
features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace)
'flatland.ordered.map @(resolve 'babashka.impl.ordered/ordered-map-ns))
features/jdbc? (assoc 'next.jdbc @(resolve 'babashka.impl.jdbc/njdbc-namespace)
'next.jdbc.sql @(resolve 'babashka.impl.jdbc/next-sql-namespace))
features/core-async? (assoc 'clojure.core.async @(resolve 'babashka.impl.async/async-namespace)
'clojure.core.async.impl.protocols @(resolve 'babashka.impl.async/async-protocols-namespace))
features/csv? (assoc 'clojure.data.csv @(resolve 'babashka.impl.csv/csv-namespace))
features/transit? (assoc 'cognitect.transit @(resolve 'babashka.impl.transit/transit-namespace))
features/datascript? (assoc 'datascript.core @(resolve 'babashka.impl.datascript/datascript-namespace))
features/httpkit-client? (assoc 'org.httpkit.client @(resolve 'babashka.impl.httpkit-client/httpkit-client-namespace)
'org.httpkit.sni-client @(resolve 'babashka.impl.httpkit-client/sni-client-namespace))
features/httpkit-server? (assoc 'org.httpkit.server @(resolve 'babashka.impl.httpkit-server/httpkit-server-namespace))
features/lanterna? (assoc 'lanterna.screen @(resolve 'babashka.impl.lanterna/lanterna-screen-namespace)
'lanterna.terminal @(resolve 'babashka.impl.lanterna/lanterna-terminal-namespace)
'lanterna.constants @(resolve 'babashka.impl.lanterna/lanterna-constants-namespace))
features/core-match? (assoc 'clojure.core.match @(resolve 'babashka.impl.match/core-match-namespace))
features/hiccup? (-> (assoc 'hiccup.core @(resolve 'babashka.impl.hiccup/hiccup-namespace))
(assoc 'hiccup2.core @(resolve 'babashka.impl.hiccup/hiccup2-namespace))
(assoc 'hiccup.util @(resolve 'babashka.impl.hiccup/hiccup-util-namespace))
(assoc 'hiccup.compiler @(resolve 'babashka.impl.hiccup/hiccup-compiler-namespace)))
;; ensure load before babashka.impl.clojure.spec.alpha for random patch!
features/test-check? (assoc 'clojure.test.check.random
@(resolve 'babashka.impl.clojure.test.check/random-namespace)
'clojure.test.check.generators
@(resolve 'babashka.impl.clojure.test.check/generators-namespace)
'clojure.test.check.rose-tree
@(resolve 'babashka.impl.clojure.test.check/rose-tree-namespace)
'clojure.test.check.properties
@(resolve 'babashka.impl.clojure.test.check/properties-namespace)
'clojure.test.check
@(resolve 'babashka.impl.clojure.test.check/test-check-namespace))
features/spec-alpha? (-> (assoc ;; spec
'clojure.spec.alpha @(resolve 'babashka.impl.spec/spec-namespace)
'clojure.spec.gen.alpha @(resolve 'babashka.impl.spec/gen-namespace)
'clojure.spec.test.alpha @(resolve 'babashka.impl.spec/test-namespace)))))
(def imports
'{ArithmeticException java.lang.ArithmeticException
AssertionError java.lang.AssertionError
BigDecimal java.math.BigDecimal
BigInteger java.math.BigInteger
Boolean java.lang.Boolean
Byte java.lang.Byte
Character java.lang.Character
Class java.lang.Class
ClassNotFoundException java.lang.ClassNotFoundException
Comparable java.lang.Comparable
Double java.lang.Double
Exception java.lang.Exception
IndexOutOfBoundsException java.lang.IndexOutOfBoundsException
IllegalArgumentException java.lang.IllegalArgumentException
Integer java.lang.Integer
Iterable java.lang.Iterable
File java.io.File
Float java.lang.Float
Long java.lang.Long
Math java.lang.Math
Number java.lang.Number
NumberFormatException java.lang.NumberFormatException
Object java.lang.Object
Runtime java.lang.Runtime
RuntimeException java.lang.RuntimeException
Process java.lang.Process
ProcessBuilder java.lang.ProcessBuilder
Short java.lang.Short
String java.lang.String
StringBuilder java.lang.StringBuilder
System java.lang.System
Thread java.lang.Thread
Throwable java.lang.Throwable})
(def input-var (sci/new-dynamic-var '*input* nil))
(def edn-readers (cond-> {}
features/yaml?
(assoc 'ordered/map @(resolve 'flatland.ordered.map/ordered-map))))
(defn edn-seq*
[^java.io.BufferedReader rdr]
(let [edn-val (edn/read {:eof ::EOF :readers edn-readers} rdr)]
(when (not (identical? ::EOF edn-val))
(cons edn-val (lazy-seq (edn-seq* rdr))))))
(defn edn-seq
[in]
(edn-seq* in))
(defn shell-seq [in]
(line-seq (java.io.BufferedReader. in)))
(declare resolve-task)
(defn error [msg exit]
(binding [*out* *err*]
(println msg)
{:exec (fn [] [nil exit])}))
(defn parse-opts [options]
(let [fst (when options (first options))
key? (when fst (str/starts-with? fst ":"))
keys (when key? (rest (str/split fst #":")))
expanded (when (and key? (> (count keys) 1))
(concat (cons ":do" (interpose ":and-do"
(map #(str ":" %)
keys)))
(rest options)))
k (when (and key? (not expanded))
(keyword (first keys)))
bb-edn (when k @bb-edn)
tasks (when (and k bb-edn)
(:tasks bb-edn))
user-task (when tasks (get tasks k))]
(cond user-task
(resolve-task user-task {:command-line-args (next options)})
expanded (parse-opts expanded)
:else
(let [opts (loop [options options
opts-map {}]
(if options
(let [opt (first options)]
(case opt
("--") (assoc opts-map :command-line-args (next options))
("--clojure" ":clojure") (assoc opts-map :clojure true
:command-line-args (rest options))
("--version" ":version") {:version true}
("--help" "-h" "-?" ":help") {:help true
:command-line-args (rest options)}
(":tasks") {:tasks tasks
:command-line-args (rest options)}
("--verbose")(recur (next options)
(assoc opts-map
:verbose? true))
("--describe" ":describe") (recur (next options)
(assoc opts-map
:describe? true))
("--stream") (recur (next options)
(assoc opts-map
:stream? true))
("-i") (recur (next options)
(assoc opts-map
:shell-in true))
("-I") (recur (next options)
(assoc opts-map
:edn-in true))
("-o") (recur (next options)
(assoc opts-map
:shell-out true))
("-O") (recur (next options)
(assoc opts-map
:edn-out true))
("-io") (recur (next options)
(assoc opts-map
:shell-in true
:shell-out true))
("-iO") (recur (next options)
(assoc opts-map
:shell-in true
:edn-out true))
("-Io") (recur (next options)
(assoc opts-map
:edn-in true
:shell-out true))
("-IO") (recur (next options)
(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" ":uberscript")
(let [options (next options)]
(recur (next options)
(assoc opts-map
:uberscript (first options))))
("--uberjar" ":uberjar")
(let [options (next options)]
(recur (next options)
(assoc opts-map
:uberjar (first options))))
("-f" "--file")
(let [options (next options)]
(recur (next options)
(assoc opts-map
:file (first options))))
("--jar" "-jar")
(let [options (next options)]
(recur (next options)
(assoc opts-map
:jar (first options))))
("--repl" ":repl")
(let [options (next options)]
(recur (next options)
(assoc opts-map
:repl true)))
("--socket-repl" ":socket-repl")
(let [options (next options)
opt (first options)
opt (when (and opt (not (str/starts-with? opt "-")))
opt)
options (if opt (next options)
options)]
(recur options
(assoc opts-map
:socket-repl (or opt "1666"))))
("--nrepl-server" ":nrepl-server")
(let [options (next options)
opt (first options)
opt (when (and opt (not (str/starts-with? opt "-")))
opt)
options (if opt (next options)
options)]
(recur options
(assoc opts-map
:nrepl (or opt "1667"))))
("--eval", "-e")
(let [options (next options)]
(recur (next options)
(update opts-map :expressions (fnil conj []) (first options))))
("--main", "-m", ":main")
(let [options (next options)]
(recur (next options)
(assoc opts-map :main (first options))))
(":do")
(let [options (next options)
options (into [] (comp (partition-by #(or
(= % ":do")
(= % ":and-do")
(= % ":or-do"))))
options)]
{:do options})
#_#_(":invoke")
{:exec-src
(pr-str '(if-let [f (requiring-resolve (symbol (first *command-line-args*)))]
(apply f (rest *command-line-args*))
(throw (Exception. (str "Var not found: " (first *command-line-args*)
" " (babashka.classpath/get-classpath))))))
:command-line-args (next options)}
;; fallback
(if (some opts-map [:file :jar :socket-repl :expressions :main])
(assoc opts-map
:command-line-args options)
(let [trimmed-opt (str/triml opt)
c (.charAt trimmed-opt 0)]
(case c
(\( \{ \[ \* \@ \#)
(-> opts-map
(update :expressions (fnil conj []) (first options))
(assoc :command-line-args (next options)))
(if (fs/exists? opt)
(assoc opts-map
(if (str/ends-with? opt ".jar")
:jar :file) opt
:command-line-args (next options))
(error (str (if (str/starts-with? opt ":")
"Task does not exist: "
"File does not exist: ") opt) 1)))))))
opts-map))]
opts))))
(defn resolve-task [task {:keys [:command-line-args]}]
(case (:task/type task)
:babashka
(let [cmd-line-args (get task :task/args)]
(parse-opts (seq (map str (concat cmd-line-args command-line-args)))))
:shell
(let [args (get task :task/args)
args (into (vec args) command-line-args)]
{:exec (fn []
[nil
(-> (p/process args {:inherit true})
deref
:exit)])})
:main
(let [main-arg (:main task)
cmd-line-args (:task/args task)]
(parse-opts (seq (map str (concat ["--main" main-arg] cmd-line-args command-line-args)))))
(error (str "No such task: " (:task/type task)) 1)))
(def should-load-inits?
"if true, then we should still load preloads and user.clj"
(volatile! true))
(def env (atom {}))
(defn exec [opts]
(binding [*unrestricted* true]
(sci/binding [core/warn-on-reflection @core/warn-on-reflection
core/data-readers @core/data-readers
sci/ns @sci/ns]
(let [{version-opt :version
:keys [:shell-in :edn-in :shell-out :edn-out
:help :file :command-line-args
:expressions :stream?
:repl :socket-repl :nrepl
:verbose? :classpath
:main :uberscript :describe?
:jar :uberjar :clojure
:exec-src :tasks]
exec-fn :exec}
opts
_ (when verbose? (vreset! common/verbose? true))
_ (do ;; set properties
(when main (System/setProperty "babashka.main" main))
(System/setProperty "babashka.version" version))
read-next (fn [*in*]
(if (pipe-signal-received?)
::EOF
(if stream?
(if shell-in (or (read-line) ::EOF)
(edn/read {:readers edn-readers
:eof ::EOF} *in*))
(delay (cond shell-in
(shell-seq *in*)
edn-in
(edn-seq *in*)
:else
(edn/read {:readers edn-readers} *in*))))))
uberscript-sources (atom ())
classpath (or classpath
(System/getenv "BABASHKA_CLASSPATH"))
_ (when classpath
(cp/add-classpath classpath))
abs-path (when file
(let [abs-path (.getAbsolutePath (io/file file))]
(vars/bindRoot sci/file abs-path)
(System/setProperty "babashka.file" abs-path)
abs-path))
_ (when jar
(cp/add-classpath jar))
load-fn (fn [{:keys [:namespace :reload]}]
(when-let [{:keys [:loader]}
@cp/cp-state]
(if ;; ignore built-in namespaces when uberscripting, unless with :reload
(and uberscript
(not reload)
(or (contains? namespaces namespace)
(contains? sci-namespaces/namespaces namespace)))
""
(let [res (cp/source-for-namespace loader namespace nil)]
(when uberscript (swap! uberscript-sources conj (:source res)))
res))))
main (if (and jar (not main))
(when-let [res (cp/getResource
(cp/loader jar)
["META-INF/MANIFEST.MF"] {:url? true})]
(cp/main-ns res))
main)
;; TODO: pull more of these values to compile time
opts {:aliases aliases
:namespaces (-> namespaces
(assoc 'clojure.core
(assoc core-extras
'load-file load-file*))
(assoc-in ['clojure.java.io 'resource]
(fn [path]
(when-let [{:keys [:loader]} @cp/cp-state]
(if (str/starts-with? path "/") nil ;; non-relative paths always return nil
(cp/getResource loader [path] {:url? true})))))
(assoc-in ['user (with-meta '*input*
(when-not stream?
{:sci.impl/deref! true}))] input-var))
:env env
:features #{:bb :clj}
:classes classes/class-map
:imports imports
:load-fn load-fn
:uberscript uberscript
:readers core/data-readers
:reify-fn reify-fn
:proxy-fn proxy-fn}
opts (addons/future opts)
sci-ctx (sci/init opts)
_ (vreset! common/ctx sci-ctx)
preloads (when @should-load-inits? (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim)))
[expressions exit-code]
(cond expressions [expressions nil]
main
(let [sym (symbol main)
ns? (namespace sym)
ns (or ns? sym)
var-name (if ns?
(name sym)
"-main")]
[[(format "(ns user (:require [%1$s])) (apply %1$s/%2$s *command-line-args*)"
ns var-name)] nil])
file (try [[(read-file file)] nil]
(catch Exception e
(error-handler e {:expression expressions
:verbose? verbose?
:preloads preloads
:loader (:loader @cp/cp-state)}))))
expression (str/join " " expressions) ;; this might mess with the locations...
exit-code
;; handle preloads
(if exit-code exit-code
(do (when @should-load-inits?
(when preloads
(sci/binding [sci/file "<preloads>"]
(try
(sci/eval-string* sci-ctx preloads)
(catch Throwable e
(error-handler e {:expression expression
:verbose? verbose?
:preloads preloads
:loader (:loader @cp/cp-state)})))))
(when @cp/cp-state
(when-let [{:keys [:file :source]}
(cp/source-for-namespace (:loader @cp/cp-state) "user" nil)]
(sci/binding [sci/file file]
(try
(sci/eval-string* sci-ctx source)
(catch Throwable e
(error-handler e {:expression expression
:verbose? verbose?
:preloads preloads
:loader (:loader @cp/cp-state)}))))))
(vreset! should-load-inits? false))
nil))
;; socket REPL is start asynchronously. when no other args are
;; provided, a normal REPL will be started as well, which causes the
;; process to wait until SIGINT
_ (when socket-repl (start-socket-repl! socket-repl sci-ctx))
exit-code
(or exit-code
(second
(cond version-opt
[(print-version) 0]
help (print-help sci-ctx command-line-args)
tasks (print-tasks tasks)
describe?
[(print-describe) 0]
repl [(repl/start-repl! sci-ctx) 0]
nrepl [(start-nrepl! nrepl sci-ctx) 0]
uberjar [nil 0]
expressions
(sci/binding [sci/file abs-path]
(try
(loop []
(let [in (read-next *in*)]
(if (identical? ::EOF in)
[nil 0] ;; done streaming
(let [res [(let [res
(sci/binding [sci/file (or @sci/file "<expr>")
input-var in
core/command-line-args command-line-args]
(sci/eval-string* sci-ctx expression))]
(when (some? res)
(if-let [pr-f (cond shell-out println
edn-out prn)]
(if (coll? res)
(doseq [l res
:while (not (pipe-signal-received?))]
(pr-f l))
(pr-f res))
(prn res)))) 0]]
(if stream?
(recur)
res)))))
(catch Throwable e
(error-handler e {:expression expression
:verbose? verbose?
:preloads preloads
:loader (:loader @cp/cp-state)}))))
exec-fn (exec-fn)
exec-src [(let [res (sci/binding [sci/file (or @sci/file "<task>")
core/command-line-args command-line-args]
(sci/eval-string* sci-ctx exec-src))]
(when (some? res)
(prn res)))
0]
clojure [nil (if-let [proc (deps/clojure command-line-args)]
(-> @proc :exit)
0)]
uberscript [nil 0]
:else [(repl/start-repl! sci-ctx) 0]))
1)]
(flush)
(when uberscript
(let [uberscript-out uberscript]
(spit uberscript-out "") ;; reset file
(doseq [s (distinct @uberscript-sources)]
(spit uberscript-out s :append true))
(spit uberscript-out preloads :append true)
(spit uberscript-out expression :append true)))
(when uberjar
(uberjar/run {:dest uberjar
:jar :uber
:classpath classpath
:main-class main
:verbose verbose?}))
exit-code))))
(defn main [& args]
(let [bb-edn-file (or (System/getenv "BABASHKA_EDN")
"bb.edn")]
(when (fs/exists? bb-edn-file)
(let [edn (edn/read-string (slurp bb-edn-file))]
(reset! bb-edn edn)))
;; we mutate the atom from tests as well, so despite the above it can contain a bb.edn
(when-let [bb-edn @bb-edn] (deps/add-deps bb-edn)))
(let [opts (parse-opts args)]
(if-let [do-opts (:do opts)]
(reduce (fn [prev-exit opts]
;; (prn :prev prev-exit)
;; (prn :opts opts)
(if (pos? prev-exit)
(case opts
([":do"] [":or-do"]) 0 ;; skipping, returning 0
(reduced prev-exit))
(case opts
[":or-do"] (reduced prev-exit) ;; short-cutting
([":do"] [":and-do"]) 0 ;; skipping, returning 0
(exec (parse-opts opts)))))
0
do-opts)
(exec opts))))
(defn -main
[& args]
(handle-pipe!)
(handle-sigint!)
(if-let [dev-opts (System/getenv "BABASHKA_DEV")]
(let [{:keys [:n]} (if (= "true" dev-opts) {:n 1}
(edn/read-string dev-opts))
last-iteration (dec n)]
(dotimes [i n]
(if (< i last-iteration)
(with-out-str (apply main args))
(do (apply main args)
(binding [*out* *err*]
(println "ran" n "times"))))))
(let [exit-code (apply main args)]
(System/exit exit-code))))
;;;; Scratch
(comment)