Merge branch 'master' into ansi

This commit is contained in:
Michiel Borkent 2019-12-16 13:57:34 +01:00
commit d36b1272ec
29 changed files with 792 additions and 269 deletions

View file

@ -3,14 +3,16 @@
rm -rf /tmp/release
mkdir -p /tmp/release
cp bb /tmp/release
# cp src-bash/bbk /tmp/release
VERSION=$(cat resources/BABASHKA_VERSION)
cd /tmp/release
## release binary as zip archive
zip "babashka-$VERSION-$BABASHKA_PLATFORM-amd64.zip" bb
zip "babashka-$VERSION-$BABASHKA_PLATFORM-amd64.zip" bb # bbk
## cleanup
rm bb
rm bb # bbk

3
.gitignore vendored
View file

@ -13,3 +13,6 @@ pom.xml.asc
/bb
.clj-kondo/.cache
!java/src/babashka/impl/LockFix.class
!test-resources/babashka/src_for_classpath_test/foo.jar
.cpcache
deps.edn

162
README.md
View file

@ -3,6 +3,8 @@
[![CircleCI](https://circleci.com/gh/borkdude/babashka/tree/master.svg?style=shield)](https://circleci.com/gh/borkdude/babashka/tree/master)
[![Clojars Project](https://img.shields.io/clojars/v/borkdude/babashka.svg)](https://clojars.org/borkdude/babashka)
[![cljdoc badge](https://cljdoc.org/badge/borkdude/babashka)](https://cljdoc.org/d/borkdude/babashka/CURRENT)
[![project chat](https://img.shields.io/badge/slack-join_chat-brightgreen.svg)](https://app.slack.com/client/T03RZGPFR/CLX41ASCS)
A Clojure [babushka](https://en.wikipedia.org/wiki/Headscarf) for the grey areas of Bash.
@ -125,22 +127,28 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk
## Usage
``` shellsession
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] ( -e <expression> | -f <file> | --socket-repl [<host>:]<port> )
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
[ arg* ]
Options:
--help, -h or -?: print this help text.
--version: print the current version of babashka.
-i: bind *in* to a lazy seq of lines from stdin.
-I: bind *in* to a lazy seq of EDN values from stdin.
-o: write lines to stdout.
-O: write EDN values to stdout.
--stream: stream over lines or EDN values from stdin. Combined with -i or -I *in* becomes a single value per iteration.
-e, --eval <expression>: evaluate an expression
-f, --file <path>: evaluate a file
--socket-repl: start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--time: print execution time before exiting.
--help, -h or -? Print this help text.
--version Print the current version of babashka.
-i Bind *in* to a lazy seq of lines from stdin.
-I Bind *in* to a lazy seq of EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--verbose Print entire stacktrace in case of exception.
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *in* becomes a single value per iteration.
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--repl Start REPL
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--time Print execution time before exiting.
If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise.
Everything after that is bound to *command-line-args*.
@ -149,8 +157,9 @@ Everything after that is bound to *command-line-args*.
The `clojure.core` functions are accessible without a namespace alias.
The following namespaces are required by default and available through the
pre-defined aliases. You may use `require` + `:as` and/or `:refer` on these
namespaces. If not all vars are available, they are enumerated explicitly.
pre-defined aliases in the `user` namespace. You may use `require` + `:as`
and/or `:refer` on these namespaces. If not all vars are available, they are
enumerated explicitly.
- `clojure.string` aliased as `str`
- `clojure.set` aliased as `set`
@ -167,6 +176,7 @@ namespaces. If not all vars are available, they are enumerated explicitly.
aliased as `conch`
- [`clojure.tools.cli`](https://github.com/clojure/tools.cli) aliased as `tools.cli`
- [`clojure.data.csv`](https://github.com/clojure/data.csv) aliased as `csv`
- [`cheshire.core`](https://github.com/dakrone/cheshire) aliased as `json`
The following Java classes are available:
@ -305,6 +315,72 @@ $ cat script.clj
("hello" "1" "2" "3")
```
## Preloads
The environment variable `BABASHKA_PRELOADS` allows to define code that will be
available in all subsequent usages of babashka.
``` shellsession
BABASHKA_PRELOADS='(defn foo [x] (+ x 2))'
BABASHKA_PRELOADS=$BABASHKA_PRELOADS' (defn bar [x] (* x 2))'
export BABASHKA_PRELOADS
```
Note that you can concatenate multiple expressions. Now you can use these functions in babashka:
``` shellsession
$ bb '(-> (foo *in*) bar)' <<< 1
6
```
You can also preload an entire file using `load-file`:
``` shellsession
export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'
```
Note that `*in*` is not available in preloads.
## Classpath
Babashka accepts a `--classpath` option that will be used to search for
namespaces and load them:
``` clojure
$ cat src/my/namespace.clj
(ns my.namespace)
(defn -main [& _args]
(println "Hello from my namespace!"))
$ bb --classpath src --main my.namespace
Hello from my namespace!
```
Note that you can use the `clojure` tool to produce classpaths and download dependencies:
``` shellsession
$ cat deps.edn
{:deps
{my_gist_script
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}}
$ CLASSPATH=$(clojure -Spath)
$ bb --classpath "$CLASSPATH" --main my-gist-script
Hello from gist script!
```
If there is no `--classpath` argument, the `BABASHKA_CLASSPATH` environment
variable will be used:
``` shellsession
$ export BABASHKA_CLASSPATH=$(clojure -Spath)
$ export BABASHKA_PRELOADS="(require '[my-gist-script])"
$ bb "(my-gist-script/-main)"
Hello from gist script!
```
## Parsing command line arguments
Babashka ships with `clojure.tools.cli`:
@ -342,32 +418,6 @@ $ ./bb example.clj
babashka doesn't support in-ns yet!
```
## Preloads
The environment variable `BABASHKA_PRELOADS` allows to define code that will be
available in all subsequent usages of babashka.
``` shellsession
BABASHKA_PRELOADS='(defn foo [x] (+ x 2))'
BABASHKA_PRELOADS=$BABASHKA_PRELOADS' (defn bar [x] (* x 2))'
export BABASHKA_PRELOADS
```
Note that you can concatenate multiple expressions. Now you can use these functions in babashka:
``` shellsession
$ bb '(-> (foo *in*) bar)' <<< 1
6
```
You can also preload an entire file using `load-file`:
``` shellsession
export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'
```
Note that `*in*` is not available in preloads.
## Socket REPL
Start the socket REPL like this:
@ -442,16 +492,8 @@ Differences with Clojure:
- A subset of Java classes are supported.
- Only the `clojure.core`, `clojure.set` and `clojure.string` namespaces are
available from Clojure.
- There is no classpath and no support for loading code from Maven/Clojars
dependencies. However, you can use `load-file` to load external code from
disk.
- `require` does not load files; it only provides a way to create different
aliases for included namespaces, which makes it easier to make scripts
portable between the JVM and babashka.
- Only the `clojure.core`, `clojure.set`, `clojure.string` and `clojure.walk`
namespaces are available from Clojure.
- Interpretation comes with overhead. Therefore tight loops are likely slower
than in Clojure on the JVM.
@ -588,7 +630,7 @@ less
### Portable tree command
See [examples/tree.clj](https://github.com/borkdude/babashka/blob/8afb87142e0e4da8b6f912cfd7daf9c30b805ab3/examples/tree.clj).
See [examples/tree.clj](https://github.com/borkdude/babashka/blob/master/examples/tree.clj).
``` shellsession
$ clojure -Sdeps '{:deps {org.clojure/tools.cli {:mvn/version "0.4.2"}}}' examples/tree.clj src
@ -605,6 +647,22 @@ src
├── impl
│ ├── tools
│ │ └── cli.clj
...
```
### List outdated maven dependencies
See [examples/outdated.clj](https://github.com/borkdude/babashka/blob/master/examples/outdated.clj).
Inspired by an idea from [@seancorfield](https://github.com/seancorfield).
``` shellsession
$ cat /tmp/deps.edn
{:deps {cheshire {:mvn/version "5.8.1"}
clj-http {:mvn/version "3.4.0"}}}
$ examples/outdated.clj /tmp/deps.edn
clj-http/clj-http can be upgraded from 3.4.0 to 3.10.0
cheshire/cheshire can be upgraded from 5.8.1 to 5.9.0
```
## Thanks

34
examples/outdated.clj Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bb
(require '[clojure.edn :as edn])
(require '[clojure.java.shell :refer [sh]])
(require '[clojure.string :as str])
(def deps (-> (slurp (or (first *command-line-args*)
"deps.edn"))
edn/read-string
:deps))
(def with-release (zipmap (keys deps)
(map #(assoc % :mvn/version "RELEASE")
(vals deps))))
(defn deps->versions [deps]
(let [res (sh "clojure" "-Sdeps" (str {:deps deps}) "-Stree")
tree (:out res)
lines (str/split tree #"\n")
top-level (remove #(str/starts-with? % " ") lines)
deps (map #(str/split % #" ") top-level)]
(reduce (fn [acc [dep version]]
(assoc acc dep version))
{}
deps)))
(def version-map (deps->versions deps))
(def new-version-map (deps->versions with-release))
(doseq [[dep version] version-map
:let [new-version (get new-version-map dep)]
:when (not= version new-version)]
(println dep "can be upgraded from" version "to" new-version))
;; Inspired by an idea from @seancorfield on Clojurians Slack

View file

@ -11,10 +11,11 @@
:resource-paths ["resources" "sci/resources"]
:dependencies [[org.clojure/clojure "1.10.1"]
[org.clojure/tools.reader "1.3.2"]
[borkdude/edamame "0.0.9-alpha.2"]
[borkdude/edamame "0.0.10-alpha.2"]
[org.clojure/core.async "0.4.500"]
[org.clojure/tools.cli "0.4.2"]
[org.clojure/data.csv "0.1.4"]
[cheshire "5.9.0"]
[io.aviso/pretty "0.1.37"]]
:profiles {:test {:dependencies [[clj-commons/conch "0.9.2"]]}
:uberjar {:global-vars {*assert* false}

View file

@ -25,7 +25,9 @@
},
{
"name":"java.io.BufferedReader",
"allPublicMethods":true
"allPublicMethods":true,
"allPublicFields": true,
"allPublicConstructors": true
},
{
"name": "java.lang.Class",
@ -122,6 +124,24 @@
"name":"java.lang.UNIXProcess",
"allPublicMethods":true
},
{
"name":"java.nio.file.attribute.FileAttribute",
"allPublicMethods":true,
"allPublicFields": true,
"allPublicConstructors": true
},
{
"name":"java.nio.file.attribute.PosixFilePermission",
"allPublicMethods":true,
"allPublicFields": true,
"allPublicConstructors": true
},
{
"name":"java.nio.file.attribute.PosixFilePermissions",
"allPublicMethods":true,
"allPublicFields": true,
"allPublicConstructors": true
},
{
"name":"java.nio.file.Path",
"allPublicMethods":true

View file

@ -1 +1 @@
0.0.37
0.0.42

View file

@ -1 +1 @@
0.0.38-SNAPSHOT
0.0.43-SNAPSHOT

2
sci

@ -1 +1 @@
Subproject commit 9ab0b1d181605e4c78eb8a90a75982df5c822514
Subproject commit 07d28ee572e90a629e01b10aa5b98cb33ccdc1e5

View file

@ -1,6 +1,15 @@
#!/usr/bin/env bash
set -eo pipefail
export BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
BABASHKA_PRELOADS=""
BABASHKA_CLASSPATH=""
lein test "$@"
lein test
BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
BABASHKA_PRELOADS_TEST=true
lein test :only babashka.main-test/preloads-test
BABASHKA_PRELOADS="(require '[env-ns])"
BABASHKA_CLASSPATH_TEST=true
BABASHKA_CLASSPATH="test-resources/babashka/src_for_classpath_test/env"
lein test :only babashka.classpath-test/classpath-env-test

219
src-bash/bbk Executable file
View file

@ -0,0 +1,219 @@
#!/usr/bin/env bash
set -e
function join { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
# Extract opts
print_classpath=false
describe=false
verbose=false
force=false
repro=false
tree=false
pom=false
resolve_tags=false
help=false
resolve_aliases=()
classpath_aliases=()
main_aliases=()
all_aliases=()
while [ $# -gt 0 ]
do
case "$1" in
-J*)
shift
;;
-R*)
resolve_aliases+=("${1:2}")
shift
;;
-C*)
classpath_aliases+=("${1:2}")
shift
;;
-O*)
shift
;;
-M*)
main_aliases+=("${1:2}")
shift
;;
-A*)
all_aliases+=("${1:2}")
shift
;;
-Sdeps)
shift
deps_data="${1}"
shift
;;
-Scp)
shift
force_cp="${1}"
shift
;;
-Spath)
print_classpath=true
shift
;;
-Sverbose)
verbose=true
shift
;;
-Sdescribe)
describe=true
shift
;;
-Sforce)
force=true
shift
;;
-Srepro)
repro=true
shift
;;
-Stree)
tree=true
shift
;;
-Spom)
pom=true
shift
;;
-Sresolve-tags)
resolve_tags=true
shift
;;
-S*)
echo "Invalid option: $1"
exit 1
;;
-h|--help|"-?")
if [[ ${#main_aliases[@]} -gt 0 ]] || [[ ${#all_aliases[@]} -gt 0 ]]; then
break
else
help=true
shift
fi
;;
*)
break
;;
esac
done
# Find clojure executable
set +e
CLOJURE_CMD=$(type -p clojure)
set -e
if [[ ! -n "$CLOJURE_CMD" ]]; then
>&2 echo "Couldn't find 'clojure'."
>&2 echo "You can launch Babashka directly using 'bb'."
>&2 echo "To use 'bbk', please ensure 'clojure' is installed and on"
>&2 echo "your path. See https://clojure.org/guides/getting_started"
exit 1
fi
if "$help"; then
cat <<-END
Usage: bbk [dep-opt*] [bb-opt*] [arg*]
The bbk script is a runner for Babashka which ultimately constructs and
invokes a command-line of the form:
bb --classpath classpath [bb-opt*] [*args]
The dep-opts are used to build the classpath using the clojure tool:
-Ralias... Concatenated resolve-deps aliases, ex: -R:bench:1.9
-Calias... Concatenated make-classpath aliases, ex: -C:dev
-Malias... Concatenated main option aliases, ex: -M:test
-Aalias... Concatenated aliases of any kind, ex: -A:dev:mem
-Sdeps EDN Deps data to use as the final deps file
-Spath Compute classpath and echo to stdout only
-Scp CP Do NOT compute or cache classpath, use this one instead
-Srepro Ignore the ~/.clojure/deps.edn config file
-Sforce Force recomputation of the classpath (don't use the cache)
-Spom Generate (or update existing) pom.xml with deps and paths
-Stree Print dependency tree
-Sresolve-tags Resolve git coordinate tags to shas and update deps.edn
-Sverbose Print important path info to console
-Sdescribe Print environment and command parsing info as data
Additionally, for compatibility with clojure, -Jopt and -Oalias... dep-opts
are accepted but ignored.
Babashka options:
END
bb -h | tail -n +9
exit 0
fi
# Execute resolve-tags command
if "$resolve_tags"; then
"$CLOJURE_CMD" -Sresolve-tags
exit
fi
clojure_args=()
if [[ -n "$deps_data" ]]; then
clojure_args+=("-Sdeps" "$deps_data")
fi
if [[ ${#resolve_aliases[@]} -gt 0 ]]; then
clojure_args+=("-R$(join '' ${resolve_aliases[@]})")
fi
if [[ ${#classpath_aliases[@]} -gt 0 ]]; then
clojure_args+=("-C$(join '' ${classpath_aliases[@]})")
fi
if [[ ${#main_aliases[@]} -gt 0 ]]; then
clojure_args+=("-M$(join '' ${main_aliases[@]})")
fi
if [[ ${#all_aliases[@]} -gt 0 ]]; then
clojure_args+=("-A$(join '' ${all_aliases[@]})")
fi
if "$repro"; then
clojure_args+=("-Srepro")
fi
if "$force"; then
clojure_args+=("-Sforce")
fi
if "$pom"; then
if "$verbose"; then
clojure_args+=("-Sverbose")
fi
"$CLOJURE_CMD" "${clojure_args[@]}" -Spom
elif "$describe"; then
if "$verbose"; then
clojure_args+=("-Sverbose")
fi
"$CLOJURE_CMD" "${clojure_args[@]}" -Sdescribe
elif "$tree"; then
if "$verbose"; then
clojure_args+=("-Sverbose")
fi
"$CLOJURE_CMD" "${clojure_args[@]}" -Stree
else
set -f
if [[ -n "$force_cp" ]]; then
cp="$force_cp"
else
if "$verbose"; then
"$CLOJURE_CMD" "${clojure_args[@]}" -Sverbose -e nil
fi
cp=`"$CLOJURE_CMD" "${clojure_args[@]}" -Spath`
fi
if "$print_classpath"; then
echo $cp
else
if [[ ${#main_aliases[@]} -gt 0 ]] || [[ ${#all_aliases[@]} -gt 0 ]]; then
# Attempt to extract the main cache filename by parsing the output of -Sverbose
cp_file=`"$CLOJURE_CMD" "${clojure_args[@]}" -Sverbose -Spath | grep cp_file | cut -d = -f 2 | sed 's/^ *//g'`
main_file="${cp_file%.cp}.main"
fi
if [[ -e "$main_file" ]]; then
main_cache_opts=($(cat "$main_file"))
fi
exec bb --classpath "$cp" "${main_cache_opts[@]}" "$@"
fi
fi

View file

@ -4,7 +4,7 @@
(defn thread
[_ _ & body]
`(~'async/thread-call (fn [] ~@body)))
`(~'clojure.core.async/thread-call (fn [] ~@body)))
(def async-namespace
{'<!! async/<!!

View file

@ -0,0 +1,14 @@
(ns babashka.impl.cheshire
{:no-doc true}
(:require [cheshire.core :as json]))
(def cheshire-core-namespace
{'encode json/encode
'generate-string json/generate-string
'encode-stream json/encode-stream
'generate-stream json/generate-stream
'encode-smile json/encode-smile
'generate-smile json/generate-smile
'decode json/decode
'parse-string json/parse-string
'decode-smile json/decode-smile})

View file

@ -0,0 +1,62 @@
(ns babashka.impl.classpath
{:no-doc true}
(:require [clojure.java.io :as io]
[clojure.string :as str])
(:import [java.util.jar JarFile JarFile$JarFileEntry]))
(set! *warn-on-reflection* true)
(defprotocol IResourceResolver
(getResource [this path]))
(deftype DirectoryResolver [path]
IResourceResolver
(getResource [this resource-path]
(let [f (io/file path resource-path)]
(when (.exists f)
(slurp f)))))
(defn path-from-jar
[^java.io.File jar-file path]
(with-open [jar (JarFile. jar-file)]
(let [entries (enumeration-seq (.entries jar))
entry (some (fn [^JarFile$JarFileEntry x]
(let [nm (.getName x)]
(when (and (not (.isDirectory x)) (= path nm))
(slurp (.getInputStream jar x))))) entries)]
entry)))
(deftype JarFileResolver [path]
IResourceResolver
(getResource [this resource-path]
(path-from-jar path resource-path)))
(defn part->entry [part]
(if (str/ends-with? part ".jar")
(JarFileResolver. (io/file part))
(DirectoryResolver. (io/file part))))
(deftype Loader [entries]
IResourceResolver
(getResource [this resource-path]
(some #(getResource % resource-path) entries)))
(defn loader [^String classpath]
(let [parts (.split classpath (System/getProperty "path.separator"))
entries (map part->entry parts)]
(Loader. entries)))
(defn source-for-namespace [loader namespace]
(let [ns-str (name namespace)
^String ns-str (munge ns-str)
path (.replace ns-str "." (System/getProperty "file.separator"))
paths (map #(str path %) [".bb" ".clj" ".cljc"])]
(some #(getResource loader %) paths)))
;;;; Scratch
(comment
(def l (loader "src:/Users/borkdude/.m2/repository/cheshire/cheshire/5.9.0/cheshire-5.9.0.jar"))
(source-for-namespace l 'babashka.impl.cheshire)
(source-for-namespace l 'cheshire.core)
)

View file

@ -2,65 +2,8 @@
{:no-doc true}
(:refer-clojure :exclude [future]))
(defn future
[_ _ & body]
`(~'future-call (fn [] ~@body)))
(defn __close!__ [^java.io.Closeable x]
(.close x))
(defn with-open*
[_ _ bindings & body]
(cond
(= (count bindings) 0) `(do ~@body)
(symbol? (bindings 0)) `(let ~(subvec bindings 0 2)
(try
(with-open ~(subvec bindings 2) ~@body)
(finally
(~'__close!__ ~(bindings 0)))))
:else (throw (IllegalArgumentException.
"with-open only allows Symbols in bindings"))))
(defn binding*
"This macro only works with symbols that evaluate to vars themselves. See `*in*` and `*out*` below."
[_ _ bindings & body]
`(do
(let []
(push-thread-bindings (hash-map ~@bindings))
(try
~@body
(finally
(pop-thread-bindings))))))
;; this works now!
"(def w (java.io.StringWriter.)) (push-thread-bindings {clojure.core/*out* w}) (try (println \"hello\") (finally (pop-thread-bindings))) (prn \">\" (str w))"
;; this also works now! "(def w (java.io.StringWriter.)) (binding [clojure.core/*out* w] (println \"hello\")) (str w)"
(defn with-out-str*
[_ _ & body]
`(let [s# (java.io.StringWriter.)]
(binding [*out* s#]
~@body
(str s#))))
(defn with-in-str*
[_ _ s & body]
`(with-open [s# (-> (java.io.StringReader. ~s) clojure.lang.LineNumberingPushbackReader.)]
(binding [*in* s#]
~@body)))
(def core-extras
{'*in* #'*in*
'*out* #'*out*
'binding (with-meta binding* {:sci/macro true})
'file-seq file-seq
'future-call future-call
'future (with-meta future {:sci/macro true})
'future-cancel future-cancel
'future-cancelled? future-cancelled?
'future-done? future-done?
'future? future?
{'file-seq file-seq
'agent agent
'send send
'send-off send-off
@ -68,18 +11,4 @@
'deliver deliver
'shutdown-agents shutdown-agents
'slurp slurp
'spit spit
'pmap pmap
'pr pr
'prn prn
'print print
'println println
'println-str println-str
'pop-thread-bindings pop-thread-bindings
'push-thread-bindings push-thread-bindings
'flush flush
'read-line read-line
'__close!__ __close!__
'with-open (with-meta with-open* {:sci/macro true})
'with-out-str (with-meta with-out-str* {:sci/macro true})
'with-in-str (with-meta with-in-str* {:sci/macro true})})
'spit spit})

View file

@ -0,0 +1,54 @@
(ns babashka.impl.repl
{:no-doc true}
(:require
[babashka.impl.clojure.main :as m]
[clojure.java.io :as io]
[clojure.string :as str]
[clojure.tools.reader.reader-types :as r]
[sci.core :as sci]
[sci.impl.interpreter :refer [eval-form]]
[sci.impl.opts :refer [init]]
[sci.impl.parser :as parser]))
(defn repl
"REPL with predefined hooks for attachable socket server."
[sci-ctx]
(let [in (r/indexing-push-back-reader (r/push-back-reader *in*))]
(m/repl
:init #(do (println "Babashka"
(str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
"REPL.")
(println "Use :repl/quit or :repl/exit to quit the REPL.")
(println "Clojure rocks, Bash reaches.")
(println))
:read (fn [_request-prompt request-exit]
(if (r/peek-char in) ;; if this is nil, we reached EOF
(let [v (parser/parse-next sci-ctx in)]
(if (or (identical? :repl/quit v)
(identical? :repl/exit v)
(identical? :edamame.impl.parser/eof v))
request-exit
v))
request-exit))
:eval (fn [expr]
(let [ret (sci/with-bindings {sci/in *in*
sci/out *out*
sci/err *err*}
(eval-form (update sci-ctx
:env
(fn [env]
(swap! env update-in [:namespaces 'clojure.core]
assoc
'*1 *1
'*2 *2
'*3 *3
'*e *e)
env))
expr))]
ret))
:need-prompt (fn [] true)
:prompt #(printf "%s=> " (-> sci-ctx :env deref :current-ns)))))
(defn start-repl! [ctx]
(let [sci-ctx (init ctx)]
(repl sci-ctx)))

View file

@ -2,51 +2,12 @@
{:no-doc true}
(:require
[babashka.impl.clojure.core.server :as server]
[babashka.impl.clojure.main :as m]
[clojure.java.io :as io]
[babashka.impl.repl :as repl]
[clojure.string :as str]
[clojure.tools.reader.reader-types :as r]
[sci.impl.interpreter :refer [opts->ctx eval-form]]
[sci.impl.parser :as parser]))
[sci.impl.opts :refer [init]]))
(set! *warn-on-reflection* true)
(defn repl
"REPL with predefined hooks for attachable socket server."
[sci-ctx]
(let [in (r/indexing-push-back-reader (r/push-back-reader *in*))]
(m/repl
:init #(do (println "Babashka"
(str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
"REPL.")
(println "Use :repl/quit or :repl/exit to quit the REPL.")
(println "Clojure rocks, Bash reaches.")
(println))
:read (fn [_request-prompt request-exit]
(if (r/peek-char in) ;; if this is nil, we reached EOF
(let [v (parser/parse-next in #{:bb} {:current (-> sci-ctx :env deref :current-ns)})]
(if (or (identical? :repl/quit v)
(identical? :repl/exit v)
(identical? :edamame.impl.parser/eof v))
request-exit
v))
request-exit))
:eval (fn [expr]
(let [ret (eval-form (update sci-ctx
:env
(fn [env]
(swap! env update-in [:namespaces 'clojure.core]
assoc
'*1 *1
'*2 *2
'*3 *3
'*e *e)
env))
expr)]
ret))
:need-prompt (fn [] true)
:prompt #(printf "%s=> " (-> sci-ctx :env deref :current-ns)))))
(defn start-repl! [host+port sci-opts]
(let [parts (str/split host+port #":")
[host port] (if (= 1 (count parts))
@ -54,12 +15,12 @@
[(first parts) (Integer. ^String (second parts))])
host+port (if-not host (str "localhost:" port)
host+port)
sci-ctx (opts->ctx sci-opts)
sci-ctx (init sci-opts)
socket (server/start-server
{:address host
:port port
:name "bb"
:accept babashka.impl.socket-repl/repl
:accept babashka.impl.repl/repl
:args [sci-ctx]})]
(println "Babashka socket REPL started at" host+port)
socket))

View file

@ -0,0 +1,9 @@
(ns babashka.impl.utils
{:no-doc true}
(:require [sci.core :as sci]))
(defn eval-string [expr ctx]
(sci/with-bindings {sci/out *out*
sci/in *in*
sci/err *err*}
(sci/eval-string expr ctx)))

View file

@ -2,21 +2,25 @@
{:no-doc true}
(:require
[babashka.impl.async :refer [async-namespace]]
[babashka.impl.cheshire :refer [cheshire-core-namespace]]
[babashka.impl.classpath :as cp]
[babashka.impl.clojure.core :refer [core-extras]]
[babashka.impl.clojure.java.io :refer [io-namespace]]
[babashka.impl.clojure.stacktrace :refer [print-stack-trace]]
[babashka.impl.conch :refer [conch-namespace]]
[babashka.impl.csv :as csv]
[babashka.impl.pipe-signal-handler :refer [handle-pipe! pipe-signal-received?]]
[babashka.impl.repl :as repl]
[babashka.impl.socket-repl :as socket-repl]
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
[io.aviso.ansi :as ansi]
[babashka.impl.utils :refer [eval-string]]
[babashka.wait :as wait]
[clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.java.shell :as shell]
[clojure.string :as str]
[sci.core :as sci])
[io.aviso.ansi :as ansi]
[sci.addons :as addons])
(:gen-class))
(set! *warn-on-reflection* true)
@ -65,6 +69,11 @@
(recur (rest options)
(assoc opts-map
:file (first options))))
("--repl")
(let [options (rest options)]
(recur (rest options)
(assoc opts-map
:repl true)))
("--socket-repl")
(let [options (rest options)]
(recur (rest options)
@ -74,7 +83,15 @@
(let [options (rest options)]
(recur (rest options)
(assoc opts-map :expression (first options))))
(if (some opts-map [:file :socket-repl :expression])
("--classpath", "-cp")
(let [options (rest options)]
(recur (rest options)
(assoc opts-map :classpath (first options))))
("--main", "-m")
(let [options (rest options)]
(recur (rest options)
(assoc opts-map :main (first options))))
(if (some opts-map [:file :socket-repl :expression :main])
(assoc opts-map
:command-line-args options)
(if (and (not= \( (first (str/trim opt)))
@ -104,31 +121,36 @@
(defn print-version []
(println (str "babashka v"(str/trim (slurp (io/resource "BABASHKA_VERSION"))))))
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [--verbose] [ --stream ] ( -e <expression> | -f <file> | --socket-repl [<host>:]<port> )")
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
[ arg* ]")
(defn print-usage []
(println usage-string))
(defn print-help []
(println (str "babashka v" (str/trim (slurp (io/resource "BABASHKA_VERSION")))))
(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, -h or -?: print this help text.
--version: print the current version of babashka.
-i: bind *in* to a lazy seq of lines from stdin.
-I: bind *in* to a lazy seq of EDN values from stdin.
-o: write lines to stdout.
-O: write EDN values to stdout.
--verbose: print entire stacktrace in case of exception.
--stream: stream over lines or EDN values from stdin. Combined with -i or -I *in* becomes a single value per iteration.
-e, --eval <expression>: evaluate an expression
-f, --file <path>: evaluate a file
--socket-repl: start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--time: print execution time before exiting.
--help, -h or -? Print this help text.
--version Print the current version of babashka.
-i Bind *in* to a lazy seq of lines from stdin.
-I Bind *in* to a lazy seq of EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--verbose Print entire stacktrace in case of exception.
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *in* becomes a single value per iteration.
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--repl Start REPL
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--time Print execution time before exiting.
If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise.
Everything after that is bound to *command-line-args*."))
@ -147,10 +169,17 @@ Everything after that is bound to *command-line-args*."))
(defn load-file* [ctx file]
(let [s (slurp file)]
(sci/eval-string s ctx)))
(eval-string s ctx)))
(defn eval* [ctx form]
(sci/eval-string (pr-str form) ctx))
(eval-string (pr-str form) ctx))
(defn start-repl! [ctx read-next]
(let [ctx (update ctx :bindings assoc
(with-meta '*in*
{:sci/deref! true})
(read-next))]
(repl/start-repl! ctx)))
(defn start-socket-repl! [address ctx read-next]
(let [ctx (update ctx :bindings assoc
@ -164,6 +193,10 @@ Everything after that is bound to *command-line-args*."))
(defn exit [n]
(throw (ex-info "" {:bb/exit-code n})))
;; (sci/set-var-root! sci/*in* *in*)
;; (sci/set-var-root! sci/*out* *out*)
;; (sci/set-var-root! sci/*err* *err*)
(defn main
[& args]
(handle-pipe!)
@ -172,7 +205,10 @@ Everything after that is bound to *command-line-args*."))
(let [t0 (System/currentTimeMillis)
{:keys [:version :shell-in :edn-in :shell-out :edn-out
:help? :file :command-line-args
:expression :stream? :time? :socket-repl :verbose?] :as _opts}
:expression :stream? :time?
:repl :socket-repl
:verbose? :classpath
:main] :as _opts}
(parse-opts args)
read-next (fn [*in*]
(if (pipe-signal-received?)
@ -187,6 +223,13 @@ Everything after that is bound to *command-line-args*."))
:else
(edn/read *in*))))))
env (atom {})
classpath (or classpath
(System/getenv "BABASHKA_CLASSPATH"))
loader (when classpath
(cp/loader classpath))
load-fn (when classpath
(fn [{:keys [:namespace]}]
(cp/source-for-namespace loader namespace)))
ctx {:aliases '{tools.cli 'clojure.tools.cli
edn clojure.edn
wait babashka.wait
@ -195,7 +238,8 @@ Everything after that is bound to *command-line-args*."))
io clojure.java.io
conch me.raynes.conch.low-level
async clojure.core.async
csv clojure.data.csv}
csv clojure.data.csv
json cheshire.core}
:namespaces {'clojure.core (assoc core-extras
'*command-line-args* command-line-args)
'clojure.tools.cli tools-cli-namespace
@ -208,6 +252,7 @@ Everything after that is bound to *command-line-args*."))
'me.raynes.conch.low-level conch-namespace
'clojure.core.async async-namespace
'clojure.data.csv csv/csv-namespace
'cheshire.core cheshire-core-namespace
'io.aviso.ansi {'blue ansi/blue
'red ansi/red}}
:bindings {'java.lang.System/exit exit ;; override exit, so we have more control
@ -218,6 +263,7 @@ Everything after that is bound to *command-line-args*."))
'java.lang.AssertionError AssertionError
'java.lang.Boolean Boolean
'java.io.BufferedWriter java.io.BufferedWriter
'java.io.BufferedReader java.io.BufferedReader
'java.lang.Class Class
'java.lang.Double Double
'java.lang.Exception Exception
@ -232,6 +278,9 @@ Everything after that is bound to *command-line-args*."))
'java.lang.System System
'java.lang.Thread Thread
'sun.nio.fs.UnixPath sun.nio.fs.UnixPath
'java.nio.file.attribute.FileAttribute java.nio.file.attribute.FileAttribute
'java.nio.file.attribute.PosixFilePermission java.nio.file.attribute.PosixFilePermission
'java.nio.file.attribute.PosixFilePermissions java.nio.file.attribute.PosixFilePermissions
'java.nio.file.CopyOption java.nio.file.CopyOption
'java.nio.file.FileAlreadyExistsException java.nio.file.FileAlreadyExistsException
'java.nio.file.Files java.nio.file.Files
@ -248,10 +297,16 @@ Everything after that is bound to *command-line-args*."))
File java.io.File
String java.lang.String
System java.lang.System
Thread java.lang.Thread}}
Thread java.lang.Thread}
:load-fn load-fn}
ctx (update ctx :bindings assoc 'eval #(eval* ctx %)
'load-file #(load-file* ctx %))
_preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (sci/eval-string ctx))
'load-file #(load-file* ctx %))
ctx (addons/future ctx)
_preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (eval-string ctx))
expression (if main
(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
main)
expression)
exit-code
(or
#_(binding [*out* *err*]
@ -261,6 +316,7 @@ Everything after that is bound to *command-line-args*."))
[(print-version) 0]
help?
[(print-help) 0]
repl [(start-repl! ctx #(read-next *in*)) 0]
socket-repl [(start-socket-repl! socket-repl ctx #(read-next *in*)) 0]
:else
(try
@ -272,7 +328,7 @@ Everything after that is bound to *command-line-args*."))
{:sci/deref! true})) in)]
(if (identical? ::EOF in)
[nil 0] ;; done streaming
(let [res [(let [res (sci/eval-string expr ctx)]
(let [res [(let [res (eval-string expr ctx)]
(when (some? res)
(if-let [pr-f (cond shell-out println
edn-out prn)]
@ -285,7 +341,7 @@ Everything after that is bound to *command-line-args*."))
(if stream?
(recur (read-next *in*))
res)))))
[(print-help) 1]))
[(start-repl! ctx #(read-next *in*)) 0]))
(catch Throwable e
(binding [*out* *err*]
(let [d (ex-data e)

View file

@ -0,0 +1,4 @@
(ns env-ns)
(defn foo []
"env!")

Binary file not shown.

View file

@ -0,0 +1,5 @@
(ns my.impl)
(defn impl-fn
"identity"
[x] x)

View file

@ -0,0 +1,5 @@
(ns my.main
(:require [my.impl :as impl]))
(defn -main [& args]
(impl/impl-fn args))

View file

@ -0,0 +1,4 @@
(ns my-script)
(defn foo []
::bb)

View file

@ -0,0 +1,27 @@
(ns babashka.classpath-test
(:require
[babashka.test-utils :as tu]
[clojure.edn :as edn]
[clojure.test :as t :refer [deftest is]]))
(defn bb [input & args]
(edn/read-string (apply tu/bb (when (some? input) (str input)) (map str args))))
(deftest classpath-test
(is (= :my-script/bb
(bb nil "--classpath" "test-resources/babashka/src_for_classpath_test"
"(require '[my-script :as ms]) (ms/foo)")))
(is (= "hello from foo\n"
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test/foo.jar"
"(require '[foo :as f]) (f/foo)"))))
(deftest classpath-env-test
;; for this test you have to set `BABASHKA_CLASSPATH` to test-resources/babashka/src_for_classpath_test/env
;; and `BABASHKA_PRELOADS` to "(require '[env-ns])"
(when (System/getenv "BABASHKA_CLASSPATH_TEST")
(println (System/getenv "BABASHKA_CLASSPATH"))
(is (= "env!" (bb nil "(env-ns/foo)")))))
(deftest main-test
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "1" "2" "3" "4"))))

View file

@ -0,0 +1,35 @@
(ns babashka.impl.repl-test
(:require
[babashka.impl.repl :refer [start-repl!]]
[clojure.string :as str]
[clojure.test :as t :refer [deftest is]]))
(set! *warn-on-reflection* true)
(defn repl! []
(start-repl! {:bindings {(with-meta '*in*
{:sci/deref! true})
(delay [1 2 3])
'*command-line-args*
["a" "b" "c"]}
:env (atom {})}))
(defn assert-repl [expr expected]
(is (str/includes? (with-out-str
(with-in-str (str expr "\n:repl/quit")
(repl!))) expected)))
(deftest repl-test
(assert-repl "(+ 1 2 3)" "6")
(assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "6")
(assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "6")
(assert-repl "1\n(inc *1)" "2")
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
(assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]")
(assert-repl "*in*" "[1 2 3]"))
;;;; Scratch
(comment
)

View file

@ -4,19 +4,29 @@
[babashka.test-utils :as tu]
[clojure.java.shell :refer [sh]]
[clojure.string :as str]
[clojure.test :as t :refer [deftest is testing]]))
[clojure.test :as t :refer [deftest is testing]]
[clojure.java.io :as io]))
(def mac?
(str/includes?
(str/lower-case (System/getProperty "os.name"))
"mac"))
(set! *warn-on-reflection* true)
(defn socket-command [expr]
(let [expr (format "echo \"%s\n:repl/exit\" | nc 127.0.0.1 1666"
(pr-str expr))
ret (sh "bash" "-c"
expr)]
(:out ret)))
(defn socket-command [expr expected]
(with-open [socket (java.net.Socket. "127.0.0.1" 1666)
reader (io/reader socket)
sw (java.io.StringWriter.)
writer (io/writer socket)]
(binding [*out* writer]
(println (str expr))
(println ":repl/exit\n"))
(loop []
(when-let [l (.readLine ^java.io.BufferedReader reader)]
(binding [*out* sw]
(println l))
(recur)))
(let [s (str sw)]
(is (str/includes? s expected)
(format "\"%s\" does not contain \"%s\""
s expected))
s)))
(deftest socket-repl-test
(try
@ -26,7 +36,8 @@
(delay [1 2 3])
'*command-line-args*
["a" "b" "c"]}
:env (atom {})})
:env (atom {})
:features #{:bb}})
(future
(sh "bash" "-c"
"echo '[1 2 3]' | ./bb --socket-repl 0.0.0.0:1666 a b c")))
@ -35,36 +46,19 @@
(while (not (zero? (:exit
(sh "bash" "-c"
"lsof -t -i:1666"))))))
(is (str/includes? (socket-command '(+ 1 2 3))
"user=> 6"))
(testing "ctrl-d exits normally, doesn't print nil"
(is (str/ends-with? (:out (sh "bash" "-c"
(if mac? ;; mac doesn't support -q
"echo \"(inc 1336)\" | nc 127.0.0.1 1666"
"echo \"(inc 1336)\" | nc -q 1 127.0.0.1 1666")))
"1337\nuser=> ")))
(is (socket-command "(+ 1 2 3)" "user=> 6"))
(testing "*in*"
(is (str/includes? (socket-command "*in*")
"[1 2 3]")))
(is (socket-command "*in*" "[1 2 3]")))
(testing "*command-line-args*"
(is (str/includes? (socket-command '*command-line-args*)
"\"a\" \"b\" \"c\"")))
(is (socket-command '*command-line-args* "\"a\" \"b\" \"c\"")))
(testing "&env"
(socket-command '(defmacro bindings [] (mapv #(list 'quote %) (keys &env))))
(socket-command '(defn bar [x y z] (bindings)))
(is (str/includes? (socket-command '(bar 1 2 3))
"[x y z]")))
(socket-command "(defmacro bindings [] (mapv #(list 'quote %) (keys &env)))" "bindings")
(socket-command "(defn bar [x y z] (bindings))" "bar")
(is (socket-command "(bar 1 2 3)" "[x y z]")))
(testing "reader conditionals"
(is (str/includes? (let [ret (sh "bash" "-c"
(format "echo \"%s\n:repl/exit\" | nc 127.0.0.1 1666"
"#?(:bb 1337 :clj 8888)"))]
(:out ret))
"1337")))
(is (socket-command "#?(:bb 1337 :clj 8888)" "1337")))
(testing "*1, *2, *3, *e"
(is (= 2 (count (re-seq #"1\n" (let [ret (sh "bash" "-c"
(format "echo \"%s\n*1\n:repl/exit\" | nc 127.0.0.1 1666"
"1"))]
(:out ret)))))))
(is (socket-command "1\n*1" "1")))
(finally
(if tu/jvm?
(stop-repl!)
@ -75,4 +69,14 @@
(comment
(socket-repl-test)
(dotimes [_ 1000]
(t/run-tests))
(stop-repl!)
(start-repl! "0.0.0.0:1666" {:bindings {(with-meta '*in*
{:sci/deref! true})
(delay [1 2 3])
'*command-line-args*
["a" "b" "c"]}
:env (atom {})})
(socket-command "(+ 1 2 3)" "6")
)

View file

@ -6,10 +6,11 @@
[clojure.java.shell :refer [sh]]
[clojure.string :as str]
[clojure.test :as test :refer [deftest is testing]]
[clojure.java.io :as io]))
[clojure.java.io :as io]
[sci.core :as sci]))
(defn bb [input & args]
(edn/read-string (apply test-utils/bb (str input) (map str args))))
(edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args))))
(deftest parse-opts-test
(is (= {:expression "(println 123)"}
@ -82,6 +83,9 @@
"(map-indexed #(-> [%1 %2]) *in*)")
(bb "(keep #(when (re-find #\"(?i)clojure\" (second %)) (first %)) *in*)"))))))
(deftest println-test
(is (= "hello\n" (test-utils/bb nil "(println \"hello\")"))))
(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)"))))))
@ -93,29 +97,20 @@
(is (not-empty s))))
(let [out (java.io.StringWriter.)
err (java.io.StringWriter.)
exit-code (binding [*out* out *err* err]
(main/main "--time" "(println \"Hello world!\") (System/exit 42)"))]
exit-code (sci/with-bindings {sci/out out
sci/err err}
(binding [*out* out *err* err]
(main/main "--time" "(println \"Hello world!\") (System/exit 42)")))]
(is (= (str out) "Hello world!\n"))
(is (re-find #"took" (str err)))
(is (= 42 exit-code))))
(deftest malformed-command-line-args-test
(is (thrown-with-msg? Exception #"File does not exist: non-existing\n"
(bb nil "-f" "non-existing")))
(testing "no arguments prints help"
(is (str/includes?
(try (test-utils/bb nil)
(catch clojure.lang.ExceptionInfo e
(:stdout (ex-data e))))
"Usage:"))))
(bb nil "-f" "non-existing"))))
(deftest ssl-test
(let [graalvm-home (System/getenv "GRAALVM_HOME")
lib-path (format "%1$s/jre/lib:%1$s/jre/lib/amd64" graalvm-home)
;; _ (prn "lib-path" lib-path)
resp (bb nil (format "(System/setProperty \"java.library.path\" \"%s\")
(slurp \"https://www.google.com\")"
lib-path))]
(let [resp (bb nil "(slurp \"https://www.google.com\")")]
(is (re-find #"doctype html" resp))))
(deftest stream-test
@ -140,7 +135,8 @@
(deftest preloads-test
;; THIS TEST REQUIRES:
;; export BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")'
(is (= "foobar" (bb nil "(str (__bb__foo) (__bb__bar))"))))
(when (System/getenv "BABASHKA_PRELOADS_TEST")
(is (= "foobar" (bb nil "(str (__bb__foo) (__bb__bar))")))))
(deftest io-test
(is (true? (bb nil "(.exists (io/file \"README.md\"))")))
@ -288,3 +284,9 @@
(java.nio.file.Files/copy p p' (into-array [java.nio.file.StandardCopyOption/REPLACE_EXISTING]))))))"
temp-path))
(is (.exists f2))))
(deftest future-print-test
(testing "the root binding of sci/*out*"
(is (= "hello" (bb nil "@(future (prn \"hello\"))"))))
)

View file

@ -1,19 +1,25 @@
(ns babashka.test-utils
(:require
[babashka.main :as main]
[me.raynes.conch :refer [let-programs] :as sh]))
[me.raynes.conch :refer [let-programs] :as sh]
[sci.core :as sci]))
(set! *warn-on-reflection* true)
(defn bb-jvm [input & args]
(let [es (java.io.StringWriter.)
os (java.io.StringWriter.)]
(binding [*err* es
*out* os]
(let [res (if input
(with-in-str input
(apply main/main args))
(apply main/main args))]
(let [os (java.io.StringWriter.)
es (java.io.StringWriter.)
is (when input
(java.io.StringReader. input))
bindings-map (cond-> {sci/out os
sci/err es}
is (assoc sci/in is))]
(sci/with-bindings bindings-map
(let [res (binding [*out* os
*err* es]
(if input
(with-in-str input (apply main/main args))
(apply main/main args)))]
(if (zero? res)
(str os)
(throw (ex-info (str es)