diff --git a/README.md b/README.md index c9009a45..ef4807e0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ $ bb '(vec (dedupe *in*))' <<< '[1 1 1 1 2]' ## Rationale If you're a bash expert, you probably don't need this. But for those of us who -can use a bit of Clojure in their shell scripts, it may be useful. +scan use a bit of Clojure in their shell scripts, it may be useful. Properties: @@ -66,7 +66,7 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk ## Usage ``` shellsession -... | bb [-i] [-o] '' +bb [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ expression ] ``` There is one special variable, `*in*`, which is the input read from stdin. The @@ -77,8 +77,14 @@ shell-scripting friendly output. To combine `-i` and `-o` you can use `-io`. The current version can be printed with `bb --version`. -Currently only the macros `if`, `when`, `and`, `or`, `->`, `->>` and `as->` are -supported. +Currently only the following special forms/macros are supported: anonymous +function literals like `#(%1 %2)`, `quote`, `do`,`if`, `when`, `let`, `and`, +`or`, `->`, `->>`, `as->`. + +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`. Examples: @@ -109,6 +115,15 @@ $ 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))' +$ 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). ## Test diff --git a/project.clj b/project.clj index e27c9736..9c32dd35 100644 --- a/project.clj +++ b/project.clj @@ -9,7 +9,7 @@ :url "http://opensource.org/licenses/eclipse-1.0.php"} :source-paths ["src"] :dependencies [[org.clojure/clojure "1.9.0"] - [borkdude/sci "0.0.2"]] + [borkdude/sci "0.0.4"]] :profiles {:clojure-1.9.0 {:dependencies [[org.clojure/clojure "1.9.0"]]} :clojure-1.10.1 {:dependencies [[org.clojure/clojure "1.10.1"]]} :test {:dependencies [[clj-commons/conch "0.9.2"]]} diff --git a/reflection.json b/reflection.json index 81942d35..c829f5fa 100644 --- a/reflection.json +++ b/reflection.json @@ -1,9 +1,33 @@ [ - { - "name": "java.lang.Class", - "allDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredMethods": true, - "allPublicMethods": true - } + { + "name": "java.lang.Class", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true + }, + { + "name":"java.io.BufferedReader", + "allPublicMethods":true + }, + { + "name":"java.lang.Process", + "allPublicMethods":true + }, + { + "name":"java.lang.ProcessBuilder", + "allPublicConstructors":true + }, + { + "name":"java.lang.String", + "allPublicMethods":true + }, + { + "name":"java.lang.UNIXProcess", + "allPublicMethods":true + }, + { + "name":"java.util.concurrent.LinkedBlockingQueue", + "allPublicMethods":true + } ] diff --git a/resources/BABASHKA_VERSION b/resources/BABASHKA_VERSION index af1ac3d8..5a5831ab 100644 --- a/resources/BABASHKA_VERSION +++ b/resources/BABASHKA_VERSION @@ -1 +1 @@ -0.0.7-SNAPSHOT +0.0.7 diff --git a/script/compile b/script/compile index 80ada177..a2ad0ea0 100755 --- a/script/compile +++ b/script/compile @@ -17,8 +17,10 @@ $GRAALVM_HOME/bin/native-image \ -J-Dclojure.spec.skip-macros=true \ -J-Dclojure.compiler.direct-linking=true \ "-H:IncludeResources=BABASHKA_VERSION" \ + "-H:IncludeResources=SCI_VERSION" \ -H:ReflectionConfigurationFiles=reflection.json \ - --initialize-at-build-time \ + --initialize-at-run-time=java.lang.Math\$RandomNumberGeneratorHolder \ + --initialize-at-build-time \ -H:Log=registerResource: \ --verbose \ --no-fallback \ diff --git a/script/test b/script/test index 54364d3c..eeef9e2d 100755 --- a/script/test +++ b/script/test @@ -2,7 +2,7 @@ set -eo pipefail -if [ "$JET_TEST_ENV" = "native" ]; then +if [ "$BABASHKA_TEST_ENV" = "native" ]; then lein test else echo "Testing with Clojure 1.9.0" diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 21a660df..6282b3f7 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -3,11 +3,15 @@ (:require [clojure.edn :as edn] [clojure.java.io :as io] + [clojure.java.shell :as cjs] [clojure.string :as str :refer [starts-with?]] [sci.core :as sci]) (:gen-class)) (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. (defn read-edn [s] (edn/read-string @@ -33,35 +37,83 @@ (get opts "-io"))) raw-out (boolean (or (get opts "-o") (get opts "-io"))) - println? (boolean (get opts "--println"))] + println? (boolean (get opts "--println")) + help? (boolean (get opts "--help"))] {:version version :raw-in raw-in :raw-out raw-out - :println? println?})) + :println? println? + :help? help?})) + +(defn parse-shell-string [s] + (str/split s #"\n")) + +(defn print-version [] + (println (str "babashka v"(str/trim (slurp (io/resource "BABASHKA_VERSION")))))) + +(def usage-string "Usage: [ --help ] [ -i ] [ -o ] [ -io ] [ --version ] [ expression ]") +(defn print-usage [] + (println usage-string)) + +(defn print-help [] + (println (str "babashka v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))) + (println (str "sci v" (str/trim (slurp (io/resource "SCI_VERSION"))))) + (println) + (print-usage) + (println) + (println "Options:") + (println " + --help: print this help text. + --version: print the current version of babashka. + + -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. +")) + +(defn main + [& args] + (or + (let [{:keys [:version :raw-in :raw-out :println? + :help?]} (parse-opts args)] + (second + (cond version + [(print-version) 0] + help? + [(print-help) 0] + :else + (try + [(let [exprs (drop-while #(str/starts-with? % "-") args) + _ (when (not= (count exprs) 1) + (throw (Exception. ^String usage-string))) + expr (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}})] + (if raw-out + (if (coll? res) + (doseq [l res] + (println l)) + (println res)) + ((if println? println? prn) res))) 0] + (catch Exception e + (binding [*out* *err*] + (println (str/trim + (or (:stderr (ex-data e)) + (.getMessage e))) )) + [nil 1]))))) + 1)) (defn -main [& args] - (let [{:keys [:version :raw-in :raw-out :println?]} (parse-opts args)] - (cond version - (println (str/trim (slurp (io/resource "BABASHKA_VERSION")))) - :else - (let [expr (last args) - in (slurp *in*) - in (if raw-in - (str/split in #"\n") - (read-edn in)) - ;; _ (prn in) - res (try (sci/eval-string expr {:bindings {'*in* in}}) - (catch Exception e - (binding [*out* *err*] - (println (.getMessage e))) - (System/exit 1)))] - (if raw-out - (if (coll? res) - (doseq [l res] - (println l)) - (println res)) - ((if println? println? prn) res)))))) + (System/exit (apply main args))) ;;;; Scratch diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index d0995f3c..255db482 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -2,6 +2,7 @@ (:require [clojure.test :as test :refer [deftest is testing]] [babashka.test-utils :as test-utils] + [babashka.main :as main] [clojure.edn :as edn] [clojure.string :as str])) @@ -50,7 +51,7 @@ "-io" (str '(shuffle *in*))) out-lines (set (str/split out #"\n"))] - (= in-lines out-lines))) + (is (= in-lines out-lines)))) (testing "find occurrences in file by line number" (is (= '(1 3) (-> @@ -58,3 +59,7 @@ "-i" "(map-indexed #(-> [%1 %2]) *in*)") (bb "(keep #(when (re-find #\"(?i)clojure\" (second %)) (first %)) *in*)")))))) + +(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)")))))) diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj index 352edfa6..bd3703e9 100644 --- a/test/babashka/test_utils.clj +++ b/test/babashka/test_utils.clj @@ -8,7 +8,7 @@ (defn bb-jvm [input & args] (with-out-str (with-in-str input - (apply main/-main args)))) + (apply main/main args)))) (defn bb-native [input & args] (let-programs [bb "./bb"]