add shebang support (#8)
This commit is contained in:
parent
c3b10a2c8a
commit
b2b35d0a7b
5 changed files with 102 additions and 27 deletions
51
README.md
51
README.md
|
|
@ -66,9 +66,11 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
``` shellsession
|
``` shellsession
|
||||||
bb [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ expression ]
|
bb [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ -f <file> ] [ expression ]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
There is one special variable, `*in*`, which is the input read from stdin. The
|
||||||
input is read as EDN by default. If the `-i` flag is provided, then the input is
|
input is read as EDN by default. If the `-i` flag is provided, then the input is
|
||||||
read as a string which is then split on newlines. The output is printed as EDN
|
read as a string which is then split on newlines. The output is printed as EDN
|
||||||
|
|
@ -79,12 +81,21 @@ The current version can be printed with `bb --version`.
|
||||||
|
|
||||||
Currently only the following special forms/macros are supported: anonymous
|
Currently only the following special forms/macros are supported: anonymous
|
||||||
function literals like `#(%1 %2)`, `quote`, `do`,`if`, `when`, `let`, `and`,
|
function literals like `#(%1 %2)`, `quote`, `do`,`if`, `when`, `let`, `and`,
|
||||||
`or`, `->`, `->>`, `as->`.
|
`or`, `->`, `->>`, `as->`. Anonymous functions literals are allowed with
|
||||||
|
currently up to three positional arguments.
|
||||||
|
|
||||||
The `clojure.core` functions are accessible without a namespace alias. Those in
|
The `clojure.core` functions are accessible without a namespace alias.
|
||||||
`clojure.string` are accessed through the alias `str`, like:
|
|
||||||
`str/includes?`. Those in `clojure.set` using the alias `set`, like:
|
The following Clojure namespaces are required by default and only available
|
||||||
`set/difference`.
|
through the aliases:
|
||||||
|
|
||||||
|
- `clojure.string` aliased as `str`
|
||||||
|
- `clojure.set` aliased as `set`
|
||||||
|
- `clojure.java.shell` aliases as `shell` (only `sh` is available)
|
||||||
|
|
||||||
|
From Java the following is available:
|
||||||
|
|
||||||
|
- `System`: `getProperty`, `getProperties`, `getenv`
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
|
|
@ -102,9 +113,6 @@ $ bb '(filterv :foo *in*)' <<< '[{:foo 1} {:bar 2}]'
|
||||||
[{:foo 1}]
|
[{:foo 1}]
|
||||||
```
|
```
|
||||||
|
|
||||||
Anonymous functions literals are allowed with currently up to three positional
|
|
||||||
arguments.
|
|
||||||
|
|
||||||
``` shellsession
|
``` shellsession
|
||||||
$ bb '(#(+ %1 %2 %3) 1 2 *in*)' <<< 3
|
$ bb '(#(+ %1 %2 %3) 1 2 *in*)' <<< 3
|
||||||
6
|
6
|
||||||
|
|
@ -115,17 +123,34 @@ $ ls | bb -i '(filterv #(re-find #"reflection" %) *in*)'
|
||||||
["reflection.json"]
|
["reflection.json"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Shell commands can be executed using `csh` which is an alias for
|
|
||||||
`clojure.java.shell/sh`:
|
|
||||||
|
|
||||||
``` shellsession
|
``` shellsession
|
||||||
$ bb '(run! #(csh "touch" (str "/tmp/test/" %)) (range 100))'
|
$ bb '(run! #(shell/sh "touch" (str "/tmp/test/" %)) (range 100))'
|
||||||
$ ls /tmp/test | bb -i '*in*'
|
$ ls /tmp/test | bb -i '*in*'
|
||||||
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]
|
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
More examples can be found in the [gallery](#gallery).
|
More examples can be found in the [gallery](#gallery).
|
||||||
|
|
||||||
|
## Running a file
|
||||||
|
|
||||||
|
Babashka expressions may be executed from a file using `-f` or `--file`:
|
||||||
|
|
||||||
|
``` shellsession
|
||||||
|
bb -f script.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `bb` with a shebang also works:
|
||||||
|
|
||||||
|
``` shellsession
|
||||||
|
$ cat script.clj
|
||||||
|
#!/usr/bin/env bb -f
|
||||||
|
|
||||||
|
(+ 1 2 3)
|
||||||
|
|
||||||
|
$ ./script.clj
|
||||||
|
6
|
||||||
|
```
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
||||||
Test on the JVM:
|
Test on the JVM:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
(:require
|
(:require
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.java.shell :as cjs]
|
[clojure.java.shell :as shell]
|
||||||
[clojure.string :as str :refer [starts-with?]]
|
[clojure.string :as str :refer [starts-with?]]
|
||||||
[sci.core :as sci])
|
[sci.core :as sci])
|
||||||
(:gen-class))
|
(:gen-class))
|
||||||
|
|
@ -38,12 +38,15 @@
|
||||||
raw-out (boolean (or (get opts "-o")
|
raw-out (boolean (or (get opts "-o")
|
||||||
(get opts "-io")))
|
(get opts "-io")))
|
||||||
println? (boolean (get opts "--println"))
|
println? (boolean (get opts "--println"))
|
||||||
help? (boolean (get opts "--help"))]
|
help? (boolean (get opts "--help"))
|
||||||
|
file (first (or (get opts "-f")
|
||||||
|
(get opts "--file")))]
|
||||||
{:version version
|
{:version version
|
||||||
:raw-in raw-in
|
:raw-in raw-in
|
||||||
:raw-out raw-out
|
:raw-out raw-out
|
||||||
:println? println?
|
:println? println?
|
||||||
:help? help?}))
|
:help? help?
|
||||||
|
:file file}))
|
||||||
|
|
||||||
(defn parse-shell-string [s]
|
(defn parse-shell-string [s]
|
||||||
(str/split s #"\n"))
|
(str/split s #"\n"))
|
||||||
|
|
@ -69,13 +72,46 @@
|
||||||
-i: read shell input into a list of strings instead of reading EDN.
|
-i: read shell input into a list of strings instead of reading EDN.
|
||||||
-o: write shell output instead of EDN.
|
-o: write shell output instead of EDN.
|
||||||
-io: combination of -i and -o.
|
-io: combination of -i and -o.
|
||||||
|
--file or -f: read expression from file instead of argument
|
||||||
"))
|
"))
|
||||||
|
|
||||||
|
(defn read-file [file]
|
||||||
|
(as-> (slurp file) x
|
||||||
|
;; remove hashbang
|
||||||
|
(str/replace x #"^#!.*" "")
|
||||||
|
(format "(do %s)" x)))
|
||||||
|
|
||||||
|
(defn get-env
|
||||||
|
([] (System/getenv))
|
||||||
|
([s] (System/getenv s)))
|
||||||
|
|
||||||
|
(defn get-property
|
||||||
|
([s]
|
||||||
|
(System/getProperty s))
|
||||||
|
([s d]
|
||||||
|
(System/getProperty s d)))
|
||||||
|
|
||||||
|
(defn get-properties []
|
||||||
|
(System/getProperties))
|
||||||
|
|
||||||
|
(def bindings
|
||||||
|
{'run! run!
|
||||||
|
'shell/sh shell/sh
|
||||||
|
'csh shell/sh ;; backwards compatibility, deprecated
|
||||||
|
'pmap pmap
|
||||||
|
'print print
|
||||||
|
'pr-str pr-str
|
||||||
|
'prn prn
|
||||||
|
'println println
|
||||||
|
'System/getenv get-env
|
||||||
|
'System/getProperty get-property
|
||||||
|
'System/getProperties get-properties})
|
||||||
|
|
||||||
(defn main
|
(defn main
|
||||||
[& args]
|
[& args]
|
||||||
(or
|
(or
|
||||||
(let [{:keys [:version :raw-in :raw-out :println?
|
(let [{:keys [:version :raw-in :raw-out :println?
|
||||||
:help?]} (parse-opts args)]
|
:help? :file]} (parse-opts args)]
|
||||||
(second
|
(second
|
||||||
(cond version
|
(cond version
|
||||||
[(print-version) 0]
|
[(print-version) 0]
|
||||||
|
|
@ -84,19 +120,18 @@
|
||||||
:else
|
:else
|
||||||
(try
|
(try
|
||||||
[(let [exprs (drop-while #(str/starts-with? % "-") args)
|
[(let [exprs (drop-while #(str/starts-with? % "-") args)
|
||||||
_ (when (not= (count exprs) 1)
|
_ (when-not (or (= 1 (count exprs)) file)
|
||||||
(throw (Exception. ^String usage-string)))
|
(throw (Exception. ^String usage-string)))
|
||||||
expr (last args)
|
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)
|
||||||
(read-edn in))))
|
(read-edn in))))
|
||||||
res (sci/eval-string
|
res (sci/eval-string
|
||||||
expr
|
expr
|
||||||
{:bindings {(with-meta '*in*
|
{:bindings (assoc bindings
|
||||||
{:sci/deref! true}) in
|
(with-meta '*in*
|
||||||
'run! run!
|
{:sci/deref! true}) in)})]
|
||||||
'csh cjs/sh}})]
|
|
||||||
(if raw-out
|
(if raw-out
|
||||||
(if (coll? res)
|
(if (coll? res)
|
||||||
(doseq [l res]
|
(doseq [l res]
|
||||||
|
|
|
||||||
|
|
@ -63,3 +63,9 @@
|
||||||
(deftest input-test
|
(deftest input-test
|
||||||
(testing "bb doesn't wait for input if *in* isn't used"
|
(testing "bb doesn't wait for input if *in* isn't used"
|
||||||
(is (= "2\n" (with-out-str (main/main "(inc 1)"))))))
|
(is (= "2\n" (with-out-str (main/main "(inc 1)"))))))
|
||||||
|
|
||||||
|
(deftest System-test
|
||||||
|
(let [res (bb nil "-f" "test/babashka/scripts/System.bb")]
|
||||||
|
(is (= "bar" (second res)))
|
||||||
|
(doseq [s res]
|
||||||
|
(is (not-empty s)))))
|
||||||
|
|
|
||||||
5
test/babashka/scripts/System.bb
Normal file
5
test/babashka/scripts/System.bb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
[(System/getProperty "user.dir")
|
||||||
|
(System/getProperty "foo" "bar")
|
||||||
|
(System/getenv "HOME")
|
||||||
|
(System/getProperties)
|
||||||
|
(System/getenv)]
|
||||||
|
|
@ -7,14 +7,18 @@
|
||||||
|
|
||||||
(defn bb-jvm [input & args]
|
(defn bb-jvm [input & args]
|
||||||
(with-out-str
|
(with-out-str
|
||||||
(with-in-str input
|
(if input
|
||||||
(apply main/main args))))
|
(with-in-str input
|
||||||
|
(apply main/main args))
|
||||||
|
(apply main/main input args))))
|
||||||
|
|
||||||
(defn bb-native [input & args]
|
(defn bb-native [input & args]
|
||||||
(let-programs [bb "./bb"]
|
(let-programs [bb "./bb"]
|
||||||
(binding [sh/*throw* false]
|
(binding [sh/*throw* false]
|
||||||
(apply bb (conj (vec args)
|
(if input
|
||||||
{:in input})))))
|
(apply bb (conj (vec args)
|
||||||
|
{:in input}))
|
||||||
|
(apply bb input args)))))
|
||||||
|
|
||||||
(def bb
|
(def bb
|
||||||
(case (System/getenv "BABASHKA_TEST_ENV")
|
(case (System/getenv "BABASHKA_TEST_ENV")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue