scripting
This commit is contained in:
parent
d6826593ad
commit
5b414072f9
5 changed files with 126 additions and 83 deletions
46
README.md
46
README.md
|
|
@ -71,18 +71,16 @@ bb [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ -f <file> ] [ expression ]
|
||||||
|
|
||||||
Type `bb --help` to see a full explanation of the options.
|
Type `bb --help` to see a full explanation of the options.
|
||||||
|
|
||||||
There is one special variable, `*in*`, which is the input read from stdin. The
|
The input is read as EDN by default. If the `-i` flag is provided, then the
|
||||||
input is read as EDN by default. If the `-i` flag is provided, then the input is
|
input is read as a string which is then split on newlines. The output is printed
|
||||||
read as a string which is then split on newlines. The output is printed as EDN
|
as EDN by default, unless the `-o` flag is provided, then the output is turned
|
||||||
by default, unless the `-o` flag is provided, then the output is turned into
|
into shell-scripting friendly output. To combine `-i` and `-o` you can use
|
||||||
shell-scripting friendly output. To combine `-i` and `-o` you can use `-io`.
|
`-io`.
|
||||||
|
|
||||||
The current version can be printed with `bb --version`.
|
The current version can be printed with `bb --version`.
|
||||||
|
|
||||||
Currently only the following special forms/macros are supported: anonymous
|
Babashka supports a subset of Clojure which is interpreted by
|
||||||
function literals like `#(%1 %2)`, `quote`, `do`,`if`, `when`, `let`, `and`,
|
[sci](https://github.com/borkdude/sci).
|
||||||
`or`, `->`, `->>`, `as->`. Anonymous functions literals are allowed with
|
|
||||||
currently up to three positional arguments.
|
|
||||||
|
|
||||||
The `clojure.core` functions are accessible without a namespace alias.
|
The `clojure.core` functions are accessible without a namespace alias.
|
||||||
|
|
||||||
|
|
@ -96,7 +94,12 @@ through the aliases:
|
||||||
|
|
||||||
From Java the following is available:
|
From Java the following is available:
|
||||||
|
|
||||||
- `System`: `getProperty`, `getProperties`, `getenv`
|
- `System`: `exit`, `getProperty`, `getProperties`, `getenv`
|
||||||
|
|
||||||
|
Special vars:
|
||||||
|
|
||||||
|
- `*in*`: contains the input read from stdin
|
||||||
|
- `*command-line-args*`: contain the command line args
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
|
|
@ -143,13 +146,26 @@ bb -f script.clj
|
||||||
Using `bb` with a shebang also works:
|
Using `bb` with a shebang also works:
|
||||||
|
|
||||||
``` shellsession
|
``` shellsession
|
||||||
$ cat script.clj
|
#!/usr/bin/env bb -io -f
|
||||||
#!/usr/bin/env bb -f
|
|
||||||
|
|
||||||
(+ 1 2 3)
|
(defn get-url [url]
|
||||||
|
(println "Fetching url:" url)
|
||||||
|
(let [{:keys [:exit :err :out]} (shell/sh "curl" "-sS" url)]
|
||||||
|
(if (zero? exit) out
|
||||||
|
(do (println "ERROR:" err)
|
||||||
|
(System/exit 1)))))
|
||||||
|
|
||||||
$ ./script.clj
|
(defn write-html [file html]
|
||||||
6
|
(println "Writing html to" file)
|
||||||
|
(spit file html))
|
||||||
|
|
||||||
|
(let [[url file] *command-line-args*]
|
||||||
|
(when (or (empty? url) (empty? file))
|
||||||
|
(println "Usage: <url> <file>")
|
||||||
|
(System/exit 1))
|
||||||
|
(write-html file (get-url url)))
|
||||||
|
|
||||||
|
(System/exit 0)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
:url "http://opensource.org/licenses/eclipse-1.0.php"}
|
:url "http://opensource.org/licenses/eclipse-1.0.php"}
|
||||||
:source-paths ["src"]
|
:source-paths ["src"]
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[borkdude/sci "0.0.4"]]
|
[borkdude/sci "0.0.7-SNAPSHOT"]]
|
||||||
:profiles {:clojure-1.9.0 {:dependencies [[org.clojure/clojure "1.9.0"]]}
|
:profiles {:clojure-1.9.0 {:dependencies [[org.clojure/clojure "1.9.0"]]}
|
||||||
:clojure-1.10.1 {:dependencies [[org.clojure/clojure "1.10.1"]]}
|
:clojure-1.10.1 {:dependencies [[org.clojure/clojure "1.10.1"]]}
|
||||||
:test {:dependencies [[clj-commons/conch "0.9.2"]]}
|
:test {:dependencies [[clj-commons/conch "0.9.2"]]}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.java.shell :as shell]
|
[clojure.java.shell :as shell]
|
||||||
[clojure.string :as str :refer [starts-with?]]
|
[clojure.string :as str]
|
||||||
[sci.core :as sci])
|
[sci.core :as sci])
|
||||||
(:gen-class))
|
(:gen-class))
|
||||||
|
|
||||||
|
|
@ -20,33 +20,34 @@
|
||||||
|
|
||||||
(defn- parse-opts [options]
|
(defn- parse-opts [options]
|
||||||
(let [opts (loop [options options
|
(let [opts (loop [options options
|
||||||
opts-map {}
|
opts-map {}]
|
||||||
current-opt nil]
|
|
||||||
(if-let [opt (first options)]
|
(if-let [opt (first options)]
|
||||||
(if (starts-with? opt "-")
|
(case opt
|
||||||
|
("--version") {:version true}
|
||||||
|
("--help") {:help? true}
|
||||||
|
("-i") (recur (rest options)
|
||||||
|
(assoc opts-map
|
||||||
|
:raw-in true))
|
||||||
|
("-o") (recur (rest options)
|
||||||
|
(assoc opts-map
|
||||||
|
:raw-out true))
|
||||||
|
("-io") (recur (rest options)
|
||||||
|
(assoc opts-map
|
||||||
|
:raw-in true
|
||||||
|
:raw-out true))
|
||||||
|
("-f" "--file")
|
||||||
|
(let [options (rest options)]
|
||||||
(recur (rest options)
|
(recur (rest options)
|
||||||
(assoc opts-map opt [])
|
(assoc opts-map
|
||||||
opt)
|
:file (first options))))
|
||||||
(recur (rest options)
|
(if (not (:file opts-map))
|
||||||
(update opts-map current-opt conj opt)
|
(assoc opts-map
|
||||||
current-opt))
|
:expression opt
|
||||||
opts-map))
|
:command-line-args (rest options))
|
||||||
version (boolean (get opts "--version"))
|
(assoc opts-map
|
||||||
raw-in (boolean (or (get opts "--raw")
|
:command-line-args options)))
|
||||||
(get opts "-i")
|
opts-map))]
|
||||||
(get opts "-io")))
|
opts))
|
||||||
raw-out (boolean (or (get opts "-o")
|
|
||||||
(get opts "-io")))
|
|
||||||
println? (boolean (get opts "--println"))
|
|
||||||
help? (boolean (get opts "--help"))
|
|
||||||
file (first (or (get opts "-f")
|
|
||||||
(get opts "--file")))]
|
|
||||||
{:version version
|
|
||||||
:raw-in raw-in
|
|
||||||
:raw-out raw-out
|
|
||||||
:println? println?
|
|
||||||
:help? help?
|
|
||||||
:file file}))
|
|
||||||
|
|
||||||
(defn parse-shell-string [s]
|
(defn parse-shell-string [s]
|
||||||
(str/split s #"\n"))
|
(str/split s #"\n"))
|
||||||
|
|
@ -54,7 +55,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: [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ expression ]")
|
(def usage-string "Usage: [ --help ] | [ --version ] | [ -i ] [ -o ] [ -io ] ( expression | -f <fie> )")
|
||||||
(defn print-usage []
|
(defn print-usage []
|
||||||
(println usage-string))
|
(println usage-string))
|
||||||
|
|
||||||
|
|
@ -76,10 +77,13 @@
|
||||||
"))
|
"))
|
||||||
|
|
||||||
(defn read-file [file]
|
(defn read-file [file]
|
||||||
|
(let [f (io/file file)]
|
||||||
|
(if (.exists f)
|
||||||
(as-> (slurp file) x
|
(as-> (slurp file) x
|
||||||
;; remove hashbang
|
;; remove hashbang
|
||||||
(str/replace x #"^#!.*" "")
|
(str/replace x #"^#!.*" "")
|
||||||
(format "(do %s)" x)))
|
(format "(do %s)" x))
|
||||||
|
(throw (Exception. (str "File does not exist: " file))))))
|
||||||
|
|
||||||
(defn get-env
|
(defn get-env
|
||||||
([] (System/getenv))
|
([] (System/getenv))
|
||||||
|
|
@ -94,10 +98,15 @@
|
||||||
(defn get-properties []
|
(defn get-properties []
|
||||||
(System/getProperties))
|
(System/getProperties))
|
||||||
|
|
||||||
|
(defn exit [n]
|
||||||
|
(System/exit n))
|
||||||
|
|
||||||
(def bindings
|
(def bindings
|
||||||
{'run! run!
|
{'run! run!
|
||||||
'shell/sh shell/sh
|
'shell/sh shell/sh
|
||||||
'csh shell/sh ;; backwards compatibility, deprecated
|
'csh shell/sh ;; backwards compatibility, deprecated
|
||||||
|
'slurp slurp
|
||||||
|
'spit spit
|
||||||
'pmap pmap
|
'pmap pmap
|
||||||
'print print
|
'print print
|
||||||
'pr-str pr-str
|
'pr-str pr-str
|
||||||
|
|
@ -106,13 +115,19 @@
|
||||||
'edn/read-string edn/read-string
|
'edn/read-string edn/read-string
|
||||||
'System/getenv get-env
|
'System/getenv get-env
|
||||||
'System/getProperty get-property
|
'System/getProperty get-property
|
||||||
'System/getProperties get-properties})
|
'System/getProperties get-properties
|
||||||
|
'System/exit exit})
|
||||||
|
|
||||||
(defn main
|
(defn main
|
||||||
[& args]
|
[& args]
|
||||||
|
#_(binding [*out* *err*]
|
||||||
|
(prn ">> args" args))
|
||||||
(or
|
(or
|
||||||
(let [{:keys [:version :raw-in :raw-out :println?
|
(let [{:keys [:version :raw-in :raw-out :println?
|
||||||
:help? :file]} (parse-opts args)]
|
:help? :file :command-line-args
|
||||||
|
:expression] :as _opts} (parse-opts args)]
|
||||||
|
#_(binding [*out* *err*]
|
||||||
|
(prn ">>" _opts))
|
||||||
(second
|
(second
|
||||||
(cond version
|
(cond version
|
||||||
[(print-version) 0]
|
[(print-version) 0]
|
||||||
|
|
@ -120,10 +135,9 @@
|
||||||
[(print-help) 0]
|
[(print-help) 0]
|
||||||
:else
|
:else
|
||||||
(try
|
(try
|
||||||
[(let [exprs (drop-while #(str/starts-with? % "-") args)
|
[(do (when-not (or expression file)
|
||||||
_ (when-not (or (= 1 (count exprs)) file)
|
(throw (Exception. "Missing expression.")))
|
||||||
(throw (Exception. ^String usage-string)))
|
(let [expr (if file (read-file file) expression)
|
||||||
expr (if file (read-file file) (last args))
|
|
||||||
in (delay (let [in (slurp *in*)]
|
in (delay (let [in (slurp *in*)]
|
||||||
(if raw-in
|
(if raw-in
|
||||||
(parse-shell-string in)
|
(parse-shell-string in)
|
||||||
|
|
@ -132,18 +146,19 @@
|
||||||
expr
|
expr
|
||||||
{:bindings (assoc bindings
|
{:bindings (assoc bindings
|
||||||
(with-meta '*in*
|
(with-meta '*in*
|
||||||
{:sci/deref! true}) in)})]
|
{:sci/deref! true}) in
|
||||||
|
'*command-line-args* command-line-args)})]
|
||||||
(if raw-out
|
(if raw-out
|
||||||
(if (coll? res)
|
(if (coll? res)
|
||||||
(doseq [l res]
|
(doseq [l res]
|
||||||
(println l))
|
(println l))
|
||||||
(println res))
|
(println res))
|
||||||
((if println? println? prn) res))) 0]
|
((if println? println? prn) res)))) 0]
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(binding [*out* *err*]
|
(binding [*out* *err*]
|
||||||
(println (str/trim
|
(when-let [msg (or (:stderr (ex-data e))
|
||||||
(or (:stderr (ex-data e))
|
(.getMessage e))]
|
||||||
(.getMessage e))) ))
|
(println (str/trim msg) )))
|
||||||
[nil 1])))))
|
[nil 1])))))
|
||||||
1))
|
1))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
(testing "fn"
|
(testing "fn"
|
||||||
(is (= 2 (bb 1 "(#(+ 1 %) *in*)")))
|
(is (= 2 (bb 1 "(#(+ 1 %) *in*)")))
|
||||||
(is (= [1 2 3] (bb 1 "(map #(+ 1 %) [0 1 2])")))
|
(is (= [1 2 3] (bb 1 "(map #(+ 1 %) [0 1 2])")))
|
||||||
(is (bb 1 "(#(when (odd? *in*) *in*) 1)")))
|
(is (= 1 (bb 1 "(#(when (odd? *in*) *in*))"))))
|
||||||
(testing "map"
|
(testing "map"
|
||||||
(is (= [1 2 3] (bb 1 '(map inc [0 1 2])))))
|
(is (= [1 2 3] (bb 1 '(map inc [0 1 2])))))
|
||||||
(testing "keep"
|
(testing "keep"
|
||||||
|
|
@ -69,3 +69,9 @@
|
||||||
(is (= "bar" (second res)))
|
(is (= "bar" (second res)))
|
||||||
(doseq [s res]
|
(doseq [s res]
|
||||||
(is (not-empty s)))))
|
(is (not-empty s)))))
|
||||||
|
|
||||||
|
(deftest malformed-command-line-args
|
||||||
|
(is (thrown-with-msg? Exception #"File does not exist: non-existing\n"
|
||||||
|
(bb nil "-f" "non-existing")))
|
||||||
|
(is (thrown-with-msg? Exception #"Missing expression.\n"
|
||||||
|
(bb nil))))
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,25 @@
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
(defn bb-jvm [input & args]
|
(defn bb-jvm [input & args]
|
||||||
|
(let [sw (java.io.StringWriter.)
|
||||||
|
res (binding [*err* sw]
|
||||||
(with-out-str
|
(with-out-str
|
||||||
(if input
|
(if input
|
||||||
(with-in-str input
|
(with-in-str input
|
||||||
(apply main/main args))
|
(apply main/main args))
|
||||||
(apply main/main input args))))
|
(apply main/main input args))))]
|
||||||
|
(if-let [err ^String (not-empty (str sw))]
|
||||||
|
(throw (Exception. err)) res)))
|
||||||
|
|
||||||
(defn bb-native [input & args]
|
(defn bb-native [input & args]
|
||||||
(let-programs [bb "./bb"]
|
(let-programs [bb "./bb"]
|
||||||
(binding [sh/*throw* false]
|
(try (if input
|
||||||
(if input
|
|
||||||
(apply bb (conj (vec args)
|
(apply bb (conj (vec args)
|
||||||
{:in input}))
|
{:in input}))
|
||||||
(apply bb input args)))))
|
(apply bb input args))
|
||||||
|
(catch Exception e
|
||||||
|
(let [err-msg (or (:stderr (ex-data e)) "")]
|
||||||
|
(throw (Exception. ^String err-msg)))))))
|
||||||
|
|
||||||
(def bb
|
(def bb
|
||||||
(case (System/getenv "BABASHKA_TEST_ENV")
|
(case (System/getenv "BABASHKA_TEST_ENV")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue