diff --git a/README.md b/README.md index ef4807e0..8274aff5 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,11 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk ## Usage ``` shellsession -bb [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ expression ] +bb [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ -f ] [ 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 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 @@ -79,12 +81,21 @@ The current version can be printed with `bb --version`. Currently only the following special forms/macros are supported: anonymous 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 -`clojure.string` are accessed through the alias `str`, like: -`str/includes?`. Those in `clojure.set` using the alias `set`, like: -`set/difference`. +The `clojure.core` functions are accessible without a namespace alias. + +The following Clojure namespaces are required by default and only available +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: @@ -102,9 +113,6 @@ $ bb '(filterv :foo *in*)' <<< '[{:foo 1} {:bar 2}]' [{:foo 1}] ``` -Anonymous functions literals are allowed with currently up to three positional -arguments. - ``` shellsession $ bb '(#(+ %1 %2 %3) 1 2 *in*)' <<< 3 6 @@ -115,17 +123,34 @@ $ ls | bb -i '(filterv #(re-find #"reflection" %) *in*)' ["reflection.json"] ``` -Shell commands can be executed using `csh` which is an alias for -`clojure.java.shell/sh`: - ``` 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*' ["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...] ``` 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 on the JVM: diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 6282b3f7..688177da 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -3,7 +3,7 @@ (:require [clojure.edn :as edn] [clojure.java.io :as io] - [clojure.java.shell :as cjs] + [clojure.java.shell :as shell] [clojure.string :as str :refer [starts-with?]] [sci.core :as sci]) (:gen-class)) @@ -38,12 +38,15 @@ raw-out (boolean (or (get opts "-o") (get opts "-io"))) 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 :raw-in raw-in :raw-out raw-out :println? println? - :help? help?})) + :help? help? + :file file})) (defn parse-shell-string [s] (str/split s #"\n")) @@ -69,13 +72,46 @@ -i: read shell input into a list of strings instead of reading EDN. -o: write shell output instead of EDN. -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 [& args] (or (let [{:keys [:version :raw-in :raw-out :println? - :help?]} (parse-opts args)] + :help? :file]} (parse-opts args)] (second (cond version [(print-version) 0] @@ -84,19 +120,18 @@ :else (try [(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))) - expr (last args) + expr (if file (read-file file) (last args)) in (delay (let [in (slurp *in*)] (if raw-in (parse-shell-string in) (read-edn in)))) res (sci/eval-string expr - {:bindings {(with-meta '*in* - {:sci/deref! true}) in - 'run! run! - 'csh cjs/sh}})] + {:bindings (assoc bindings + (with-meta '*in* + {:sci/deref! true}) in)})] (if raw-out (if (coll? res) (doseq [l res] diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index 255db482..e787dd13 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -63,3 +63,9 @@ (deftest input-test (testing "bb doesn't wait for input if *in* isn't used" (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))))) diff --git a/test/babashka/scripts/System.bb b/test/babashka/scripts/System.bb new file mode 100644 index 00000000..bb9916cb --- /dev/null +++ b/test/babashka/scripts/System.bb @@ -0,0 +1,5 @@ +[(System/getProperty "user.dir") + (System/getProperty "foo" "bar") + (System/getenv "HOME") + (System/getProperties) + (System/getenv)] diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj index bd3703e9..4f73ffef 100644 --- a/test/babashka/test_utils.clj +++ b/test/babashka/test_utils.clj @@ -7,14 +7,18 @@ (defn bb-jvm [input & args] (with-out-str - (with-in-str input - (apply main/main args)))) + (if input + (with-in-str input + (apply main/main args)) + (apply main/main input args)))) (defn bb-native [input & args] (let-programs [bb "./bb"] (binding [sh/*throw* false] - (apply bb (conj (vec args) - {:in input}))))) + (if input + (apply bb (conj (vec args) + {:in input})) + (apply bb input args))))) (def bb (case (System/getenv "BABASHKA_TEST_ENV")