This commit is contained in:
Michiel Borkent 2021-03-27 14:28:19 +01:00
parent 235adfcfe5
commit f02bf19e80
4 changed files with 256 additions and 235 deletions

View file

@ -102,96 +102,105 @@
(binding [*out* *err*] (binding [*out* *err*]
(apply println msgs))) (apply println msgs)))
(defn print-help [ctx command-line-args] (defn print-help [_ctx _command-line-args]
(if (empty? command-line-args) (println (str "Babashka v" version))
(do ;; (println (str "sci v" (str/trim (slurp (io/resource "SCI_VERSION")))))
(println (str "Babashka v" version)) (println)
;; (println (str "sci v" (str/trim (slurp (io/resource "SCI_VERSION"))))) (println "Options must appear in the order of groups mentioned below.")
(println) (println "
(println "Options must appear in the order of groups mentioned below.")
(println "
Help: Help:
--help, -h or -? Print this help text. help, -h or -? Print this help text.
--version Print the current version of babashka. version, Print the current version of babashka.
--describe Print an EDN map with information about this version of babashka. describe Print an EDN map with information about this version of babashka.
tasks List tasks.
doc <var> Print docstring of var or task.
Evaluation: Evaluation:
-e, --eval <expr> Evaluate an expression. -e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file. -f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use. -cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args. -m, --main <ns> Call the -main function from namespace with args.
--verbose Print debug information and entire stacktrace in case of exception. --verbose Print debug information and entire stacktrace in case of exception.
REPL: REPL:
--repl Start REPL. Use rlwrap for history. 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). 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). 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: In- and output flags:
-i Bind *input* to a lazy seq of lines from stdin. -i Bind *input* to a lazy seq of lines from stdin.
-I Bind *input* to a lazy seq of EDN values from stdin. -I Bind *input* to a lazy seq of EDN values from stdin.
-o Write lines to stdout. -o Write lines to stdout.
-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 *input* becomes a single value per iteration. --stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
Uberscript: Uberscript:
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single file. uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single file.
Uberjar: Uberjar:
--uberjar <jar> Similar to --uberscript but creates jar file. uberjar <jar> Similar to --uberscript but creates jar file.
Clojure: Clojure:
--clojure [args...] Invokes clojure. Takes same args as the official clojure CLI. 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. 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*. Everything after that is bound to *command-line-args*.
Use -- to separate script command line args from bb command line args. Use -- to separate script command line args from bb command line args.
") ")
[nil 0]) ;; end do [nil 0])
(let [k (first command-line-args)
k (keyword (subs k 1))] (defn print-doc [ctx command-line-args]
(if-let [task (get-in @bb-edn [:tasks k])] (let [arg (first command-line-args)]
(let [{:keys [:args] (if (str/starts-with? arg ":")
task-key :task} (decode-task task)] (let [k (keyword (subs arg 1))]
(if-let [help-text (:help task)] (if-let [task (get-in @bb-edn [:tasks k])]
[(println help-text) 0] (let [{:keys [:args]
(if-let [main (when (= :main task-key) task-key :task} (decode-task task)]
(first args))] (if-let [help-text (:doc task)]
(let [main (if (simple-symbol? main) [(println help-text) 0]
(symbol (str main) "-main") (if-let [main (when (= :main task-key)
main)] (first args))]
(if-let [doc (sci/eval-string* ctx (format "(some-> (requiring-resolve '%s) meta :doc)" main))] (let [main (if (simple-symbol? main)
[(println doc) 0] (symbol (str main) "-main")
[(print-error "No help found for task:" k) 1])) main)]
[(print-error "No help found for task:" k) 1]))) (if-let [doc (sci/eval-string* ctx (format "(some-> (requiring-resolve '%s) meta :doc)" main))]
[(print-error "Task does not exist:" k) 1]) [(println doc) 0]
,)) ;; end if [(print-error "No docstring found for task:" k) 1]))
,) ;; end defn [(print-error "No docstring found for task:" k) 1])))
[(print-error "Task does not exist:" k) 1]))
(let [arg (if (str/includes? arg "/") arg (str "clojure.core/" arg))]
(if-let [doc (sci/eval-string* ctx (format "(some-> (requiring-resolve '%s) meta :doc)" arg))]
[(println doc) 0]
[(print-error "No docstring found for var:" arg) 1]))))
,)
(defn print-tasks [tasks] (defn print-tasks [tasks]
(let [tasks (into (sorted-map) tasks) (if (seq tasks)
ks (keys tasks) (let [tasks (into (sorted-map) tasks)
longest (apply max ks (keys tasks)
(map (comp count str) longest (apply max
ks)) (map (comp count str)
fmt (str "%1$-" longest "s")] ks))
(println "The following tasks are available:") fmt (str "%1$-" longest "s")]
(println) (println "The following tasks are available:")
(doseq [[k v] tasks] (println)
(println (str (format fmt k) (doseq [[k v] tasks]
(when-let [d (:description v)] (println (str (format fmt k)
(str " " d))))) (when-let [d (:description v)]
(println) (str " " d)))))
(println "Run bb :help <task> to view help of a specific task.") (println)
[nil 0])) (println "Run bb :help <task> to view help of a specific task.")
[nil 0])
(do (println "No tasks found.")
[nil 0])))
(defn print-describe [] (defn print-describe []
(println (println
@ -421,6 +430,21 @@ Use -- to separate script command line args from bb command line args.
(println msg) (println msg)
{:exec (fn [] [nil exit])})) {:exec (fn [] [nil exit])}))
(defn command? [x]
(case x
("clojure"
"version"
"help"
"doc"
"tasks"
"uberjar"
"uberscript"
"repl"
"socket-repl"
"nrepl-server"
"describe") true
false))
(defn parse-opts [options] (defn parse-opts [options]
(let [fst (when options (first options)) (let [fst (when options (first options))
key? (when fst (str/starts-with? fst ":")) key? (when fst (str/starts-with? fst ":"))
@ -429,158 +453,151 @@ Use -- to separate script command line args from bb command line args.
(into [:do] (map (comp vector keyword) keys))) (into [:do] (map (comp vector keyword) keys)))
k (when (and key? (not expanded)) k (when (and key? (not expanded))
(keyword (first keys))) (keyword (first keys)))
task? (or expanded k) bb-edn @bb-edn
bb-edn (when task? @bb-edn) tasks (when bb-edn
tasks (when (and task? bb-edn)
(:tasks bb-edn)) (:tasks bb-edn))
user-task (when tasks (get tasks k))] user-task (when (and tasks k)
(get tasks k))
opt (first options)]
(cond user-task (cond user-task
(resolve-task tasks user-task {:command-line-args (next options)}) (resolve-task tasks user-task {:command-line-args (next options)})
expanded (resolve-task tasks expanded nil) expanded (resolve-task tasks expanded nil)
:else (and (command? opt)
(let [opts (loop [options options (not (fs/regular-file? opt)))
opts-map {}] (recur (cons (str "--" opt) (next options)))
(if options :else
(let [opt (first options)] (let [opts (loop [options options
(case opt opts-map {}]
("--") (assoc opts-map :command-line-args (next options)) (if options
("--clojure" ":clojure") (assoc opts-map :clojure true (let [opt (first options)]
:command-line-args (rest options)) (case opt
("--version" ":version") {:version true} ("--") (assoc opts-map :command-line-args (next options))
("--help" "-h" "-?" ":help") {:help true ("--clojure") (assoc opts-map :clojure true
:command-line-args (rest options)} :command-line-args (rest options))
(":tasks") {:tasks tasks ("--version") {:version true}
:command-line-args (rest options)} ("--help" "-h" "-?" "help")
("--verbose")(recur (next options) {:help true
(assoc opts-map :command-line-args (rest options)}
:verbose? true)) ("--doc")
("--describe" ":describe") (recur (next options) {:doc true
(assoc opts-map :command-line-args (rest options)}
:describe? true)) ("--tasks") {:tasks (or tasks {})
("--stream") (recur (next options) :command-line-args (rest options)}
(assoc opts-map ("--verbose") (recur (next options)
:stream? true)) (assoc opts-map
("-i") (recur (next options) :verbose? true))
(assoc opts-map ("--describe") (recur (next options)
:shell-in true)) (assoc opts-map
("-I") (recur (next options) :describe? true))
(assoc opts-map ("--stream") (recur (next options)
:edn-in true)) (assoc opts-map
("-o") (recur (next options) :stream? true))
(assoc opts-map ("-i") (recur (next options)
:shell-out true)) (assoc opts-map
("-O") (recur (next options) :shell-in true))
(assoc opts-map ("-I") (recur (next options)
:edn-out true)) (assoc opts-map
("-io") (recur (next options) :edn-in true))
(assoc opts-map ("-o") (recur (next options)
:shell-in true (assoc opts-map
:shell-out true)) :shell-out true))
("-iO") (recur (next options) ("-O") (recur (next options)
(assoc opts-map (assoc opts-map
:shell-in true :edn-out true))
:edn-out true)) ("-io") (recur (next options)
("-Io") (recur (next options) (assoc opts-map
(assoc opts-map :shell-in true
:edn-in true :shell-out true))
:shell-out true)) ("-iO") (recur (next options)
("-IO") (recur (next options) (assoc opts-map
(assoc opts-map :shell-in true
:edn-in true :edn-out true))
:edn-out true)) ("-Io") (recur (next options)
("--classpath", "-cp") (assoc opts-map
(let [options (next options)] :edn-in true
(recur (next options) :shell-out true))
(assoc opts-map :classpath (first options)))) ("-IO") (recur (next options)
("--uberscript" ":uberscript") (assoc opts-map
(let [options (next options)] :edn-in true
(recur (next options) :edn-out true))
(assoc opts-map ("--classpath", "-cp")
:uberscript (first options)))) (let [options (next options)]
("--uberjar" ":uberjar") (recur (next options)
(let [options (next options)] (assoc opts-map :classpath (first options))))
(recur (next options) ("--uberscript")
(assoc opts-map (let [options (next options)]
:uberjar (first options)))) (recur (next options)
("-f" "--file") (assoc opts-map
(let [options (next options)] :uberscript (first options))))
(recur (next options) ("--uberjar")
(assoc opts-map (let [options (next options)]
:file (first options)))) (recur (next options)
("--jar" "-jar") (assoc opts-map
(let [options (next options)] :uberjar (first options))))
(recur (next options) ("-f" "--file")
(assoc opts-map (let [options (next options)]
:jar (first options)))) (recur (next options)
("--repl" ":repl") (assoc opts-map
(let [options (next options)] :file (first options))))
(recur (next options) ("--jar" "-jar")
(assoc opts-map (let [options (next options)]
:repl true))) (recur (next options)
("--socket-repl" ":socket-repl") (assoc opts-map
(let [options (next options) :jar (first options))))
opt (first options) ("--repl")
opt (when (and opt (not (str/starts-with? opt "-"))) (let [options (next options)]
opt) (recur (next options)
options (if opt (next options) (assoc opts-map
options)] :repl true)))
(recur options ("--socket-repl")
(assoc opts-map (let [options (next options)
:socket-repl (or opt "1666")))) opt (first options)
("--nrepl-server" ":nrepl-server") opt (when (and opt (not (str/starts-with? opt "-")))
(let [options (next options) opt)
opt (first options) options (if opt (next options)
opt (when (and opt (not (str/starts-with? opt "-"))) options)]
opt) (recur options
options (if opt (next options) (assoc opts-map
options)] :socket-repl (or opt "1666"))))
(recur options ("--nrepl-server")
(assoc opts-map (let [options (next options)
:nrepl (or opt "1667")))) opt (first options)
("--eval", "-e") opt (when (and opt (not (str/starts-with? opt "-")))
(let [options (next options)] opt)
(recur (next options) options (if opt (next options)
(update opts-map :expressions (fnil conj []) (first options)))) options)]
("--main", "-m", ":main") (recur options
(let [options (next options)] (assoc opts-map
(recur (next options) :nrepl (or opt "1667"))))
(assoc opts-map :main (first options)))) ("--eval", "-e")
#_#_(":do") (let [options (next options)]
(let [options (next options) (recur (next options)
options (into [] (comp (partition-by #(or (update opts-map :expressions (fnil conj []) (first options))))
(= % ":do") ("--main", "-m",)
(= % ":and-do") (let [options (next options)]
(= % ":or-do")))) (recur (next options)
options)] (assoc opts-map :main (first options))))
{:do options}) ;; fallback
#_#_(":invoke") (if (some opts-map [:file :jar :socket-repl :expressions :main])
{:exec-src (assoc opts-map
(pr-str '(if-let [f (requiring-resolve (symbol (first *command-line-args*)))] :command-line-args options)
(apply f (rest *command-line-args*)) (let [trimmed-opt (str/triml opt)
(throw (Exception. (str "Var not found: " (first *command-line-args*) c (.charAt trimmed-opt 0)]
" " (babashka.classpath/get-classpath)))))) (case c
:command-line-args (next options)} (\( \{ \[ \* \@ \#)
;; fallback (-> opts-map
(if (some opts-map [:file :jar :socket-repl :expressions :main]) (update :expressions (fnil conj []) (first options))
(assoc opts-map (assoc :command-line-args (next options)))
:command-line-args options) (if (fs/exists? opt)
(let [trimmed-opt (str/triml opt) (assoc opts-map
c (.charAt trimmed-opt 0)] (if (str/ends-with? opt ".jar")
(case c :jar :file) opt
(\( \{ \[ \* \@ \#) :command-line-args (next options))
(-> opts-map (error (str (if (str/starts-with? opt ":")
(update :expressions (fnil conj []) (first options)) "Task does not exist: "
(assoc :command-line-args (next options))) "File does not exist: ") opt) 1)))))))
(if (fs/exists? opt) opts-map))]
(assoc opts-map opts))))
(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 [tasks task {:keys [:command-line-args]}] (defn resolve-task [tasks task {:keys [:command-line-args]}]
(let [{:keys [:task :opts :args]} (decode-task task)] (let [{:keys [:task :opts :args]} (decode-task task)]
@ -630,7 +647,7 @@ Use -- to separate script command line args from bb command line args.
:verbose? :classpath :verbose? :classpath
:main :uberscript :describe? :main :uberscript :describe?
:jar :uberjar :clojure :jar :uberjar :clojure
:exec-src :tasks] :exec-src :tasks :doc]
exec-fn :exec} exec-fn :exec}
opts opts
_ (when verbose? (vreset! common/verbose? true)) _ (when verbose? (vreset! common/verbose? true))
@ -762,6 +779,7 @@ Use -- to separate script command line args from bb command line args.
[(print-version) 0] [(print-version) 0]
help (print-help sci-ctx command-line-args) help (print-help sci-ctx command-line-args)
tasks (print-tasks tasks) tasks (print-tasks tasks)
doc (print-doc sci-ctx command-line-args)
describe? describe?
[(print-describe) 0] [(print-describe) 0]
repl [(repl/start-repl! sci-ctx) 0] repl [(repl/start-repl! sci-ctx) 0]

View file

@ -29,7 +29,7 @@
(with-config {} (with-config {}
(is (thrown-with-msg? (is (thrown-with-msg?
Exception #"Task does not exist: :sum" Exception #"Task does not exist: :sum"
(bb :help :sum))))) (bb "doc" :sum)))))
(deftest babashka-task-test (deftest babashka-task-test
(with-config {:tasks {:sum [:babashka "-e" "(+ 1 2 3)"]}} (with-config {:tasks {:sum [:babashka "-e" "(+ 1 2 3)"]}}
@ -100,38 +100,40 @@
(is (= 6 (bb :all)))))) (is (= 6 (bb :all))))))
(deftest prioritize-user-task-test (deftest prioritize-user-task-test
(is (map? (bb :describe))) (is (map? (bb "describe")))
(with-config {:tasks {:describe [:babashka "-e" "(+ 1 2 3)"]}} (with-config {:tasks {:describe [:babashka "-e" "(+ 1 2 3)"]}}
(is (= 6 (bb :describe))))) (is (= 6 (bb :describe)))))
(deftest help-task-test (deftest doc-task-test
(with-config {:tasks {:cool-task (with-config {:tasks {:cool-task
{:help "Usage: bb :cool-task {:doc "Usage: bb :cool-task
Addition is a pretty advanced topic. Let us start with the identity element Addition is a pretty advanced topic. Let us start with the identity element
0. ..." 0. ..."
:task [:babashka "-e" "(+ 1 2 3)"]}}} :task [:babashka "-e" "(+ 1 2 3)"]}}}
(is (str/includes? (apply test-utils/bb nil (is (str/includes? (apply test-utils/bb nil
(map str [:help :cool-task])) (map str ["doc" :cool-task]))
"Usage: bb :cool-task")))) "Usage: bb :cool-task"))))
(deftest list-tasks-test (deftest list-tasks-test
(with-config {}
(let [res (test-utils/bb nil "tasks")]
(is (str/includes? res "No tasks found."))))
(with-config {:tasks {:task-1 (with-config {:tasks {:task-1
{:description "Return the sum of 1, 2 and 3." {:description "Return the sum of 1, 2 and 3."
:help "Usage: bb :cool-task :doc "Usage: bb :cool-task
Addition is a pretty advanced topic. Let us start with the identity element Addition is a pretty advanced topic. Let us start with the identity element
0. ..."} 0. ..."}
:task [:babashka "-e" "(+ 1 2 3)"] :task [:babashka "-e" "(+ 1 2 3)"]
:cool-task-2 :cool-task-2
{:description "Return the sum of 4, 5 and 6." {:description "Return the sum of 4, 5 and 6."
:help "Usage: bb :cool-task :doc "Usage: bb :cool-task
Addition is a pretty advanced topic. Let us start with the identity element Addition is a pretty advanced topic. Let us start with the identity element
0. ..." 0. ..."
:task [:babashka "-e" "(+ 4 5 6)"]}}} :task [:babashka "-e" "(+ 4 5 6)"]}}}
(let [res (apply test-utils/bb nil (let [res (test-utils/bb nil "tasks")]
(map str [:tasks]))]
(is (str/includes? res "The following tasks are available:")) (is (str/includes? res "The following tasks are available:"))
(is (str/includes? res ":task-1 Return the")) (is (str/includes? res ":task-1 Return the"))
(is (str/includes? res ":cool-task-2 Return the"))))) (is (str/includes? res ":cool-task-2 Return the")))))
@ -141,5 +143,5 @@ Addition is a pretty advanced topic. Let us start with the identity element
:tasks {:main-task [:main 'tasks 1 2 3]}} :tasks {:main-task [:main 'tasks 1 2 3]}}
(is (= '("1" "2" "3") (bb :main-task))) (is (= '("1" "2" "3") (bb :main-task)))
(let [res (apply test-utils/bb nil (let [res (apply test-utils/bb nil
(map str [:help :main-task]))] (map str ["doc" :main-task]))]
(is (str/includes? res "Usage: just pass some args"))))) (is (str/includes? res "Usage: just pass some args")))))

View file

@ -7,21 +7,10 @@
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.java.shell :refer [sh]] [clojure.java.shell :refer [sh]]
[clojure.string :as str] [clojure.string :as str]
[clojure.test :as test :refer [deftest is testing *report-counters*]] [clojure.test :as test :refer [deftest is testing]]
[flatland.ordered.map :refer [ordered-map]] [flatland.ordered.map :refer [ordered-map]]
[sci.core :as sci])) [sci.core :as sci]))
(defmethod clojure.test/report :begin-test-var [m]
(println "===" (-> m :var meta :name))
(println))
(defmethod clojure.test/report :end-test-var [_m]
(let [{:keys [:fail :error]} @*report-counters*]
(when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST"))
(or (pos? fail) (pos? error)))
(println "=== Failing fast")
(System/exit 1))))
(defn bb [input & args] (defn bb [input & args]
(edn/read-string (edn/read-string
{:readers *data-readers* {:readers *data-readers*

View file

@ -4,6 +4,7 @@
[babashka.main :as main] [babashka.main :as main]
[babashka.process :as p] [babashka.process :as p]
[clojure.edn :as edn] [clojure.edn :as edn]
[clojure.test :as test :refer [*report-counters*]]
[sci.core :as sci] [sci.core :as sci]
[sci.impl.vars :as vars])) [sci.impl.vars :as vars]))
@ -11,6 +12,17 @@
(def ^:dynamic *bb-edn-path* nil) (def ^:dynamic *bb-edn-path* nil)
(defmethod clojure.test/report :begin-test-var [m]
(println "===" (-> m :var meta :name))
(println))
(defmethod clojure.test/report :end-test-var [_m]
(let [{:keys [:fail :error]} @*report-counters*]
(when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST"))
(or (pos? fail) (pos? error)))
(println "=== Failing fast")
(System/exit 1))))
(defn bb-jvm [input-or-opts & args] (defn bb-jvm [input-or-opts & args]
(reset! cp/cp-state nil) (reset! cp/cp-state nil)
(reset! main/env {}) (reset! main/env {})