diff --git a/.carve_ignore b/.carve_ignore
new file mode 100644
index 00000000..765c627a
--- /dev/null
+++ b/.carve_ignore
@@ -0,0 +1,3 @@
+babashka.impl.clojure.stacktrace/root-cause
+babashka.impl.classes/generate-reflection-file
+babashka.main/-main
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 16e2cbbb..c25a8ea2 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -11,7 +11,7 @@ jobs:
working_directory: ~/repo
environment:
LEIN_ROOT: "true"
- # GRAALVM_HOME: /home/circleci/graalvm-ce-java8-19.3.0
+ BABASHKA_PLATFORM: linux # could be used in jar name
steps:
- checkout
- run:
@@ -34,18 +34,6 @@ jobs:
name: Install lsof
command: |
sudo apt-get install lsof
- # - run:
- # name: Download GraalVM
- # command: |
- # cd ~
- # if ! [ -d graalvm-ce-java8-19.3.0 ]; then
- # curl -O -sL https://github.com/oracle/graal/releases/download/vm-19.2.0/graalvm-ce-linux-amd64-19.2.0.tar.gz
- # tar xzf graalvm-ce-linux-amd64-19.2.0.tar.gz
- # fi
- # - run:
- # name: Install GraalVM SSL libs
- # command: |
- # .circleci/script/graalvm_ssl
- run:
name: Run JVM tests
command: |
@@ -58,6 +46,16 @@ jobs:
name: Run as lein command
command: |
.circleci/script/lein
+ - run:
+ name: Create uberjar
+ command: |
+ mkdir -p /tmp/release
+ lein do clean, uberjar
+ VERSION=$(cat resources/BABASHKA_VERSION)
+ cp target/babashka-$VERSION-standalone.jar /tmp/release/babashka-$VERSION-standalone.jar
+ - store_artifacts:
+ path: /tmp/release
+ destination: release
- save_cache:
paths:
- ~/.m2
@@ -117,6 +115,7 @@ jobs:
name: Run tests
command: |
script/test
+ script/run_lib_tests
# - run:
# name: Performance report
# command: |
@@ -133,6 +132,10 @@ jobs:
- store_artifacts:
path: /tmp/release
destination: release
+ - run:
+ name: Publish artifact link to Slack
+ command: |
+ ./bb .circleci/script/publish_artifact.clj
mac:
macos:
xcode: "9.0"
@@ -180,6 +183,7 @@ jobs:
name: Run tests
command: |
script/test
+ script/run_lib_tests
# - run:
# name: Performance report
# command: |
@@ -196,6 +200,10 @@ jobs:
- store_artifacts:
path: /tmp/release
destination: release
+ - run:
+ name: Publish artifact link to Slack
+ command: |
+ ./bb .circleci/script/publish_artifact.clj
deploy:
docker:
- image: circleci/clojure:lein-2.8.1
diff --git a/.circleci/script/publish_artifact.clj b/.circleci/script/publish_artifact.clj
new file mode 100755
index 00000000..cb55f321
--- /dev/null
+++ b/.circleci/script/publish_artifact.clj
@@ -0,0 +1,19 @@
+(require '[clojure.java.shell :refer [sh]]
+ '[cheshire.core :refer [generate-string]])
+
+(def channel "#babashka_circleci_builds")
+#_(def channel "#_test")
+
+(def text (format "[%s - %s@%s]: https://%s-201467090-gh.circle-artifacts.com/0/release/babashka-0.0.61-SNAPSHOT-%s-amd64.zip"
+ (System/getenv "BABASHKA_PLATFORM")
+ (System/getenv "CIRCLE_BRANCH")
+ (System/getenv "CIRCLE_SHA1")
+ (System/getenv "CIRCLE_BUILD_NUM")
+ (System/getenv "BABASHKA_PLATFORM")))
+
+(def slack-hook-url (System/getenv "SLACK_HOOK_URL"))
+(when slack-hook-url
+ (let [json (generate-string {:username "borkdude"
+ :channel channel
+ :text text})]
+ (sh "curl" "-X" "POST" "-H" "Content-Type: application/json" "-d" json slack-hook-url)))
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 3a14c403..08efe995 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -9,7 +9,7 @@ assignees: ''
**version**
-[ Please specify which version of babashka you're using. You can find this with `babashka --version`. The documentation on the master branch may be ahead of the most released version. You can check the docs for your version by going to cljdoc. ]
+[ Please specify which version of babashka you're using. You can find this with `babashka --version`. The documentation on the master branch may be ahead of the most released version. ]
**platform**
diff --git a/.gitignore b/.gitignore
index a96a9d65..d8a7040d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,4 @@ pom.xml.asc
!java/src/babashka/impl/LockFix.class
!test-resources/babashka/src_for_classpath_test/foo.jar
.cpcache
-deps.edn
+reflection.json
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 00000000..e75cccb9
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,11 @@
+# Changes
+
+## Breaking changes
+
+## v0.0.44 - 0.0.45
+- #173: Rename `*in*` to `*input*` (in the `user` namespace). The reason for
+ this is that itt shadowed `clojure.core/*in*` when used unqualified.
+
+## v0.0.43
+- #160: Add support for `java.lang.ProcessBuilder`. See docs. This replaces the
+ `conch` namespace.
diff --git a/README.md b/README.md
index 65771518..ffb50eaa 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,24 @@
-# babashka
+
[](https://circleci.com/gh/borkdude/babashka/tree/master)
[](https://clojars.org/borkdude/babashka)
-[](https://cljdoc.org/d/borkdude/babashka/CURRENT)
[](https://app.slack.com/client/T03RZGPFR/CLX41ASCS)
+
A Clojure [babushka](https://en.wikipedia.org/wiki/Headscarf) for the grey areas of Bash.
+
+
## Quickstart
``` shellsession
$ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install)
-$ ls | bb --time -i '(filter #(-> % io/file .isDirectory) *in*)'
+$ ls | bb --time -i '(filter #(-> % io/file .isDirectory) *input*)'
("doc" "resources" "sci" "script" "src" "target" "test")
bb took 4ms.
```
@@ -41,45 +47,42 @@ Non-goals:
* Provide a mixed Clojure/bash DSL (see portability).
* Replace existing shells. Babashka is a tool you can use inside existing shells like bash and it is designed to play well with them. It does not aim to replace them.
-Reasons why babashka may not be the right fit for your use case:
-
-- It uses [sci](https://github.com/borkdude/sci) for interpreting Clojure. Sci
-implements only a subset of Clojure and is not as performant as compiled code.
-- External libraries are not available (although you may use `load-file` for
- loading external scripts).
+Babashka uses [sci](https://github.com/borkdude/sci) for interpreting Clojure. Sci
+implements a subset of Clojure and is not as performant as compiled code. If your script is taking more than a few seconds, Clojure on the JVM may be a better fit.
Read more about the differences with Clojure [here](#differences-with-clojure).
## Status
-Experimental. Breaking changes are expected to happen at this phase.
+Experimental. Breaking changes are expected to happen at this phase. Keep an eye
+on [CHANGES.md](CHANGES.md) for a list of breaking changes.
## Examples
``` shellsession
-$ ls | bb -i '*in*'
-["LICENSE" "README.md" "bb" "doc" "pom.xml" "project.clj" "reflection.json" "resources" "script" "src" "target" "test"]
+$ ls | bb -i '*input*'
+["LICENSE" "README.md" "bb" "doc" "pom.xml" "project.clj" "resources" "script" "src" "target" "test"]
-$ ls | bb -i '(count *in*)'
+$ ls | bb -i '(count *input*)'
12
-$ bb '(vec (dedupe *in*))' <<< '[1 1 1 1 2]'
+$ bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]'
[1 2]
-$ bb '(filterv :foo *in*)' <<< '[{:foo 1} {:bar 2}]'
+$ bb '(filterv :foo *input*)' <<< '[{:foo 1} {:bar 2}]'
[{:foo 1}]
-$ bb '(#(+ %1 %2 %3) 1 2 *in*)' <<< 3
+$ bb '(#(+ %1 %2 %3) 1 2 *input*)' <<< 3
6
-$ ls | bb -i '(filterv #(re-find #"reflection" %) *in*)'
-["reflection.json"]
+$ ls | bb -i '(filterv #(re-find #"README" %) *input*)'
+["README.md"]
$ bb '(run! #(shell/sh "touch" (str "/tmp/test/" %)) (range 100))'
-$ ls /tmp/test | bb -i '*in*'
+$ ls /tmp/test | bb -i '*input*'
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]
-$ bb -O '(repeat "dude")' | bb --stream '(str *in* "rino")' | bb -I '(take 3 *in*)'
+$ bb -O '(repeat "dude")' | bb --stream '(str *input* "rino")' | bb -I '(take 3 *input*)'
("duderino" "duderino" "duderino")
```
@@ -128,27 +131,31 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk
``` shellsession
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
- [ ( --classpath | -cp ) ] [ ( --main | -m ) ]
- ( -e | -f | --repl | --socket-repl [:] )
+ [ ( --classpath | -cp ) ] [ --uberscript ]
+ [ ( --main | -m ) | -e | -f |
+ --repl | --socket-repl [:] ]
[ 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.
- --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 Evaluate an expression.
- -f, --file Evaluate a file.
- -cp, --classpath Classpath to use.
- -m, --main 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.
+ --help, -h or -? Print this help text.
+ --version Print the current version of babashka.
+
+ -i Bind *input* to a lazy seq of lines from stdin.
+ -I Bind *input* 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 *input* becomes a single value per iteration.
+ --uberscript Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
+
+ -e, --eval Evaluate an expression.
+ -f, --file Evaluate a file.
+ -cp, --classpath Classpath to use.
+ -m, --main 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*.
@@ -165,40 +172,22 @@ enumerated explicitly.
- `clojure.set` aliased as `set`
- `clojure.edn` aliased as `edn`:
- `read-string`
-- `clojure.java.shell` aliases as `shell`:
- - `sh`
+- `clojure.java.shell` aliases as `shell`
- `clojure.java.io` aliased as `io`:
- - `as-relative-path`, `copy`, `delete-file`, `file`
+ - `as-relative-path`, `as-url`, `copy`, `delete-file`, `file`, `input-stream`,
+ `make-parents`, `output-stream`, `reader`, `resource`, `writer`
+- `clojure.main`: `repl`
- [`clojure.core.async`](https://clojure.github.io/core.async/) aliased as
`async`. The `alt` and `go` macros are not available but `alts!!` does work as
it is a function.
-- [`me.raynes.conch.low-level`](https://github.com/clj-commons/conch#low-level-usage)
- aliased as `conch`
+- `clojure.stacktrace`
- [`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:
+A selection of java classes are available, see `babashka/impl/classes.clj`.
-- `ArithmeticException`
-- `AssertionError`
-- `Boolean`
-- `Class`
-- `Double`
-- `Exception`
-- `clojure.lang.ExceptionInfo`
-- `Integer`
-- `java.io.File`
-- `java.nio.Files`
-- `java.util.regex.Pattern`
-- `String`
-- `System`
-- `Thread`
-
-More classes can be added by request. See `reflection.json` and the `:classes`
-option in `main.clj`.
-
-Babashka supports `import` : `(import clojure.lang.ExceptionInfo)`.
+Babashka supports `import`: `(import clojure.lang.ExceptionInfo)`.
Babashka supports a subset of the `ns` form where you may use `:require` and `:import`:
@@ -211,11 +200,48 @@ Babashka supports a subset of the `ns` form where you may use `:require` and `:i
For the unsupported parts of the ns form, you may use [reader
conditionals](#reader-conditionals) to maintain compatibility with JVM Clojure.
-Special vars:
+### Input and output flags
-- `*in*`: contains the input read from stdin. EDN by default, multiple lines of
-text with the `-i` option, or multiple EDN values with the `-I` option.
-- `*command-line-args*`: contain the command line args
+In one-liners the `*input*` value may come in handy. It contains the input read from stdin as EDN by default. If you want to read in text, use the `-i` flag, which binds `*input*` to a lazy seq of lines of text. If you want to read multiple EDN values, use the `-I` flag. The `-o` option prints the result as lines of text. The `-O` option prints the result as lines of EDN values.
+
+The following table illustrates the combination of options for commands of the form
+
+ echo "{{Input}}" | bb {{Input flags}} {{Output flags}} "*input*"
+
+| Input | Input flags | Output flag | `*input*` | Output |
+|----------------|-------------|-------------|---------------|----------|
+| `{:a 1}`
`{:a 2}` | | | `{:a 1}` | `{:a 1}` |
+| hello
bye | `-i` | | `("hello" "bye")` | `("hello" "bye")` |
+| hello
bye | `-i` | `-o` | `("hello" "bye")` | hello
bye |
+| `{:a 1}`
`{:a 2}` | `-I` | | `({:a 1} {:a 2})` | `({:a 1} {:a 2})` |
+| `{:a 1}`
`{:a 2}` | `-I` | `-O` | `({:a 1} {:a 2})` | `{:a 1}`
`{:a 2}` |
+
+When combined with the `--stream` option, the expression is executed for each value in the input:
+
+``` clojure
+$ echo '{:a 1} {:a 2}' | bb --stream '*input*'
+{:a 1}
+{:a 2}
+```
+
+### Current file path
+
+The var `*file*` contains the full path of the file that is currently being
+executed:
+
+``` shellsession
+$ cat example.clj
+(prn *file*)
+
+$ bb example.clj
+"/Users/borkdude/example.clj"
+```
+
+### Command-line arguments
+
+Command-line arguments can be retrieved using `*command-line-args*`.
+
+### Additional functions
Additionally, babashka adds the following functions:
@@ -329,7 +355,7 @@ export BABASHKA_PRELOADS
Note that you can concatenate multiple expressions. Now you can use these functions in babashka:
``` shellsession
-$ bb '(-> (foo *in*) bar)' <<< 1
+$ bb '(-> (foo *input*) bar)' <<< 1
6
```
@@ -339,7 +365,7 @@ You can also preload an entire file using `load-file`:
export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'
```
-Note that `*in*` is not available in preloads.
+Note that `*input*` is not available in preloads.
## Classpath
@@ -361,10 +387,10 @@ Note that you can use the `clojure` tool to produce classpaths and download depe
``` shellsession
$ cat deps.edn
{:deps
- {my_gist_script
- {:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
- :sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}}
-
+ {my_gist_script
+ {:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
+ :sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}
+ :aliases {:my-script {:main-opts ["-m" "my-gist-script"]}}}
$ CLASSPATH=$(clojure -Spath)
$ bb --classpath "$CLASSPATH" --main my-gist-script
@@ -381,6 +407,57 @@ $ bb "(my-gist-script/-main)"
Hello from gist script!
```
+### Deps.clj
+
+The [`deps.clj`](https://github.com/borkdude/deps.clj/) script can be used to work with `deps.edn`-based projects:
+
+``` shell
+$ deps.clj -A:my-script -Scommand "bb -cp {{classpath}} {{main-opts}}"
+Hello from gist script!
+```
+
+Create these aliases for brevity:
+
+``` shell
+$ alias bbk='deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
+$ alias babashka='rlwrap deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
+$ bbk -A:my-script
+Hello from gist script!
+$ babashka
+Babashka v0.0.58 REPL.
+Use :repl/quit or :repl/exit to quit the REPL.
+Clojure rocks, Bash reaches.
+
+user=> (require '[my-gist-script :as mgs])
+nil
+user=> (mgs/-main)
+Hello from gist script!
+nil
+```
+
+## Uberscript
+
+The `--uberscript` option collects the expressions in
+`BABASHKA_PRELOADS`, the command line expression or file, the main entrypoint
+and all required namespaces from the classpath into a single file. This can be
+convenient for debugging and deployment.
+
+Given the `deps.edn` from above:
+
+``` clojure
+$ deps.clj -A:my-script -Scommand "bb -cp {{classpath}} {{main-opts}} --uberscript my-script.clj"
+
+$ cat my-script.clj
+(ns my-gist-script)
+(defn -main [& args]
+ (println "Hello from gist script!"))
+(ns user (:require [my-gist-script]))
+(apply my-gist-script/-main *command-line-args*)
+
+$ bb my-script.clj
+Hello from gist script!
+```
+
## Parsing command line arguments
Babashka ships with `clojure.tools.cli`:
@@ -446,22 +523,27 @@ A socket REPL client for Emacs is
## Spawning and killing a process
-You may use the `conch` namespace for this. It maps to
-[`me.raynes.conch.low-level`](https://github.com/clj-commons/conch#low-level-usage).
+Use the `java.lang.ProcessBuilder` class.
Example:
``` clojure
-$ bb '
-(def ws (conch/proc "python" "-m" "SimpleHTTPServer" "1777"))
-(net/wait-for-it "localhost" 1777) (conch/destroy ws)'
+user=> (def ws (-> (ProcessBuilder. ["python" "-m" "SimpleHTTPServer" "1777"]) (.start)))
+#'user/ws
+user=> (wait/wait-for-port "localhost" 1777)
+{:host "localhost", :port 1777, :took 2}
+user=> (.destroy ws)
+nil
```
+Also see this [example](examples/process_builder.clj).
+
## Async
-Apart from `future` for creating threads and the `conch` namespace for creating
-processes, you may use the `async` namespace, which maps to `clojure.core.async`, for asynchronous scripting. The following
-example shows how to get first available value from two different processes:
+Apart from `future` and `pmap` for creating threads, you may use the `async`
+namespace, which maps to `clojure.core.async`, for asynchronous scripting. The
+following example shows how to get first available value from two different
+processes:
``` clojure
bb '
@@ -487,9 +569,6 @@ same. Multi-threading is supported (`pmap`, `future`).
Differences with Clojure:
-- No first class vars. Note that you can define and redefine global values with
-`def` / `defn`, but there is no `var` indirection.
-
- A subset of Java classes are supported.
- Only the `clojure.core`, `clojure.set`, `clojure.string` and `clojure.walk`
@@ -500,6 +579,62 @@ Differences with Clojure:
- No support for unboxed types.
+## External resources
+
+### Tools and libraries
+
+The following libraries are known to work with Babashka:
+
+#### [deps.clj](https://github.com/borkdude/deps.clj)
+
+A port of the [clojure](https://github.com/clojure/brew-install/) bash script to
+Clojure / babashka.
+
+#### [spartan.test](https://github.com/borkdude/spartan.test/)
+
+A minimal test framework compatible with babashka.
+
+#### [medley](https://github.com/borkdude/medley/)
+
+A fork of [medley](https://github.com/weavejester/medley) made compatible with
+babashka. Requires `bb` >= v0.0.58.
+
+#### [clj-http-lite](https://github.com/borkdude/clj-http-lite)
+
+This fork does not depend on any other libraries. Example:
+
+``` shell
+$ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {clj-http-lite {:git/url "https://github.com/borkdude/clj-http-lite" :sha "f44ebe45446f0f44f2b73761d102af3da6d0a13e"}}}' -Spath)"
+
+$ bb "(require '[clj-http.lite.client :as client]) (:status (client/get \"https://www.clojure.org\"))"
+200
+```
+
+#### [limit-break](https://github.com/technomancy/limit-break)
+
+A debug REPL library. Example:
+
+``` shell
+$ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {limit-break {:git/url "https://github.com/technomancy/limit-break" :sha "050fcfa0ea29fe3340927533a6fa6fffe23bfc2f" :deps/manifest :deps}}}' -Spath)"
+
+$ bb "(require '[limit.break :as lb]) (let [x 1] (lb/break))"
+Babashka v0.0.49 REPL.
+Use :repl/quit or :repl/exit to quit the REPL.
+Clojure rocks, Bash reaches.
+
+break> x
+1
+```
+
+### Blogs
+
+- [Clojure Start Time in 2019](https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019) by Stuart Sierra
+- [Advent of Random
+ Hacks](https://lambdaisland.com/blog/2019-12-19-advent-of-parens-19-advent-of-random-hacks)
+ by Arne Brasseur
+- [Clojure in the Shell](https://lambdaisland.com/blog/2019-12-05-advent-of-parens-5-clojure-in-the-shell) by Arne Brasseur
+- [Clojure Tool](https://purelyfunctional.tv/issues/purelyfunctional-tv-newsletter-351-clojure-tool-babashka/) by Eric Normand
+
## Developing Babashka
To work on Babashka itself make sure Git submodules are checked out.
@@ -520,6 +655,19 @@ You need [Leiningen](https://leiningen.org/), and for building binaries you need
`lein repl` will get you a standard REPL/nREPL connection. To work on tests use `lein with-profiles +test repl`.
+### Adding classes
+
+Add necessary classes to `babashka/impl/classes.clj`. For every addition, write
+a unit test, so it's clear why it is added and removing it will break the
+tests. Try to reduce the size of the binary by only adding the necessary parts
+of a class in `:instance-check`, `:constructors`, `:methods`, `:fields` or
+`:custom`.
+
+The `reflection.json` file that is needed for GraalVM compilation is generated
+with:
+
+ lein with-profiles +reflection run
+
### Test
Test on the JVM (for development):
@@ -553,7 +701,7 @@ welcome!
### Delete a list of files returned by a Unix command
```
-find . | grep conflict | bb -i '(doseq [f *in*] (.delete (io/file f)))'
+find . | grep conflict | bb -i '(doseq [f *input*] (.delete (io/file f)))'
```
### Calculate aggregate size of directory
@@ -587,7 +735,7 @@ $ cat /tmp/test.txt
3 Babashka
4 Goodbye
-$ < /tmp/test.txt bb -io '(shuffle *in*)'
+$ < /tmp/test.txt bb -io '(shuffle *input*)'
3 Babashka
2 Clojure
4 Goodbye
@@ -601,7 +749,7 @@ For converting JSON to EDN, see [jet](https://github.com/borkdude/jet).
``` shellsession
$ curl -s https://api.github.com/repos/borkdude/babashka/tags |
jet --from json --keywordize --to edn |
-bb '(-> *in* first :name (subs 1))'
+bb '(-> *input* first :name (subs 1))'
"0.0.4"
```
@@ -610,16 +758,18 @@ bb '(-> *in* first :name (subs 1))'
``` shellsession
$ curl -s https://api.github.com/repos/borkdude/babashka/releases |
jet --from json --keywordize |
-bb '(-> *in* first :assets)' |
-bb '(some #(re-find #".*linux.*" (:browser_download_url %)) *in*)'
+bb '(-> *input* first :assets)' |
+bb '(some #(re-find #".*linux.*" (:browser_download_url %)) *input*)'
"https://github.com/borkdude/babashka/releases/download/v0.0.4/babashka-0.0.4-linux-amd64.zip"
```
### View download statistics from Clojars
+Contributed by [@plexus](https://github.com/plexus).
+
``` shellsession
$ curl https://clojars.org/stats/all.edn |
-bb -o '(for [[[group art] counts] *in*] (str (reduce + (vals counts)) " " group "/" art))' |
+bb -o '(for [[[group art] counts] *input*] (str (reduce + (vals counts)) " " group "/" art))' |
sort -rn |
less
14113842 clojure-complete/clojure-complete
@@ -665,6 +815,29 @@ 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
```
+### Convert project.clj to deps.edn
+
+Contributed by [@plexus](https://github.com/plexus).
+
+``` shellsession
+$ cat project.clj |
+sed -e 's/#=//g' -e 's/~@//g' -e 's/~//g' |
+bb '(let [{:keys [dependencies source-paths resource-paths]} (apply hash-map (drop 3 *input*))]
+ {:paths (into source-paths resource-paths)
+ :deps (into {} (for [[d v] dependencies] [d {:mvn/version v}]))}) ' |
+jet --pretty > deps.edn
+```
+
+### Print current time in California
+
+See [examples/pst.clj](https://github.com/borkdude/babashka/blob/master/examples/pst.clj)
+
+### Tiny http server
+
+See [examples/http_server.clj](https://github.com/borkdude/babashka/blob/master/examples/http_server.clj)
+
+Original by [@souenzzo](https://gist.github.com/souenzzo/a959a4c5b8c0c90df76fe33bb7dfe201)
+
## Thanks
- [adgoji](https://www.adgoji.com/) for financial support
@@ -677,5 +850,3 @@ Distributed under the EPL License. See LICENSE.
This project contains code from:
- Clojure, which is licensed under the same EPL License.
-- [conch](https://github.com/clj-commons/conch), which is licensed under the
-same EPL License.
diff --git a/deps.edn b/deps.edn
new file mode 100644
index 00000000..e3051185
--- /dev/null
+++ b/deps.edn
@@ -0,0 +1,12 @@
+{:paths ["src" "sci/src" "resources" "sci/resources"],
+ :deps {org.clojure/clojure {:mvn/version "1.10.1"},
+ org.clojure/tools.reader {:mvn/version "1.3.2"},
+ borkdude/edamame {:mvn/version "0.0.10-alpha.4"},
+ borkdude/graal.locking {:mvn/version "0.0.2"},
+ borkdude/sci.impl.reflector {:mvn/version "0.0.1"}
+ org.clojure/core.async {:mvn/version "0.4.500"},
+ org.clojure/tools.cli {:mvn/version "0.4.2"},
+ org.clojure/data.csv {:mvn/version "0.1.4"},
+ cheshire {:mvn/version "5.9.0"}}
+ :aliases {:main
+ {:main-opts ["-m" "babashka.main"]}}}
diff --git a/examples/http_server.clj b/examples/http_server.clj
new file mode 100755
index 00000000..0c045993
--- /dev/null
+++ b/examples/http_server.clj
@@ -0,0 +1,51 @@
+#!/usr/bin/env bb
+
+(import (java.net ServerSocket))
+(require '[clojure.string :as string]
+ '[clojure.java.io :as io])
+
+(with-open [server-socket (new ServerSocket 8080)
+ client-socket (.accept server-socket)]
+ (loop []
+ (let [out (io/writer (.getOutputStream client-socket))
+ in (io/reader (.getInputStream client-socket))
+ [req-line & headers] (loop [headers []]
+ (let [line (.readLine in)]
+ (if (string/blank? line)
+ headers
+ (recur (conj headers line)))))
+ [_ _ path _] (re-find #"([^\s]+)\s([^\s]+)\s([^\s]+)" req-line)
+ f (io/file (format "./%s" path))
+ status (if (.exists f)
+ 200
+ 404)
+ html (fn html-fn [tag & body]
+ (let [attrs? (map? (first body))
+ attrs-str (str (when attrs?
+ (format " %s" (string/join " " (for [[k v] (first body)]
+ (format "%s=%s" (name k) (name v)))))))]
+ (format "<%s%s>%s%s>"
+ (name tag)
+ attrs-str
+ (string/join (if attrs? (rest body) body))
+ (name tag))))
+ body (cond
+ (not (.exists f)) ""
+ (.isFile f) (slurp f)
+ (.isDirectory f) (format "\n%s"
+ (html :html
+ (html :head
+ (html :title path))
+ (html :body
+ (html :h1 path)
+ (html :tt
+ (apply html :pre
+ (for [i (.list f)]
+ (html :div (html :a {:href (format "\"%s\"" i)} i)))))))))]
+ (prn path)
+ (.write out (format "HTTP/1.1 %s OK\r\nContent-Length: %s\r\n\r\n%s"
+ status
+ (count body)
+ body))
+ (.flush out))
+ (recur)))
diff --git a/examples/process_builder.clj b/examples/process_builder.clj
new file mode 100755
index 00000000..0b064516
--- /dev/null
+++ b/examples/process_builder.clj
@@ -0,0 +1,19 @@
+#!/usr/bin/env bb
+
+(require '[clojure.java.io :as io])
+(import '[java.lang ProcessBuilder$Redirect])
+
+(defn grep [input pattern]
+ (let [proc (-> (ProcessBuilder. ["grep" pattern])
+ (.redirectOutput ProcessBuilder$Redirect/INHERIT)
+ (.redirectError ProcessBuilder$Redirect/INHERIT)
+ (.start))
+ proc-input (.getOutputStream proc)]
+ (with-open [w (io/writer proc-input)]
+ (binding [*out* w]
+ (print input)
+ (flush)))
+ (.waitFor proc)
+ nil))
+
+(grep "hello\nbye\n" "bye")
diff --git a/examples/pst.clj b/examples/pst.clj
new file mode 100755
index 00000000..2a919f5f
--- /dev/null
+++ b/examples/pst.clj
@@ -0,0 +1,7 @@
+#!/usr/bin/env bb
+
+(def now (java.time.ZonedDateTime/now))
+(def LA-timezone (java.time.ZoneId/of "America/Los_Angeles"))
+(def LA-time (.withZoneSameInstant now LA-timezone))
+(def pattern (java.time.format.DateTimeFormatter/ofPattern "HH:mm"))
+(println (.format LA-time pattern))
diff --git a/logo/babashka.png b/logo/babashka.png
new file mode 100644
index 00000000..f042bf5e
Binary files /dev/null and b/logo/babashka.png differ
diff --git a/logo/babashka.svg b/logo/babashka.svg
new file mode 100644
index 00000000..f3f63ee5
--- /dev/null
+++ b/logo/babashka.svg
@@ -0,0 +1,30 @@
+
diff --git a/logo/icon.png b/logo/icon.png
new file mode 100644
index 00000000..b1dd077c
Binary files /dev/null and b/logo/icon.png differ
diff --git a/logo/icon.svg b/logo/icon.svg
new file mode 100644
index 00000000..ef89d121
--- /dev/null
+++ b/logo/icon.svg
@@ -0,0 +1,18 @@
+
diff --git a/project.clj b/project.clj
index 055a9d2a..70a59c9d 100644
--- a/project.clj
+++ b/project.clj
@@ -11,7 +11,9 @@
:resource-paths ["resources" "sci/resources"]
:dependencies [[org.clojure/clojure "1.10.1"]
[org.clojure/tools.reader "1.3.2"]
- [borkdude/edamame "0.0.10-alpha.2"]
+ [borkdude/edamame "0.0.10-alpha.4"]
+ [borkdude/graal.locking "0.0.2"]
+ [borkdude/sci.impl.reflector "0.0.1"]
[org.clojure/core.async "0.4.500"]
[org.clojure/tools.cli "0.4.2"]
[org.clojure/data.csv "0.1.4"]
@@ -22,7 +24,8 @@
:jvm-opts ["-Dclojure.compiler.direct-linking=true"
"-Dclojure.spec.skip-macros=true"]
:main babashka.main
- :aot :all}}
+ :aot :all}
+ :reflection {:main babashka.impl.classes/generate-reflection-file}}
:aliases {"bb" ["run" "-m" "babashka.main"]}
:deploy-repositories [["clojars" {:url "https://clojars.org/repo"
:username :env/clojars_user
diff --git a/reflection.json b/reflection.json
deleted file mode 100644
index 676d7b83..00000000
--- a/reflection.json
+++ /dev/null
@@ -1,189 +0,0 @@
-[
- {
- "name":"java.lang.ArithmeticException",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.lang.AssertionError",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.lang.Boolean",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.io.BufferedWriter",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.io.BufferedReader",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name": "java.lang.Class",
- "allDeclaredConstructors": true,
- "allPublicConstructors": true,
- "allDeclaredMethods": true,
- "allPublicMethods": true
- },
- {
- "name":"java.lang.Double",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.lang.Exception",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name": "clojure.lang.ExceptionInfo",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.lang.Integer",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.io.File",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"clojure.lang.LineNumberingPushbackReader",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.util.concurrent.LinkedBlockingQueue",
- "allPublicMethods":true
- },
- {
- "name":"java.util.regex.Pattern",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.lang.Process",
- "allPublicMethods":true
- },
- {
- "name":"java.lang.ProcessBuilder",
- "allPublicConstructors":true
- },
- {
- "name":"java.lang.String",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.io.StringReader",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.io.StringWriter",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.lang.System",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.lang.Thread",
- "methods": [{"name":"run"},{"name":"toString"},{"name":"isInterrupted"},{"name":"currentThread"},{"name":"getName"},{"name":"join"},{"name":"getThreadGroup"},{"name":"getStackTrace"},{"name":"holdsLock"},{"name":"checkAccess"},{"name":"dumpStack"},{"name":"yield"},{"name":"setPriority"},{"name":"setDaemon"},{"name":"start"},{"name":"sleep"},{"name":"interrupt"},{"name":"interrupted"},{"name":"isAlive"},{"name":"getPriority"},{"name":"setName"},{"name":"activeCount"},{"name":"enumerate"},{"name":"isDaemon"},{"name":"getContextClassLoader"},{"name":"setContextClassLoader"},{"name":"getAllStackTraces"},{"name":"getId"},{"name":"getState"},{"name":"setDefaultUncaughtExceptionHandler"},{"name":"getDefaultUncaughtExceptionHandler"},{"name":"getUncaughtExceptionHandler"},{"name":"setUncaughtExceptionHandler"}],
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "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
- },
- {
- "name":"java.nio.file.CopyOption",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.nio.file.FileAlreadyExistsException",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.nio.file.Files",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.nio.file.NoSuchFileException",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"java.nio.file.StandardCopyOption",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {
- "name":"sun.nio.fs.UnixPath",
- "allPublicMethods":true,
- "allPublicFields": true,
- "allPublicConstructors": true
- },
- {"name":"com.sun.xml.internal.stream.XMLInputFactoryImpl",
- "methods":[{"name":"","parameterTypes":[] }]},
- {"name":"com.sun.xml.internal.stream.XMLOutputFactoryImpl",
- "methods":[{"name":"","parameterTypes":[] }]}
-]
diff --git a/resources/BABASHKA_RELEASED_VERSION b/resources/BABASHKA_RELEASED_VERSION
index 54a00221..67ccd586 100644
--- a/resources/BABASHKA_RELEASED_VERSION
+++ b/resources/BABASHKA_RELEASED_VERSION
@@ -1 +1 @@
-0.0.42
+0.0.60
\ No newline at end of file
diff --git a/resources/BABASHKA_VERSION b/resources/BABASHKA_VERSION
index 382bf853..e2e3d510 100644
--- a/resources/BABASHKA_VERSION
+++ b/resources/BABASHKA_VERSION
@@ -1 +1 @@
-0.0.43-SNAPSHOT
+0.0.61-SNAPSHOT
\ No newline at end of file
diff --git a/sci b/sci
index 07d28ee5..57b584ba 160000
--- a/sci
+++ b/sci
@@ -1 +1 @@
-Subproject commit 07d28ee572e90a629e01b10aa5b98cb33ccdc1e5
+Subproject commit 57b584ba0a6a1f74a887d350463a700976dd37d8
diff --git a/script/bump_version.clj b/script/bump_version.clj
new file mode 100755
index 00000000..6a3591bd
--- /dev/null
+++ b/script/bump_version.clj
@@ -0,0 +1,60 @@
+#!/usr/bin/env bb
+
+(ns bump-version
+ (:require [clojure.java.io :as io]
+ [clojure.string :as str]))
+
+(import '[java.lang ProcessBuilder$Redirect])
+
+(defn shell-command
+ "Executes shell command. Exits script when the shell-command has a non-zero exit code, propagating it.
+ Accepts the following options:
+ `:input`: instead of reading from stdin, read from this string.
+ `:to-string?`: instead of writing to stdoud, write to a string and
+ return it."
+ ([args] (shell-command args nil))
+ ([args {:keys [:input :to-string?]}]
+ (let [args (mapv str args)
+ pb (cond-> (-> (ProcessBuilder. ^java.util.List args)
+ (.redirectError ProcessBuilder$Redirect/INHERIT))
+ (not to-string?) (.redirectOutput ProcessBuilder$Redirect/INHERIT)
+ (not input) (.redirectInput ProcessBuilder$Redirect/INHERIT))
+ proc (.start pb)]
+ (when input
+ (with-open [w (io/writer (.getOutputStream proc))]
+ (binding [*out* w]
+ (print input)
+ (flush))))
+ (let [string-out
+ (when to-string?
+ (let [sw (java.io.StringWriter.)]
+ (with-open [w (io/reader (.getInputStream proc))]
+ (io/copy w sw))
+ (str sw)))
+ exit-code (.waitFor proc)]
+ (when-not (zero? exit-code)
+ (System/exit exit-code))
+ string-out))))
+
+(def version-file (io/file "resources" "BABASHKA_VERSION"))
+(def released-version-file (io/file "resources" "BABASHKA_RELEASED_VERSION"))
+
+(case (first *command-line-args*)
+ "release" (let [version-string (str/trim (slurp version-file))
+ [major minor patch] (str/split version-string #"\.")
+ patch (str/replace patch "-SNAPSHOT" "")
+ new-version (str/join "." [major minor patch])]
+ (spit version-file new-version)
+ (shell-command ["git" "commit" "-a" "-m" (str "v" new-version)])
+ (shell-command ["git" "diff" "HEAD^" "HEAD"]))
+ "post-release" (do
+ (io/copy version-file released-version-file)
+ (let [version-string (str/trim (slurp version-file))
+ [major minor patch] (str/split version-string #"\.")
+ patch (Integer. patch)
+ patch (str (inc patch) "-SNAPSHOT")
+ new-version (str/join "." [major minor patch])]
+ (spit version-file new-version)
+ (shell-command ["git" "commit" "-a" "-m" "Version bump"])
+ (shell-command ["git" "diff" "HEAD^" "HEAD"])))
+ (println "Expected: release | post-release."))
diff --git a/script/compile b/script/compile
index 94cb69fa..ac925a5a 100755
--- a/script/compile
+++ b/script/compile
@@ -23,7 +23,9 @@ BABASHKA_VERSION=$(cat resources/BABASHKA_VERSION)
# mkdir -p src/sci
# cp -R /tmp/sci/src/* src
+lein with-profiles +reflection do run
lein do clean, uberjar
+
$NATIVE_IMAGE \
-jar target/babashka-$BABASHKA_VERSION-standalone.jar \
-H:Name=bb \
diff --git a/script/lib_tests/clj_http_lite_test b/script/lib_tests/clj_http_lite_test
new file mode 100755
index 00000000..d9c0cf43
--- /dev/null
+++ b/script/lib_tests/clj_http_lite_test
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+export BABASHKA_CLASSPATH=$(clojure -Sdeps '{:deps {clj-http-lite {:git/url "https://github.com/borkdude/clj-http-lite" :sha "f44ebe45446f0f44f2b73761d102af3da6d0a13e"}}}' -Spath)
+
+./bb -e "
+(require '[clj-http.lite.client :as client])
+
+(prn (:status (client/get \"https://www.clojure.org\")))
+
+(prn (:status (client/get \"https://postman-echo.com/get?foo1=bar1&foo2=bar2\")))
+
+(prn (:status (client/post \"https://postman-echo.com/post\")))
+
+(prn (:status (client/put \"https://postman-echo.com/put\")))
+"
diff --git a/script/lib_tests/deps_clj_test b/script/lib_tests/deps_clj_test
new file mode 100755
index 00000000..29d78dc3
--- /dev/null
+++ b/script/lib_tests/deps_clj_test
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+curl -sL https://raw.githubusercontent.com/borkdude/deps.clj/master/deps.clj -o deps_test.clj
+chmod +x deps_test.clj
+./bb deps_test.clj -Sdescribe
+rm deps_test.clj
+
diff --git a/script/lib_tests/spartan_spec_test b/script/lib_tests/spartan_spec_test
new file mode 100755
index 00000000..5afe02ef
--- /dev/null
+++ b/script/lib_tests/spartan_spec_test
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+export BABASHKA_CLASSPATH=$(clojure -Sdeps '{:deps {spartan.spec {:git/url "https://github.com/borkdude/spartan.spec" :sha "16f7eec4b6589c77c96c9fcf989f78fffcee7c4c"}}}' -Spath)
+
+./bb -e "
+(require '[spartan.spec :as s])
+(s/explain (s/cat :i int? :s string?) [1 :foo])
+(s/conform (s/cat :i int? :s string?) [1 \"foo\"])
+"
diff --git a/script/run_lib_tests b/script/run_lib_tests
new file mode 100755
index 00000000..9e107365
--- /dev/null
+++ b/script/run_lib_tests
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+script/lib_tests/clj_http_lite_test
+script/lib_tests/deps_clj_test
+script/lib_tests/spartan_spec_test
diff --git a/src-bash/bbk b/src-bash/bbk
deleted file mode 100755
index 38b6c36e..00000000
--- a/src-bash/bbk
+++ /dev/null
@@ -1,219 +0,0 @@
-#!/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
diff --git a/src/babashka/impl/Boolean.clj b/src/babashka/impl/Boolean.clj
deleted file mode 100644
index ee2e1853..00000000
--- a/src/babashka/impl/Boolean.clj
+++ /dev/null
@@ -1,15 +0,0 @@
-(ns babashka.impl.Boolean
- {:no-doc true}
- (:refer-clojure :exclude [list]))
-
-(set! *warn-on-reflection* true)
-
-(defn parseBoolean [^String x]
- (Boolean/parseBoolean x))
-
-(def boolean-bindings
- {'Boolean/parseBoolean parseBoolean})
-
-(comment
-
- )
diff --git a/src/babashka/impl/Double.clj b/src/babashka/impl/Double.clj
deleted file mode 100644
index 50882b12..00000000
--- a/src/babashka/impl/Double.clj
+++ /dev/null
@@ -1,15 +0,0 @@
-(ns babashka.impl.Double
- {:no-doc true}
- (:refer-clojure :exclude [list]))
-
-(set! *warn-on-reflection* true)
-
-(defn parseDouble [^String x]
- (Double/parseDouble x))
-
-(def double-bindings
- {'Double/parseDouble parseDouble})
-
-(comment
-
- )
diff --git a/src/babashka/impl/cheshire.clj b/src/babashka/impl/cheshire.clj
index 21faa7ba..76cc4180 100644
--- a/src/babashka/impl/cheshire.clj
+++ b/src/babashka/impl/cheshire.clj
@@ -11,4 +11,8 @@
'generate-smile json/generate-smile
'decode json/decode
'parse-string json/parse-string
+ 'parse-smile json/parse-smile
+ 'parse-stream json/parse-stream
+ 'parsed-seq json/parsed-seq
+ 'parsed-smile-seq json/parsed-smile-seq
'decode-smile json/decode-smile})
diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj
new file mode 100644
index 00000000..415c2e41
--- /dev/null
+++ b/src/babashka/impl/classes.clj
@@ -0,0 +1,232 @@
+(ns babashka.impl.classes
+ {:no-doc true}
+ (:require
+ [cheshire.core :as json]
+ #_[clojure.string :as str]))
+
+;; (def os-name (str/lower-case (System/getProperty "os.name")))
+;; (def os (cond (str/includes? os-name "mac") :mac
+;; (or (str/includes? os-name "nix")
+;; (str/includes? os-name "nux")) :linux
+;; (str/includes? os-name "win") :windows))
+;; (def unix-like? (or (identical? os :linux)
+;; (identical? os :mac)))
+
+(def classes
+ '{:all [java.io.BufferedReader
+ java.io.BufferedWriter
+ java.io.ByteArrayInputStream
+ java.io.ByteArrayOutputStream
+ java.io.File
+ java.io.InputStream
+ java.io.IOException
+ java.io.OutputStream
+ java.io.StringReader
+ java.io.StringWriter
+ java.lang.ArithmeticException
+ java.lang.AssertionError
+ java.lang.Boolean
+ java.lang.Class
+ java.lang.Double
+ java.lang.Exception
+ java.lang.Integer
+ java.lang.Math
+ java.util.concurrent.LinkedBlockingQueue
+ java.lang.Object
+ java.lang.String
+ java.lang.System
+ java.lang.Throwable
+ java.lang.Process
+ java.lang.ProcessBuilder
+ java.lang.ProcessBuilder$Redirect
+ java.net.URI
+ java.net.HttpURLConnection
+ java.net.ServerSocket
+ java.net.Socket
+ java.net.UnknownHostException
+ java.net.URLEncoder
+ java.net.URLDecoder
+ java.nio.file.CopyOption
+ java.nio.file.FileAlreadyExistsException
+ java.nio.file.Files
+ java.nio.file.LinkOption
+ java.nio.file.NoSuchFileException
+ java.nio.file.Path
+ java.nio.file.Paths
+ java.nio.file.StandardCopyOption
+ java.nio.file.attribute.FileAttribute
+ java.nio.file.attribute.FileTime
+ java.nio.file.attribute.PosixFilePermission
+ java.nio.file.attribute.PosixFilePermissions
+ java.time.format.DateTimeFormatter
+ java.time.Clock
+ java.time.DateTimeException
+ java.time.DayOfWeek
+ java.time.Duration
+ java.time.Instant
+ java.time.LocalDate
+ java.time.LocalDateTime
+ java.time.LocalTime
+ java.time.Month
+ java.time.MonthDay
+ java.time.OffsetDateTime
+ java.time.OffsetTime
+ java.time.Period
+ java.time.Year
+ java.time.YearMonth
+ java.time.ZonedDateTime
+ java.time.ZoneId
+ java.time.ZoneOffset
+ java.time.temporal.TemporalAccessor
+ java.util.regex.Pattern
+ java.util.Base64
+ java.util.Base64$Decoder
+ java.util.Base64$Encoder
+ java.util.Date
+ java.util.UUID
+ java.util.concurrent.TimeUnit
+ java.util.zip.InflaterInputStream
+ java.util.zip.DeflaterInputStream
+ java.util.zip.GZIPInputStream
+ java.util.zip.GZIPOutputStream]
+ :constructors [clojure.lang.Delay
+ clojure.lang.MapEntry
+ clojure.lang.LineNumberingPushbackReader]
+ :methods [borkdude.graal.LockFix ;; support for locking
+ ]
+ :fields [clojure.lang.PersistentQueue]
+ :instance-checks [clojure.lang.ExceptionInfo
+ clojure.lang.IObj
+ clojure.lang.IEditableCollection]
+ :custom {clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true
+ :allPublicMethods true}
+ java.lang.Thread
+ {:allPublicConstructors true
+ ;; generated with `public-declared-method-names`, see in
+ ;; `comment` below
+ :methods [{:name "activeCount"}
+ {:name "checkAccess"}
+ {:name "currentThread"}
+ {:name "dumpStack"}
+ {:name "enumerate"}
+ {:name "getAllStackTraces"}
+ {:name "getContextClassLoader"}
+ {:name "getDefaultUncaughtExceptionHandler"}
+ {:name "getId"}
+ {:name "getName"}
+ {:name "getPriority"}
+ {:name "getStackTrace"}
+ {:name "getState"}
+ {:name "getThreadGroup"}
+ {:name "getUncaughtExceptionHandler"}
+ {:name "holdsLock"}
+ {:name "interrupt"}
+ {:name "interrupted"}
+ {:name "isAlive"}
+ {:name "isDaemon"}
+ {:name "isInterrupted"}
+ {:name "join"}
+ {:name "run"}
+ {:name "setContextClassLoader"}
+ {:name "setDaemon"}
+ {:name "setDefaultUncaughtExceptionHandler"}
+ {:name "setName"}
+ {:name "setPriority"}
+ {:name "setUncaughtExceptionHandler"}
+ {:name "sleep"}
+ {:name "start"}
+ {:name "toString"}
+ {:name "yield"}]}
+ java.net.URL
+ {:allPublicConstructors true
+ :allPublicFields true
+ ;; generated with `public-declared-method-names`, see in
+ ;; `comment` below
+ :methods [{:name "equals"}
+ {:name "getAuthority"}
+ {:name "getContent"}
+ {:name "getDefaultPort"}
+ {:name "getFile"}
+ {:name "getHost"}
+ {:name "getPath"}
+ {:name "getPort"}
+ {:name "getProtocol"}
+ {:name "getQuery"}
+ {:name "getRef"}
+ {:name "getUserInfo"}
+ {:name "hashCode"}
+ {:name "openConnection"}
+ {:name "openStream"}
+ {:name "sameFile"}
+ ;; not supported: {:name "setURLStreamHandlerFactory"}
+ {:name "toExternalForm"}
+ {:name "toString"}
+ {:name "toURI"}]}}})
+
+(defmacro gen-class-map []
+ (let [classes (concat (:all classes)
+ (keys (:custom classes))
+ (:constructors classes)
+ (:methods classes)
+ (:fields classes)
+ (:instance-checks classes))
+ m (apply hash-map
+ (for [c classes
+ c [(list 'quote c) c]]
+ c))]
+ (assoc m :public-class
+ (fn [v]
+ (cond (instance? java.nio.file.Path v)
+ java.nio.file.Path
+ (instance? java.lang.Process v)
+ java.lang.Process)))))
+
+(def class-map (gen-class-map))
+
+(defn generate-reflection-file
+ "Generate reflection.json file"
+ [& args]
+ (let [entries (vec (for [c (sort (:all classes))
+ :let [class-name (str c)]]
+ {:name class-name
+ :allPublicMethods true
+ :allPublicFields true
+ :allPublicConstructors true}))
+ constructors (vec (for [c (sort (:constructors classes))
+ :let [class-name (str c)]]
+ {:name class-name
+ :allPublicConstructors true}))
+ methods (vec (for [c (sort (:methods classes))
+ :let [class-name (str c)]]
+ {:name class-name
+ :allPublicMethods true}))
+ fields (vec (for [c (sort (:fields classes))
+ :let [class-name (str c)]]
+ {:name class-name
+ :allPublicFields true}))
+ custom-entries (for [[c v] (:custom classes)
+ :let [class-name (str c)]]
+ (assoc v :name class-name))
+ all-entries (concat entries constructors methods fields custom-entries)]
+ (spit (or
+ (first args)
+ "reflection.json") (json/generate-string all-entries {:pretty true}))))
+
+(comment
+
+ (defn public-declared-method? [c m]
+ (and (= c (.getDeclaringClass m))
+ (not (.getAnnotation m Deprecated))))
+
+ (defn public-declared-method-names [c]
+ (->> (.getMethods c)
+ (keep (fn [m]
+ (when (public-declared-method? c m)
+ {:name (.getName m)})) )
+ (distinct)
+ (sort-by :name)
+ (vec)))
+
+ (public-declared-method-names java.lang.UNIXProcess)
+ (public-declared-method-names java.net.URL)
+ )
diff --git a/src/babashka/impl/classpath.clj b/src/babashka/impl/classpath.clj
index 1b16611f..a90e5126 100644
--- a/src/babashka/impl/classpath.clj
+++ b/src/babashka/impl/classpath.clj
@@ -7,29 +7,37 @@
(set! *warn-on-reflection* true)
(defprotocol IResourceResolver
- (getResource [this path]))
+ (getResource [this path opts]))
(deftype DirectoryResolver [path]
IResourceResolver
- (getResource [this resource-path]
+ (getResource [this resource-path {:keys [:url?]}]
(let [f (io/file path resource-path)]
(when (.exists f)
- (slurp f)))))
+ (if url?
+ (java.net.URL. (str "file:"
+ (.getCanonicalPath f)))
+ {:file (.getCanonicalPath f)
+ :source (slurp f)})))))
(defn path-from-jar
- [^java.io.File jar-file path]
+ [^java.io.File jar-file path {:keys [:url?]}]
(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)]
+ (if url?
+ (java.net.URL.
+ (str "jar:file:" (.getCanonicalPath jar-file) "!/" path))
+ {:file path
+ :source (slurp (.getInputStream jar x))})))) entries)]
entry)))
(deftype JarFileResolver [path]
IResourceResolver
- (getResource [this resource-path]
- (path-from-jar path resource-path)))
+ (getResource [this resource-path opts]
+ (path-from-jar path resource-path opts)))
(defn part->entry [part]
(if (str/ends-with? part ".jar")
@@ -38,25 +46,25 @@
(deftype Loader [entries]
IResourceResolver
- (getResource [this resource-path]
- (some #(getResource % resource-path) entries)))
+ (getResource [this resource-path opts]
+ (some #(getResource % resource-path opts) 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]
+(defn source-for-namespace [loader namespace opts]
(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)))
+ (some #(getResource loader % opts) 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)
+ (source-for-namespace l 'babashka.impl.cheshire nil)
+ (:file (source-for-namespace l 'cheshire.core nil))
)
diff --git a/src/babashka/impl/clojure/core.clj b/src/babashka/impl/clojure/core.clj
index 896d06a2..d3a69ee4 100644
--- a/src/babashka/impl/clojure/core.clj
+++ b/src/babashka/impl/clojure/core.clj
@@ -1,14 +1,22 @@
(ns babashka.impl.clojure.core
{:no-doc true}
- (:refer-clojure :exclude [future]))
+ (:refer-clojure :exclude [future])
+ (:require [borkdude.graal.locking :as locking]))
+
+(defn locking* [form bindings v f & args]
+ (apply @#'locking/locking form bindings v f args))
(def core-extras
{'file-seq file-seq
'agent agent
+ 'instance? instance? ;; TODO: move to sci
'send send
'send-off send-off
'promise promise
'deliver deliver
+ 'locking (with-meta locking* {:sci/macro true})
'shutdown-agents shutdown-agents
'slurp slurp
- 'spit spit})
+ 'spit spit
+ 'Throwable->map Throwable->map
+ 'compare-and-set! compare-and-set!})
diff --git a/src/babashka/impl/clojure/core/server.clj b/src/babashka/impl/clojure/core/server.clj
index ea6f14c8..2b1c821f 100644
--- a/src/babashka/impl/clojure/core/server.clj
+++ b/src/babashka/impl/clojure/core/server.clj
@@ -14,10 +14,11 @@
:no-doc true}
babashka.impl.clojure.core.server
(:refer-clojure :exclude [locking])
+ (:require [sci.core :as sci])
(:import
[clojure.lang LineNumberingPushbackReader]
[java.net InetAddress Socket ServerSocket SocketException]
- [java.io Reader Writer PrintWriter BufferedWriter BufferedReader InputStreamReader OutputStreamWriter]))
+ [java.io BufferedWriter InputStreamReader OutputStreamWriter]))
(set! *warn-on-reflection* true)
@@ -41,9 +42,9 @@
args - to pass to accept-fn"
[^Socket conn client-id in out err accept args]
(try
- (binding [*in* in
- *out* out
- *err* err]
+ (sci/with-bindings {sci/in in
+ sci/out out
+ sci/err err}
(swap! server assoc-in [:sessions client-id] {})
(apply accept args))
(catch SocketException _disconnect)
diff --git a/src/babashka/impl/clojure/java/io.clj b/src/babashka/impl/clojure/java/io.clj
index 59e9b1f2..5c26164b 100644
--- a/src/babashka/impl/clojure/java/io.clj
+++ b/src/babashka/impl/clojure/java/io.clj
@@ -1,8 +1,10 @@
(ns babashka.impl.clojure.java.io
+ {:no-doc true}
(:require [clojure.java.io :as io]))
(def io-namespace
{'as-relative-path io/as-relative-path
+ 'as-url io/as-url
'copy io/copy
'delete-file io/delete-file
'file io/file
diff --git a/src/babashka/impl/clojure/java/shell.clj b/src/babashka/impl/clojure/java/shell.clj
new file mode 100644
index 00000000..1d3aa429
--- /dev/null
+++ b/src/babashka/impl/clojure/java/shell.clj
@@ -0,0 +1,42 @@
+(ns babashka.impl.clojure.java.shell
+ {:no-doc true}
+ (:require [clojure.java.shell :as shell]
+ [sci.core :as sci]))
+
+(def sh-dir (sci/new-dynamic-var '*sh-dir* nil))
+(def sh-env (sci/new-dynamic-var '*sh-env* nil))
+
+(defn with-sh-dir*
+ [_ _ dir & forms]
+ `(binding [clojure.java.shell/*sh-dir* ~dir]
+ ~@forms))
+
+(defn with-sh-env*
+ [_ _ env & forms]
+ `(binding [clojure.java.shell/*sh-env* ~env]
+ ~@forms))
+
+(defn parse-args
+ [args]
+ (let [default-encoding "UTF-8" ;; see sh doc string
+ default-opts {:out-enc default-encoding
+ :in-enc default-encoding
+ :dir @sh-dir
+ :env @sh-env}
+ [cmd opts] (split-with string? args)
+ opts (merge default-opts (apply hash-map opts))
+ opts-seq (apply concat opts)]
+ (concat cmd opts-seq)))
+
+(defn sh [& args]
+ (let [args (parse-args args)]
+ (apply shell/sh args)))
+
+(def shell-namespace
+ {'*sh-dir* sh-dir
+ '*sh-env* sh-env
+ 'with-sh-dir (with-meta with-sh-dir*
+ {:sci/macro true})
+ 'with-sh-env (with-meta with-sh-env*
+ {:sci/macro true})
+ 'sh sh})
diff --git a/src/babashka/impl/clojure/main.clj b/src/babashka/impl/clojure/main.clj
index b7c9010a..3d8a9f2f 100644
--- a/src/babashka/impl/clojure/main.clj
+++ b/src/babashka/impl/clojure/main.clj
@@ -15,9 +15,13 @@
:author "Stephen C. Gilardi and Rich Hickey"
:no-doc true}
babashka.impl.clojure.main
- (:refer-clojure :exclude [with-bindings])
- (:import (java.io StringReader)
- (clojure.lang LineNumberingPushbackReader LispReader$ReaderException)))
+ (:refer-clojure :exclude [with-bindings]))
+
+(defn demunge
+ "Given a string representation of a fn class,
+ as in a stack trace element, returns a readable version."
+ [fn-name]
+ (clojure.lang.Compiler/demunge fn-name))
(defmacro with-bindings
"Executes body in the context of thread-local bindings for several vars
@@ -44,18 +48,6 @@
*e nil]
~@body))
-(defn repl-prompt
- "Default :prompt hook for repl"
- []
- (print "bb=> " ))
-
-(defn repl-caught
- "Default :caught hook for repl"
- [e]
- (binding [*out* *err*]
- (println (.getMessage ^Exception e))
- (flush)))
-
(defn repl
"Generic, reusable, read-eval-print loop. By default, reads from *in*,
writes to *out*, and prints exception summaries to *err*. If you use the
@@ -92,14 +84,7 @@
read, eval, or print throws an exception or error
default: repl-caught"
[& options]
- (let [{:keys [init need-prompt prompt flush read eval print caught]
- :or {need-prompt (if (instance? LineNumberingPushbackReader *in*)
- #(.atLineStart ^LineNumberingPushbackReader *in*)
- #(identity true))
- prompt repl-prompt
- flush flush
- print prn
- caught repl-caught}}
+ (let [{:keys [init need-prompt prompt flush read eval print caught]}
(apply hash-map options)
request-prompt (Object.)
request-exit (Object.)
diff --git a/src/babashka/impl/clojure/stacktrace.clj b/src/babashka/impl/clojure/stacktrace.clj
index a220fd88..96fd9f32 100644
--- a/src/babashka/impl/clojure/stacktrace.clj
+++ b/src/babashka/impl/clojure/stacktrace.clj
@@ -80,9 +80,9 @@
(print "Caused by: " )
(recur cause n))))
-(defn e
- "REPL utility. Prints a brief stack trace for the root cause of the
- most recent exception."
- {:added "1.1"}
- []
- (print-stack-trace (root-cause *e) 8))
+(def stacktrace-namespace
+ {'root-cause root-cause
+ 'print-trace-element print-trace-element
+ 'print-throwable print-throwable
+ 'print-stack-trace print-stack-trace
+ 'print-cause-trace print-cause-trace})
diff --git a/src/babashka/impl/conch.clj b/src/babashka/impl/conch.clj
deleted file mode 100644
index e53e8a99..00000000
--- a/src/babashka/impl/conch.clj
+++ /dev/null
@@ -1,18 +0,0 @@
-(ns babashka.impl.conch
- {:no-doc true}
- (:require
- [babashka.impl.me.raynes.conch.low-level :as ll]))
-
-(def conch-namespace
- {;; low level API
- 'proc ll/proc
- 'destroy ll/destroy
- 'exit-code ll/exit-code
- 'flush ll/flush
- 'done ll/done
- 'stream-to ll/stream-to
- 'feed-from ll/feed-from
- 'stream-to-string ll/stream-to-string
- 'stream-to-out ll/stream-to-out
- 'feed-from-string ll/feed-from-string
- 'read-line ll/read-line})
diff --git a/src/babashka/impl/exceptions.clj b/src/babashka/impl/exceptions.clj
index 2d0dc9fd..b0c882ca 100644
--- a/src/babashka/impl/exceptions.clj
+++ b/src/babashka/impl/exceptions.clj
@@ -1,7 +1 @@
-(ns babashka.impl.exceptions)
-
-(def exception-bindings
- {'ArithmeticException ArithmeticException
- 'java.lang.ArithmeticException ArithmeticException
- 'java.lang.AssertionError AssertionError
- 'AssertionError AssertionError})
+(ns babashka.impl.exceptions)
\ No newline at end of file
diff --git a/src/babashka/impl/me/raynes/conch/low_level.clj b/src/babashka/impl/me/raynes/conch/low_level.clj
deleted file mode 100644
index 0de31bdc..00000000
--- a/src/babashka/impl/me/raynes/conch/low_level.clj
+++ /dev/null
@@ -1,126 +0,0 @@
-;; From https://github.com/clj-commons/conch
-
-(ns babashka.impl.me.raynes.conch.low-level
- "A simple but flexible library for shelling out from Clojure."
- {:no-doc true}
- (:refer-clojure :exclude [flush read-line])
- (:require [clojure.java.io :as io])
- (:import [java.util.concurrent TimeUnit TimeoutException]
- [java.io InputStream OutputStream]))
-
-(set! *warn-on-reflection* true)
-
-(defn proc
- "Spin off another process. Returns the process's input stream,
- output stream, and err stream as a map of :in, :out, and :err keys
- If passed the optional :dir and/or :env keyword options, the dir
- and enviroment will be set to what you specify. If you pass
- :verbose and it is true, commands will be printed. If it is set to
- :very, environment variables passed, dir, and the command will be
- printed. If passed the :clear-env keyword option, then the process
- will not inherit its environment from its parent process."
- [& args]
- (let [[cmd args] (split-with (complement keyword?) args)
- args (apply hash-map args)
- builder (ProcessBuilder. ^"[Ljava.lang.String;" (into-array String cmd))
- env (.environment builder)]
- (when (:clear-env args)
- (.clear env))
- (doseq [[k v] (:env args)]
- (.put env k v))
- (when-let [dir (:dir args)]
- (.directory builder (io/file dir)))
- (when (:verbose args) (apply println cmd))
- (when (= :very (:verbose args))
- (when-let [env (:env args)] (prn env))
- (when-let [dir (:dir args)] (prn dir)))
- (when (:redirect-err args)
- (.redirectErrorStream builder true))
- (let [process (.start builder)]
- {:out (.getInputStream process)
- :in (.getOutputStream process)
- :err (.getErrorStream process)
- :process process})))
-
-(defn destroy
- "Destroy a process."
- [process]
- (.destroy ^Process (:process process)))
-
-;; .waitFor returns the exit code. This makes this function useful for
-;; both getting an exit code and stopping the thread until a process
-;; terminates.
-(defn exit-code
- "Waits for the process to terminate (blocking the thread) and returns
- the exit code. If timeout is passed, it is assumed to be milliseconds
- to wait for the process to exit. If it does not exit in time, it is
- killed (with or without fire)."
- ([process] (.waitFor ^Process (:process process)))
- ([process timeout]
- (try
- (let [^java.util.concurrent.Future fut
- (future (.waitFor ^Process (:process process)))]
- (.get fut timeout TimeUnit/MILLISECONDS))
- (catch Exception e
- (if (or (instance? TimeoutException e)
- (instance? TimeoutException (.getCause e)))
- (do (destroy process)
- :timeout)
- (throw e))))))
-
-(defn flush
- "Flush the output stream of a process."
- [process]
- (let [^OutputStream in (:in process)]
- (.flush in)))
-
-(defn done
- "Close the process's output stream (sending EOF)."
- [proc]
- (let [^OutputStream in (:in proc)]
- (.close in)))
-
-(defn stream-to
- "Stream :out or :err from a process to an ouput stream.
- Options passed are fed to clojure.java.io/copy. They are :encoding to
- set the encoding and :buffer-size to set the size of the buffer.
- :encoding defaults to UTF-8 and :buffer-size to 1024."
- [process from to & args]
- (apply io/copy (process from) to args))
-
-(defn feed-from
- "Feed to a process's input stream with optional. Options passed are
- fed to clojure.java.io/copy. They are :encoding to set the encoding
- and :buffer-size to set the size of the buffer. :encoding defaults to
- UTF-8 and :buffer-size to 1024. If :flush is specified and is false,
- the process will be flushed after writing."
- [process from & {flush? :flush :or {flush? true} :as all}]
- (apply io/copy from (:in process) all)
- (when flush? (flush process)))
-
-(defn stream-to-string
- "Streams the output of the process to a string and returns it."
- [process from & args]
- (with-open [writer (java.io.StringWriter.)]
- (apply stream-to process from writer args)
- (str writer)))
-
-;; The writer that Clojure wraps System/out in for *out* seems to buffer
-;; things instead of writing them immediately. This wont work if you
-;; really want to stream stuff, so we'll just skip it and throw our data
-;; directly at System/out.
-(defn stream-to-out
- "Streams the output of the process to System/out"
- [process from & args]
- (apply stream-to process from (System/out) args))
-
-(defn feed-from-string
- "Feed the process some data from a string."
- [process s & args]
- (apply feed-from process (java.io.StringReader. s) args))
-
-(defn read-line
- "Read a line from a process' :out or :err."
- [process from]
- (binding [*in* (io/reader (from process))]
- (clojure.core/read-line)))
diff --git a/src/babashka/impl/repl.clj b/src/babashka/impl/repl.clj
index d97cec3a..3460aaeb 100644
--- a/src/babashka/impl/repl.clj
+++ b/src/babashka/impl/repl.clj
@@ -5,50 +5,72 @@
[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]))
+ [sci.impl.parser :as parser]
+ [sci.core :as sci]
+ [sci.impl.io :as sio]))
+
+(defn repl-caught
+ "Default :caught hook for repl"
+ [e]
+ (sci/with-bindings {sci/out @sci/err}
+ (sio/println (.getMessage ^Exception e))
+ (sio/flush)))
(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)))))
+ ([sci-ctx] (repl sci-ctx nil))
+ ([sci-ctx {:keys [:init :read :eval :need-prompt :prompt :flush :print :caught]}]
+ (let [in (r/indexing-push-back-reader (r/push-back-reader @sci/in))]
+ (m/repl
+ :init (or init
+ #(do (sio/println "Babashka"
+ (str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
+ "REPL.")
+ (sio/println "Use :repl/quit or :repl/exit to quit the REPL.")
+ (sio/println "Clojure rocks, Bash reaches.")
+ (sio/println)))
+ :read (or read
+ (fn [_request-prompt request-exit]
+ ;; (prn "PEEK" @sci/in (r/peek-char @sci/in))
+ ;; (prn "PEEK" @sci/in (r/peek-char @sci/in)) this works fine
+ (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 (or 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 (or need-prompt (fn [] true))
+ :prompt (or prompt #(sio/printf "%s=> " (-> sci-ctx :env deref :current-ns)))
+ :flush (or flush sio/flush)
+ :print (or print sio/prn)
+ :caught (or caught repl-caught)))))
-(defn start-repl! [ctx]
- (let [sci-ctx (init ctx)]
- (repl sci-ctx)))
+(defn start-repl!
+ ([sci-ctx] (start-repl! sci-ctx nil))
+ ([sci-ctx opts]
+ (repl sci-ctx opts)))
+
+;;;; Scratch
+
+(comment
+ (def in (-> (java.io.StringReader. "(+ 1 2 3)") clojure.lang.LineNumberingPushbackReader.))
+ (r/peek-char in)
+ (r/read-char in)
+ )
diff --git a/src/babashka/impl/socket_repl.clj b/src/babashka/impl/socket_repl.clj
index 13195287..c18458d3 100644
--- a/src/babashka/impl/socket_repl.clj
+++ b/src/babashka/impl/socket_repl.clj
@@ -3,19 +3,17 @@
(:require
[babashka.impl.clojure.core.server :as server]
[babashka.impl.repl :as repl]
- [clojure.string :as str]
- [sci.impl.opts :refer [init]]))
+ [clojure.string :as str]))
(set! *warn-on-reflection* true)
-(defn start-repl! [host+port sci-opts]
+(defn start-repl! [host+port sci-ctx]
(let [parts (str/split host+port #":")
[host port] (if (= 1 (count parts))
[nil (Integer. ^String (first parts))]
[(first parts) (Integer. ^String (second parts))])
host+port (if-not host (str "localhost:" port)
host+port)
- sci-ctx (init sci-opts)
socket (server/start-server
{:address host
:port port
@@ -29,7 +27,6 @@
(server/stop-server))
(comment
- (def sock (start-repl! "0.0.0.0:1666" {:env (atom {})}))
@#'server/servers
(stop-repl!)
)
diff --git a/src/babashka/impl/utils.clj b/src/babashka/impl/utils.clj
deleted file mode 100644
index 55387bc3..00000000
--- a/src/babashka/impl/utils.clj
+++ /dev/null
@@ -1,9 +0,0 @@
-(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)))
diff --git a/src/babashka/main.clj b/src/babashka/main.clj
index b1bd3679..c9f5c86b 100644
--- a/src/babashka/main.clj
+++ b/src/babashka/main.clj
@@ -3,26 +3,34 @@
(:require
[babashka.impl.async :refer [async-namespace]]
[babashka.impl.cheshire :refer [cheshire-core-namespace]]
+ [babashka.impl.classes :as classes]
+ [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.clojure.java.shell :refer [shell-namespace]]
+ [babashka.impl.clojure.main :refer [demunge]]
+ [babashka.impl.clojure.stacktrace :refer [stacktrace-namespace print-stack-trace]]
[babashka.impl.csv :as csv]
- [babashka.impl.xml :as xml]
[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]]
- [babashka.impl.utils :refer [eval-string]]
- [babashka.impl.classpath :as cp]
+ [babashka.impl.xml :as xml]
[babashka.wait :as wait]
[clojure.edn :as edn]
[clojure.java.io :as io]
- [clojure.java.shell :as shell]
[clojure.string :as str]
- [sci.addons :as addons])
+ [sci.addons :as addons]
+ [sci.core :as sci]
+ [sci.impl.interpreter :refer [eval-string*]]
+ [sci.impl.opts :as sci-opts]
+ [sci.impl.vars :as vars])
(:gen-class))
+(sci/alter-var-root sci/in (constantly *in*))
+(sci/alter-var-root sci/out (constantly *out*))
+(sci/alter-var-root sci/err (constantly *err*))
+
(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 '...'
@@ -31,77 +39,83 @@
(defn parse-opts [options]
(let [opts (loop [options options
opts-map {}]
- (if-let [opt (first options)]
- (case opt
- ("--version") {:version true}
- ("--help" "-h" "-?") {:help? true}
- ("--verbose")(recur (rest options)
+ (if options
+ (let [opt (first options)]
+ (case opt
+ ("--version") {:version true}
+ ("--help" "-h" "-?") {:help? true}
+ ("--verbose")(recur (next options)
+ (assoc opts-map
+ :verbose? true))
+ ("--stream") (recur (next options)
+ (assoc opts-map
+ :stream? true))
+ ("--time") (recur (next options)
(assoc opts-map
- :verbose? true))
- ("--stream") (recur (rest options)
- (assoc opts-map
- :stream? true))
- ("--time") (recur (rest options)
- (assoc opts-map
- :time? true))
- ("-i") (recur (rest options)
- (assoc opts-map
- :shell-in true))
- ("-I") (recur (rest options)
- (assoc opts-map
- :edn-in true))
- ("-o") (recur (rest options)
- (assoc opts-map
- :shell-out true))
- ("-O") (recur (rest options)
- (assoc opts-map
- :edn-out true))
- ("-io") (recur (rest options)
- (assoc opts-map
- :shell-in true
- :shell-out true))
- ("-IO") (recur (rest options)
- (assoc opts-map
- :edn-in true
- :edn-out true))
- ("-f" "--file")
- (let [options (rest options)]
- (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)
- (assoc opts-map
- :socket-repl (first options))))
- ("--eval", "-e")
- (let [options (rest options)]
- (recur (rest options)
- (assoc opts-map :expression (first options))))
- ("--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)))
- (.exists (io/file opt)))
+ :time? true))
+ ("-i") (recur (next options)
+ (assoc opts-map
+ :shell-in true))
+ ("-I") (recur (next options)
+ (assoc opts-map
+ :edn-in true))
+ ("-o") (recur (next options)
+ (assoc opts-map
+ :shell-out true))
+ ("-O") (recur (next options)
+ (assoc opts-map
+ :edn-out true))
+ ("-io") (recur (next options)
+ (assoc opts-map
+ :shell-in true
+ :shell-out true))
+ ("-IO") (recur (next options)
+ (assoc opts-map
+ :edn-in true
+ :edn-out true))
+ ("--classpath", "-cp")
+ (let [options (next options)]
+ (recur (next options)
+ (assoc opts-map :classpath (first options))))
+ ("--uberscript")
+ (let [options (next options)]
+ (recur (next options)
+ (assoc opts-map
+ :uberscript (first options))))
+ ("-f" "--file")
+ (let [options (next options)]
+ (recur (next options)
+ (assoc opts-map
+ :file (first options))))
+ ("--repl")
+ (let [options (next options)]
+ (recur (next options)
+ (assoc opts-map
+ :repl true)))
+ ("--socket-repl")
+ (let [options (next options)]
+ (recur (next options)
+ (assoc opts-map
+ :socket-repl (first options))))
+ ("--eval", "-e")
+ (let [options (next options)]
+ (recur (next options)
+ (assoc opts-map :expression (first options))))
+ ("--main", "-m")
+ (let [options (next options)]
+ (recur (next options)
+ (assoc opts-map :main (first options))))
+ (if (some opts-map [:file :socket-repl :expression :main])
(assoc opts-map
- :file opt
- :command-line-args (rest options))
- (assoc opts-map
- :expression opt
- :command-line-args (rest options)))))
+ :command-line-args options)
+ (if (and (not= \( (first (str/trim opt)))
+ (.exists (io/file opt)))
+ (assoc opts-map
+ :file opt
+ :command-line-args (next options))
+ (assoc opts-map
+ :expression opt
+ :command-line-args (next options))))))
opts-map))]
opts))
@@ -122,8 +136,9 @@
(println (str "babashka v"(str/trim (slurp (io/resource "BABASHKA_VERSION"))))))
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
- [ ( --classpath | -cp ) ] [ ( --main | -m ) ]
- ( -e | -f | --repl | --socket-repl [:] )
+ [ ( --classpath | -cp ) ] [ --uberscript ]
+ [ ( --main | -m ) | -e | -f |
+ --repl | --socket-repl [:] ]
[ arg* ]")
(defn print-usage []
(println usage-string))
@@ -136,21 +151,24 @@
(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 Evaluate an expression.
- -f, --file Evaluate a file.
- -cp, --classpath Classpath to use.
- -m, --main 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.
+ --help, -h or -? Print this help text.
+ --version Print the current version of babashka.
+
+ -i Bind *input* to a lazy seq of lines from stdin.
+ -I Bind *input* 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 *input* becomes a single value per iteration.
+ --uberscript Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
+
+ -e, --eval Evaluate an expression.
+ -f, --file Evaluate a file.
+ -cp, --classpath Classpath to use.
+ -m, --main 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*."))
@@ -167,28 +185,21 @@ Everything after that is bound to *command-line-args*."))
(edn/read {;;:readers *data-readers*
:eof ::EOF} *in*))
-(defn load-file* [ctx file]
- (let [s (slurp file)]
- (eval-string s ctx)))
+(def reflection-var (sci/new-dynamic-var '*warn-on-reflection* false))
-(defn eval* [ctx form]
- (eval-string (pr-str form) ctx))
+(defn load-file* [sci-ctx f]
+ (let [f (io/file f)
+ s (slurp f)]
+ (sci/with-bindings {vars/file-var (.getCanonicalPath f)}
+ (eval-string* sci-ctx s))))
-(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 eval* [sci-ctx form]
+ (eval-string* sci-ctx (pr-str form)))
-(defn start-socket-repl! [address ctx read-next]
- (let [ctx (update ctx :bindings assoc
- (with-meta '*in*
- {:sci/deref! true})
- (read-next))]
- (socket-repl/start-repl! address ctx)
- ;; hang until SIGINT
- @(promise)))
+(defn start-socket-repl! [address ctx]
+ (socket-repl/start-repl! address ctx)
+ ;; hang until SIGINT
+ @(promise))
(defn exit [n]
(throw (ex-info "" {:bb/exit-code n})))
@@ -197,6 +208,49 @@ Everything after that is bound to *command-line-args*."))
;; (sci/set-var-root! sci/*out* *out*)
;; (sci/set-var-root! sci/*err* *err*)
+(def aliases
+ '{tools.cli 'clojure.tools.cli
+ edn clojure.edn
+ wait babashka.wait
+ sig babashka.signal
+ shell clojure.java.shell
+ io clojure.java.io
+ async clojure.core.async
+ csv clojure.data.csv
+ json cheshire.core})
+
+(def namespaces
+ {'clojure.tools.cli tools-cli-namespace
+ 'clojure.edn {'read edn/read
+ 'read-string edn/read-string}
+ 'clojure.java.shell shell-namespace
+ 'babashka.wait {'wait-for-port wait/wait-for-port
+ 'wait-for-path wait/wait-for-path}
+ 'babashka.signal {'pipe-signal-received? pipe-signal-received?}
+ 'clojure.java.io io-namespace
+ 'clojure.core.async async-namespace
+ 'clojure.data.csv csv/csv-namespace
+ 'cheshire.core cheshire-core-namespace
+ 'clojure.stacktrace stacktrace-namespace
+ 'clojure.main {'demunge demunge}
+ 'clojure.repl {'demunge demunge}
+ 'clojure.data.xml xml/xml-namespace})
+
+(def bindings
+ {'java.lang.System/exit exit ;; override exit, so we have more control
+ 'System/exit exit})
+
+(defn error-handler* [^Exception e verbose?]
+ (binding [*out* *err*]
+ (let [d (ex-data e)
+ exit-code (:bb/exit-code d)]
+ (if exit-code [nil exit-code]
+ (do (if verbose?
+ (print-stack-trace e)
+ (println (.getMessage e)))
+ (flush)
+ [nil 1])))))
+
(defn main
[& args]
(handle-pipe!)
@@ -208,7 +262,7 @@ Everything after that is bound to *command-line-args*."))
:expression :stream? :time?
:repl :socket-repl
:verbose? :classpath
- :main] :as _opts}
+ :main :uberscript] :as _opts}
(parse-opts args)
read-next (fn [*in*]
(if (pipe-signal-received?)
@@ -222,6 +276,7 @@ Everything after that is bound to *command-line-args*."))
(edn-seq *in*)
:else
(edn/read *in*))))))
+ uberscript-sources (atom ())
env (atom {})
classpath (or classpath
(System/getenv "BABASHKA_CLASSPATH"))
@@ -229,133 +284,122 @@ Everything after that is bound to *command-line-args*."))
(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
- sig babashka.signal
- shell clojure.java.shell
- io clojure.java.io
- conch me.raynes.conch.low-level
- async clojure.core.async
- 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
- 'clojure.edn {'read-string edn/read-string}
- 'clojure.java.shell {'sh shell/sh}
- 'babashka.wait {'wait-for-port wait/wait-for-port
- 'wait-for-path wait/wait-for-path}
- 'babashka.signal {'pipe-signal-received? pipe-signal-received?}
- 'clojure.java.io io-namespace
- 'me.raynes.conch.low-level conch-namespace
- 'clojure.core.async async-namespace
- 'clojure.data.csv csv/csv-namespace
- 'cheshire.core cheshire-core-namespace
- 'clojure.data.xml xml/xml-namespace}
- :bindings {'java.lang.System/exit exit ;; override exit, so we have more control
- 'System/exit exit}
+ (let [res (cp/source-for-namespace loader namespace nil)]
+ (when uberscript (swap! uberscript-sources conj (:source res)))
+ res)))
+ _ (when file (vars/bindRoot vars/file-var (.getCanonicalPath (io/file file))))
+ ctx {:aliases aliases
+ :namespaces (-> namespaces
+ (assoc 'clojure.core
+ (assoc core-extras
+ '*command-line-args*
+ (sci/new-dynamic-var '*command-line-args* command-line-args)
+ '*file* vars/file-var
+ '*warn-on-reflection* reflection-var))
+ (assoc-in ['clojure.java.io 'resource]
+ #(when classpath (cp/getResource loader % {:url? true}))))
+ :bindings bindings
:env env
:features #{:bb}
- :classes {'java.lang.ArithmeticException ArithmeticException
- '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
- 'clojure.lang.ExceptionInfo clojure.lang.ExceptionInfo
- 'java.lang.Integer Integer
- 'java.io.File java.io.File
- 'clojure.lang.LineNumberingPushbackReader clojure.lang.LineNumberingPushbackReader
- 'java.util.regex.Pattern java.util.regex.Pattern
- 'java.lang.String String
- 'java.io.StringReader java.io.StringReader
- 'java.io.StringWriter java.io.StringWriter
- '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
- 'java.nio.file.NoSuchFileException java.nio.file.NoSuchFileException
- 'java.nio.file.StandardCopyOption java.nio.file.StandardCopyOption
- }
+ :classes classes/class-map
:imports '{ArithmeticException java.lang.ArithmeticException
AssertionError java.lang.AssertionError
Boolean java.lang.Boolean
Class java.lang.Class
Double java.lang.Double
Exception java.lang.Exception
+ IllegalArgumentException java.lang.IllegalArgumentException
Integer java.lang.Integer
File java.io.File
+ Math java.lang.Math
+ Object java.lang.Object
+ ProcessBuilder java.lang.ProcessBuilder
String java.lang.String
System java.lang.System
Thread java.lang.Thread}
- :load-fn load-fn}
- ctx (update ctx :bindings assoc 'eval #(eval* ctx %)
- 'load-file #(load-file* ctx %))
+ :load-fn load-fn
+ :dry-run uberscript}
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)
+ sci-ctx (sci-opts/init ctx)
+ _ (swap! (:env sci-ctx)
+ (fn [env]
+ (update-in env [:namespaces 'clojure.core] assoc
+ 'eval #(eval* sci-ctx %)
+ 'load-file #(load-file* sci-ctx %))))
+ _ (swap! (:env sci-ctx)
+ (fn [env]
+ (assoc-in env [:namespaces 'clojure.main 'repl]
+ (fn [& opts]
+ (let [opts (apply hash-map opts)]
+ (repl/start-repl! sci-ctx opts))))))
+ preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim))
+ [expression exit-code]
+ (cond expression [expression nil]
+ main [(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
+ main) nil]
+ file (try [(read-file file) nil]
+ (catch Exception e
+ (error-handler* e verbose?))))
exit-code
- (or
- #_(binding [*out* *err*]
- (prn ">>" _opts))
- (second
- (cond version
- [(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
- (let [expr (if file (read-file file) expression)]
- (if expr
- (loop [in (read-next *in*)]
- (let [ctx (update-in ctx [:namespaces 'user] assoc (with-meta '*in*
- (when-not stream?
- {:sci/deref! true})) in)]
- (if (identical? ::EOF in)
- [nil 0] ;; done streaming
- (let [res [(let [res (eval-string expr ctx)]
- (when (some? res)
- (if-let [pr-f (cond shell-out println
- edn-out prn)]
- (if (coll? res)
- (doseq [l res
- :while (not (pipe-signal-received?))]
- (pr-f l))
- (pr-f res))
- (prn res)))) 0]]
- (if stream?
- (recur (read-next *in*))
- res)))))
- [(start-repl! ctx #(read-next *in*)) 0]))
- (catch Throwable e
- (binding [*out* *err*]
- (let [d (ex-data e)
- exit-code (:bb/exit-code d)]
- (if exit-code [nil exit-code]
- (do (if verbose?
- (print-stack-trace e)
- (println (.getMessage e)))
- (flush)
- [nil 1]))))))))
- 1)
+ ;; handle preloads
+ (if exit-code exit-code
+ (do (when preloads (try (eval-string* sci-ctx preloads)
+ (catch Throwable e
+ (error-handler* e verbose?))))
+ nil))
+ exit-code
+ (or exit-code
+ (sci/with-bindings {reflection-var false}
+ (or
+ #_(binding [*out* *err*]
+ (prn ">>" _opts))
+ (second
+ (cond version
+ [(print-version) 0]
+ help?
+ [(print-help) 0]
+ repl [(repl/start-repl! sci-ctx) 0]
+ socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
+ (not (str/blank? expression))
+ (try
+ (loop [in (read-next *in*)]
+ (let [_ (swap! env update-in [:namespaces 'user]
+ assoc (with-meta '*input*
+ (when-not stream?
+ {:sci.impl/deref! true}))
+ (sci/new-dynamic-var '*input* in))]
+ (if (identical? ::EOF in)
+ [nil 0] ;; done streaming
+ (let [res [(let [res (eval-string* sci-ctx expression)]
+ (when (some? res)
+ (if-let [pr-f (cond shell-out println
+ edn-out prn)]
+ (if (coll? res)
+ (doseq [l res
+ :while (not (pipe-signal-received?))]
+ (pr-f l))
+ (pr-f res))
+ (prn res)))) 0]]
+ (if stream?
+ (recur (read-next *in*))
+ res)))))
+ (catch Throwable e
+ (error-handler* e verbose?)))
+ uberscript [nil 0]
+ :else [(repl/start-repl! sci-ctx) 0]))
+ 1)))
t1 (System/currentTimeMillis)]
+ (flush)
+ (when uberscript
+ uberscript
+ (let [uberscript-out uberscript]
+ (spit uberscript-out "") ;; reset file
+ (doseq [s @uberscript-sources]
+ (spit uberscript-out s :append true))
+ (spit uberscript-out preloads :append true)
+ (spit uberscript-out expression :append true)))
(when time? (binding [*out* *err*]
(println "bb took" (str (- t1 t0) "ms."))))
- (flush)
exit-code))
(defn -main
diff --git a/src/babashka/wait.clj b/src/babashka/wait.clj
index dbf54d2b..af2cc9d4 100644
--- a/src/babashka/wait.clj
+++ b/src/babashka/wait.clj
@@ -17,8 +17,8 @@
opts)
t0 (System/currentTimeMillis)]
(loop []
- (let [v (try (Socket. host port)
- (- (System/currentTimeMillis) t0)
+ (let [v (try (with-open [_ (Socket. host port)]
+ (- (System/currentTimeMillis) t0))
(catch ConnectException _e
(let [took (- (System/currentTimeMillis) t0)]
(if (and timeout (>= took timeout))
diff --git a/test-resources/babashka/src_for_classpath_test/ns_with_error.clj b/test-resources/babashka/src_for_classpath_test/ns_with_error.clj
new file mode 100644
index 00000000..d1f909ba
--- /dev/null
+++ b/test-resources/babashka/src_for_classpath_test/ns_with_error.clj
@@ -0,0 +1,4 @@
+(ns ns-with-error)
+
+(def x 0)
+(def y (/ 1 0))
diff --git a/test/babashka/classpath_test.clj b/test/babashka/classpath_test.clj
index b3b876d3..dd20b197 100644
--- a/test/babashka/classpath_test.clj
+++ b/test/babashka/classpath_test.clj
@@ -2,7 +2,8 @@
(:require
[babashka.test-utils :as tu]
[clojure.edn :as edn]
- [clojure.test :as t :refer [deftest is]]))
+ [clojure.test :as t :refer [deftest is]]
+ [clojure.java.io :as io]))
(defn bb [input & args]
(edn/read-string (apply tu/bb (when (some? input) (str input)) (map str args))))
@@ -25,3 +26,26 @@
(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"))))
+
+(deftest uberscript-test
+ (let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
+ (.deleteOnExit tmp-file)
+ (tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "--uberscript" (.getPath tmp-file))
+ (is (= "(\"1\" \"2\" \"3\" \"4\")\n"
+ (tu/bb nil "--file" (.getPath tmp-file) "1" "2" "3" "4")))))
+
+(deftest error-while-loading-test
+ (is (true?
+ (bb nil "--classpath" "test-resources/babashka/src_for_classpath_test"
+ "
+(try
+ (require '[ns-with-error])
+ (catch Exception nil))
+(nil? (resolve 'ns-with-error/x))"))))
+
+(deftest resource-test
+ (let [tmp-file (java.io.File/createTempFile "icon" ".png")]
+ (.deleteOnExit tmp-file)
+ (bb nil "--classpath" "logo" "-e" (format "(io/copy (io/input-stream (io/resource \"icon.png\")) (io/file \"%s\"))" (.getPath tmp-file)))
+ (is (= (.length (io/file "logo" "icon.png"))
+ (.length tmp-file)))))
diff --git a/test/babashka/file_var_test.clj b/test/babashka/file_var_test.clj
new file mode 100644
index 00000000..909373bb
--- /dev/null
+++ b/test/babashka/file_var_test.clj
@@ -0,0 +1,17 @@
+(ns babashka.file-var-test
+ (:require
+ [babashka.test-utils :as tu]
+ [clojure.test :as t :refer [deftest is]]
+ [clojure.string :as str]))
+
+(defn bb [input & args]
+ (apply tu/bb (when (some? input) (str input)) (map str args)))
+
+(deftest file-var-test
+ (let [[f1 f2 f3]
+ (str/split (bb nil "--classpath" "test/babashka/scripts"
+ "test/babashka/scripts/file_var.bb")
+ #"\n")]
+ (is (str/ends-with? f1 "file_var_classpath.bb"))
+ (is (str/ends-with? f2 "loaded_by_file_var.bb"))
+ (is (str/ends-with? f3 "file_var.bb"))))
diff --git a/test/babashka/http_connection_test.clj b/test/babashka/http_connection_test.clj
new file mode 100644
index 00000000..ec78fa12
--- /dev/null
+++ b/test/babashka/http_connection_test.clj
@@ -0,0 +1,21 @@
+(ns babashka.http-connection-test
+ (:require
+ [babashka.test-utils :as tu]
+ [clojure.test :as t :refer [deftest is]]
+ [clojure.string :as str]))
+
+(defn bb [& args]
+ (apply tu/bb nil (map str args)))
+
+(deftest open-connection-test
+ (is (= "\"1\"" (str/trim (bb "-e" "
+(require '[cheshire.core :as json])
+(let [conn ^java.net.HttpURLConnection (.openConnection (java.net.URL. \"https://postman-echo.com/get?foo=1\"))]
+ (.setConnectTimeout conn 1000)
+ (.setRequestProperty conn \"Content-Type\" \"application/json\") ;; nonsensical, but to test if this method exists
+ (.connect conn)
+ (let [is (.getInputStream conn)
+ err (.getErrorStream conn)
+ response (json/decode (slurp is) true)]
+ (-> response :args :foo)))
+")))))
diff --git a/test/babashka/impl/clojure/java/shell_test.clj b/test/babashka/impl/clojure/java/shell_test.clj
new file mode 100644
index 00000000..0f1cd8db
--- /dev/null
+++ b/test/babashka/impl/clojure/java/shell_test.clj
@@ -0,0 +1,17 @@
+(ns babashka.impl.clojure.java.shell-test
+ (:require [clojure.test :as t :refer [deftest is testing]]
+ [babashka.test-utils :as test-utils]
+ [clojure.string :as str]))
+
+(deftest with-sh-env-test
+ (is (= "\"BAR\""
+ (str/trim (test-utils/bb nil "
+(-> (shell/with-sh-env {:FOO \"BAR\"}
+ (shell/sh \"bash\" \"-c\" \"echo $FOO\"))
+ :out
+ str/trim)"))))
+ (is (str/includes? (str/trim (test-utils/bb nil "
+(-> (shell/with-sh-dir \"logo\"
+ (shell/sh \"ls\"))
+ :out)"))
+ "icon.svg")))
diff --git a/test/babashka/impl/repl_test.clj b/test/babashka/impl/repl_test.clj
index 8adbde77..d0f1bc5b 100644
--- a/test/babashka/impl/repl_test.clj
+++ b/test/babashka/impl/repl_test.clj
@@ -2,32 +2,38 @@
(:require
[babashka.impl.repl :refer [start-repl!]]
[clojure.string :as str]
- [clojure.test :as t :refer [deftest is]]))
+ [clojure.test :as t :refer [deftest is]]
+ [sci.impl.opts :refer [init]]
+ [sci.core :as sci]
+ [sci.impl.vars :as vars]))
(set! *warn-on-reflection* true)
+;; (vars/bindRoot sci/in *in*)
+;; (vars/bindRoot sci/out *out*)
+(vars/bindRoot sci/err *err*)
+
(defn repl! []
- (start-repl! {:bindings {(with-meta '*in*
- {:sci/deref! true})
- (delay [1 2 3])
- '*command-line-args*
- ["a" "b" "c"]}
- :env (atom {})}))
+ (start-repl! (init {:bindings {'*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")
+ (is (str/includes? (sci/with-out-str
+ (sci/with-in-str (str expr "\n:repl/quit")
(repl!))) expected)))
(deftest repl-test
+ (assert-repl "1" "1")
+ (assert-repl "[1 2 3]" "[1 2 3]")
+ (assert-repl "()" "()")
(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]"))
+ (assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]"))
;;;; Scratch
diff --git a/test/babashka/impl/socket_repl_test.clj b/test/babashka/impl/socket_repl_test.clj
index 085561dd..83038f0e 100644
--- a/test/babashka/impl/socket_repl_test.clj
+++ b/test/babashka/impl/socket_repl_test.clj
@@ -5,7 +5,8 @@
[clojure.java.shell :refer [sh]]
[clojure.string :as str]
[clojure.test :as t :refer [deftest is testing]]
- [clojure.java.io :as io]))
+ [clojure.java.io :as io]
+ [sci.impl.opts :refer [init]]))
(set! *warn-on-reflection* true)
@@ -31,13 +32,10 @@
(deftest socket-repl-test
(try
(if tu/jvm?
- (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 {})
- :features #{:bb}})
+ (start-repl! "0.0.0.0:1666" (init {:bindings {'*command-line-args*
+ ["a" "b" "c"]}
+ :env (atom {})
+ :features #{:bb}}))
(future
(sh "bash" "-c"
"echo '[1 2 3]' | ./bb --socket-repl 0.0.0.0:1666 a b c")))
@@ -47,8 +45,6 @@
(sh "bash" "-c"
"lsof -t -i:1666"))))))
(is (socket-command "(+ 1 2 3)" "user=> 6"))
- (testing "*in*"
- (is (socket-command "*in*" "[1 2 3]")))
(testing "*command-line-args*"
(is (socket-command '*command-line-args* "\"a\" \"b\" \"c\"")))
(testing "&env"
@@ -72,7 +68,7 @@
(dotimes [_ 1000]
(t/run-tests))
(stop-repl!)
- (start-repl! "0.0.0.0:1666" {:bindings {(with-meta '*in*
+ (start-repl! "0.0.0.0:1666" {:bindings {(with-meta '*input*
{:sci/deref! true})
(delay [1 2 3])
'*command-line-args*
diff --git a/test/babashka/java_time_test.clj b/test/babashka/java_time_test.clj
new file mode 100644
index 00000000..65b22709
--- /dev/null
+++ b/test/babashka/java_time_test.clj
@@ -0,0 +1,19 @@
+(ns babashka.java-time-test
+ (:require
+ [babashka.test-utils :as test-utils]
+ [clojure.edn :as edn]
+ [clojure.test :as test :refer [deftest is]]))
+
+(defn bb [expr]
+ (edn/read-string (apply test-utils/bb nil [(str expr)])))
+
+(deftest java-time-test
+ (is (= "2019-12-18" (bb '(str (java.time.LocalDate/of 2019 12 18)))))
+ (is (= "2019-12-01" (bb '(str
+ (-> (java.time.LocalDate/of 2019 12 18)
+ (.minusDays 17))))))
+ (is (= "MONDAY" (bb '(str java.time.DayOfWeek/MONDAY))))
+ (is (= "18-12-2019 16:01:41"
+ (bb '(.format
+ (java.time.LocalDateTime/parse "2019-12-18T16:01:41.485")
+ (java.time.format.DateTimeFormatter/ofPattern "dd-MM-yyyy HH:mm:ss"))))))
diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj
index e1f2a224..0e90881b 100644
--- a/test/babashka/main_test.clj
+++ b/test/babashka/main_test.clj
@@ -3,10 +3,10 @@
[babashka.main :as main]
[babashka.test-utils :as test-utils]
[clojure.edn :as edn]
+ [clojure.java.io :as io]
[clojure.java.shell :refer [sh]]
[clojure.string :as str]
[clojure.test :as test :refer [deftest is testing]]
- [clojure.java.io :as io]
[sci.core :as sci]))
(defn bb [input & args]
@@ -21,58 +21,58 @@
(testing "distinguish automatically between expression or file name"
(is (= {:expression "(println 123)"
- :command-line-args []}
+ :command-line-args nil}
(main/parse-opts ["(println 123)"])))
(is (= {:file "src/babashka/main.clj"
- :command-line-args []}
+ :command-line-args nil}
(main/parse-opts ["src/babashka/main.clj"])))
(is (= {:expression "does-not-exist"
- :command-line-args []}
+ :command-line-args nil}
(main/parse-opts ["does-not-exist"])))))
(deftest main-test
(testing "-io behaves as identity"
- (= "foo\nbar\n" (test-utils/bb "foo\nbar\n" "-io" "*in*")))
+ (= "foo\nbar\n" (test-utils/bb "foo\nbar\n" "-io" "*input*")))
(testing "if and when"
- (is (= 1 (bb 0 '(if (zero? *in*) 1 2))))
- (is (= 2 (bb 1 '(if (zero? *in*) 1 2))))
- (is (= 1 (bb 0 '(when (zero? *in*) 1))))
- (is (nil? (bb 1 '(when (zero? *in*) 1)))))
+ (is (= 1 (bb 0 '(if (zero? *input*) 1 2))))
+ (is (= 2 (bb 1 '(if (zero? *input*) 1 2))))
+ (is (= 1 (bb 0 '(when (zero? *input*) 1))))
+ (is (nil? (bb 1 '(when (zero? *input*) 1)))))
(testing "and and or"
- (is (= false (bb 0 '(and false true *in*))))
- (is (= 0 (bb 0 '(and true true *in*))))
- (is (= 1 (bb 1 '(or false false *in*))))
- (is (= false (bb false '(or false false *in*))))
- (is (= 3 (bb false '(or false false *in* 3)))))
+ (is (= false (bb 0 '(and false true *input*))))
+ (is (= 0 (bb 0 '(and true true *input*))))
+ (is (= 1 (bb 1 '(or false false *input*))))
+ (is (= false (bb false '(or false false *input*))))
+ (is (= 3 (bb false '(or false false *input* 3)))))
(testing "fn"
- (is (= 2 (bb 1 "(#(+ 1 %) *in*)")))
+ (is (= 2 (bb 1 "(#(+ 1 %) *input*)")))
(is (= [1 2 3] (bb 1 "(map #(+ 1 %) [0 1 2])")))
- (is (= 1 (bb 1 "(#(when (odd? *in*) *in*))"))))
+ (is (= 1 (bb 1 "(#(when (odd? *input*) *input*))"))))
(testing "map"
(is (= [1 2 3] (bb 1 '(map inc [0 1 2])))))
(testing "keep"
(is (= [false true false] (bb 1 '(keep odd? [0 1 2])))))
(testing "->"
- (is (= 4 (bb 1 '(-> *in* inc inc (inc))))))
+ (is (= 4 (bb 1 '(-> *input* inc inc (inc))))))
(testing "->>"
- (is (= 10 (edn/read-string (test-utils/bb "foo\n\baar\baaaaz" "-i" "(->> *in* (map count) (apply max))")))))
+ (is (= 10 (edn/read-string (test-utils/bb "foo\n\baar\baaaaz" "-i" "(->> *input* (map count) (apply max))")))))
(testing "literals"
(is (= {:a 4
:b {:a 2}
:c [1 1]
:d #{1 2}}
- (bb 1 '{:a (+ 1 2 *in*)
- :b {:a (inc *in*)}
- :c [*in* *in*]
- :d #{*in* (inc *in*)}}))))
+ (bb 1 '{:a (+ 1 2 *input*)
+ :b {:a (inc *input*)}
+ :c [*input* *input*]
+ :d #{*input* (inc *input*)}}))))
(testing "shuffle the contents of a file"
(let [in "foo\n Clojure is nice. \nbar\n If you're nice to clojure. "
in-lines (set (str/split in #"\n"))
out (test-utils/bb in
"-io"
- (str '(shuffle *in*)))
+ (str '(shuffle *input*)))
out-lines (set (str/split out #"\n"))]
(is (= in-lines out-lines))))
(testing "find occurrences in file by line number"
@@ -80,14 +80,14 @@
(->
(bb "foo\n Clojure is nice. \nbar\n If you're nice to clojure. "
"-i"
- "(map-indexed #(-> [%1 %2]) *in*)")
- (bb "(keep #(when (re-find #\"(?i)clojure\" (second %)) (first %)) *in*)"))))))
+ "(map-indexed #(-> [%1 %2]) *input*)")
+ (bb "(keep #(when (re-find #\"(?i)clojure\" (second %)) (first %)) *input*)"))))))
(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"
+ (testing "bb doesn't wait for input if *input* isn't used"
(is (= "2\n" (with-out-str (main/main "(inc 1)"))))))
(deftest System-test
@@ -114,12 +114,12 @@
(is (re-find #"doctype html" resp))))
(deftest stream-test
- (is (= "2\n3\n4\n" (test-utils/bb "1 2 3" "--stream" "(inc *in*)")))
- (is (= "2\n3\n4\n" (test-utils/bb "{:x 2} {:x 3} {:x 4}" "--stream" "(:x *in*)")))
+ (is (= "2\n3\n4\n" (test-utils/bb "1 2 3" "--stream" "(inc *input*)")))
+ (is (= "2\n3\n4\n" (test-utils/bb "{:x 2} {:x 3} {:x 4}" "--stream" "(:x *input*)")))
(let [x "foo\n\bar\n"]
- (is (= x (test-utils/bb x "--stream" "-io" "*in*"))))
+ (is (= x (test-utils/bb x "--stream" "-io" "*input*"))))
(let [x "f\n\b\n"]
- (is (= x (test-utils/bb x "--stream" "-io" "(subs *in* 0 1)")))))
+ (is (= x (test-utils/bb x "--stream" "-io" "(subs *input* 0 1)")))))
(deftest load-file-test
(let [tmp (java.io.File/createTempFile "script" ".clj")]
@@ -145,29 +145,35 @@
(deftest pipe-test
(when test-utils/native?
(let [out (:out (sh "bash" "-c" "./bb -o '(range)' |
- ./bb --stream '(* *in* *in*)' |
+ ./bb --stream '(* *input* *input*)' |
head -n10"))
out (str/split-lines out)
out (map edn/read-string out)]
(is (= (take 10 (map #(* % %) (range))) out))))
(when test-utils/native?
(let [out (:out (sh "bash" "-c" "./bb -O '(repeat \"dude\")' |
- ./bb --stream '(str *in* \"rino\")' |
- ./bb -I '(take 3 *in*)'"))
+ ./bb --stream '(str *input* \"rino\")' |
+ ./bb -I '(take 3 *input*)'"))
out (edn/read-string out)]
(is (= '("duderino" "duderino" "duderino") out)))))
(deftest lazy-text-in-test
(when test-utils/native?
- (let [out (:out (sh "bash" "-c" "yes | ./bb -i '(take 2 *in*)'"))
+ (let [out (:out (sh "bash" "-c" "yes | ./bb -i '(take 2 *input*)'"))
out (edn/read-string out)]
(is (= '("y" "y") out)))))
(deftest future-test
(is (= 6 (bb nil "@(future (+ 1 2 3))"))))
-(deftest conch-test
- (is (str/includes? (bb nil "(->> (conch/proc \"ls\") (conch/stream-to-string :out))")
+(deftest process-builder-test
+ (is (str/includes? (bb nil "
+(def ls (-> (ProcessBuilder. [\"ls\"]) (.start)))
+(def input (.getOutputStream ls))
+(.write (io/writer input) \"hello\") ;; dummy test just to see if this works
+(def output (.getInputStream ls))
+(assert (int? (.waitFor ls)))
+(slurp output)")
"LICENSE")))
(deftest create-temp-file-test
@@ -181,11 +187,14 @@
temp-dir-path))))))
(deftest wait-for-port-test
- (is (= :timed-out
- (bb nil "(def web-server (conch/proc \"python\" \"-m\" \"SimpleHTTPServer\" \"7171\"))
- (wait/wait-for-port \"127.0.0.1\" 7171)
- (conch/destroy web-server)
- (wait/wait-for-port \"localhost\" 7172 {:default :timed-out :timeout 50})"))))
+ (let [server (test-utils/start-server! 1777)]
+ (is (= 1777 (:port (bb nil "(wait/wait-for-port \"127.0.0.1\" 1777)"))))
+ (test-utils/stop-server! server)
+ (is (= :timed-out (bb nil "(wait/wait-for-port \"127.0.0.1\" 1777 {:default :timed-out :timeout 50})"))))
+ (let [edn (bb nil (io/file "test" "babashka" "scripts" "socket_server.bb"))]
+ (is (= "127.0.0.1" (:host edn)))
+ (is (= 1777 (:port edn)))
+ (is (number? (:took edn)))))
(deftest wait-for-path-test
(let [temp-dir-path (System/getProperty "java.io.tmpdir")]
@@ -274,8 +283,8 @@
f2 (.toFile p')]
(bb nil (format
"(let [f (io/file \"%s\")
- p (.toPath (io/file f))
- p' (.resolveSibling p \"f2\")]
+ p (.toPath (io/file f))
+ p' (.resolveSibling p \"f2\")]
(.delete (.toFile p'))
(dotimes [_ 2]
(try
@@ -287,6 +296,48 @@
(deftest future-print-test
(testing "the root binding of sci/*out*"
- (is (= "hello" (bb nil "@(future (prn \"hello\"))"))))
+ (is (= "hello" (bb nil "@(future (prn \"hello\"))")))))
+(deftest Math-test
+ (is (== 8.0 (bb nil "(Math/pow 2 3)"))))
+
+(deftest Base64-test
+ (is (= "babashka"
+ (bb nil "(String. (.decode (java.util.Base64/getDecoder) (.encode (java.util.Base64/getEncoder) (.getBytes \"babashka\"))))"))))
+
+(deftest Thread-test
+ (is (= "hello" (bb nil "(doto (java.lang.Thread. (fn [] (prn \"hello\"))) (.start) (.join)) nil"))))
+
+(deftest dynvar-test
+ (is (= 1 (bb nil "(binding [*command-line-args* 1] *command-line-args*)")))
+ (is (= 1 (bb nil "(binding [*input* 1] *input*)"))))
+
+(deftest file-in-error-msg-test
+ (is (thrown-with-msg? Exception #"error.bb"
+ (bb nil (.getPath (io/file "test" "babashka" "scripts" "error.bb"))))))
+
+(deftest compatibility-test
+ (is (true? (bb nil "(set! *warn-on-reflection* true)"))))
+
+(deftest clojure-main-repl-test
+ (is (= "\"> foo!\\nnil\\n> \"\n" (test-utils/bb nil "
+(defn foo [] (println \"foo!\"))
+(with-out-str
+ (with-in-str \"(foo)\"
+ (clojure.main/repl :init (fn []) :prompt (fn [] (print \"> \")))))"))))
+
+(deftest command-line-args-test
+ (is (true? (bb nil "(nil? *command-line-args*)")))
+ (is (= ["1" "2" "3"] (bb nil "*command-line-args*" "1" "2" "3"))))
+
+(deftest need-constructors-test
+ (testing "the clojure.lang.Delay constructor works"
+ (is (= 1 (bb nil "@(delay 1)"))))
+ (testing "the clojure.lang.MapEntry constructor works"
+ (is (true? (bb nil "(= (first {1 2}) (clojure.lang.MapEntry. 1 2))")))))
+
+;;;; Scratch
+
+(comment
+ (dotimes [_ 10] (wait-for-port-test))
)
diff --git a/test/babashka/scripts/error.bb b/test/babashka/scripts/error.bb
new file mode 100644
index 00000000..f4333f8b
--- /dev/null
+++ b/test/babashka/scripts/error.bb
@@ -0,0 +1 @@
+(/ 1 0)
diff --git a/test/babashka/scripts/file_var.bb b/test/babashka/scripts/file_var.bb
new file mode 100644
index 00000000..a35bf126
--- /dev/null
+++ b/test/babashka/scripts/file_var.bb
@@ -0,0 +1,6 @@
+(ns file-var
+ (:require [clojure.java.io :as io]))
+
+(require '[file-var-classpath])
+(load-file (io/file "test" "babashka" "scripts" "loaded_by_file_var.bb"))
+(println *file*)
diff --git a/test/babashka/scripts/file_var_classpath.bb b/test/babashka/scripts/file_var_classpath.bb
new file mode 100644
index 00000000..9c67bd2f
--- /dev/null
+++ b/test/babashka/scripts/file_var_classpath.bb
@@ -0,0 +1 @@
+(println *file*)
diff --git a/test/babashka/scripts/loaded_by_file_var.bb b/test/babashka/scripts/loaded_by_file_var.bb
new file mode 100644
index 00000000..c34fdfa2
--- /dev/null
+++ b/test/babashka/scripts/loaded_by_file_var.bb
@@ -0,0 +1,3 @@
+(ns loaded-by-file-var)
+
+(println *file*)
diff --git a/test/babashka/scripts/socket_server.bb b/test/babashka/scripts/socket_server.bb
new file mode 100644
index 00000000..76467255
--- /dev/null
+++ b/test/babashka/scripts/socket_server.bb
@@ -0,0 +1,22 @@
+(require '[babashka.wait :as wait])
+
+(defn socket-loop [^java.net.ServerSocket server]
+ (with-open [listener server]
+ (loop []
+ (with-open [socket (.accept listener)]
+ (let [input-stream (.getInputStream socket)]
+ (print (slurp input-stream))
+ (flush)))
+ (recur))))
+
+(defn start-server! [port]
+ (let [server (java.net.ServerSocket. port)]
+ (future (socket-loop server))
+ server))
+
+(defn stop-server! [^java.net.ServerSocket server]
+ (.close server))
+
+(let [server (start-server! 1777)]
+ (prn (wait/wait-for-port "127.0.0.1" 1777))
+ (stop-server! server))
diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj
index 5a42415d..071e263c 100644
--- a/test/babashka/test_utils.clj
+++ b/test/babashka/test_utils.clj
@@ -2,7 +2,8 @@
(:require
[babashka.main :as main]
[me.raynes.conch :refer [let-programs] :as sh]
- [sci.core :as sci]))
+ [sci.core :as sci]
+ [sci.impl.vars :as vars]))
(set! *warn-on-reflection* true)
@@ -14,17 +15,25 @@
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)
- {:stdout (str os)
- :stderr (str es)})))))))
+ (try
+ (when input (vars/bindRoot sci/in is))
+ (vars/bindRoot sci/out os)
+ (vars/bindRoot sci/err es)
+ (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)
+ {:stdout (str os)
+ :stderr (str es)})))))
+ (finally
+ (when input (vars/bindRoot sci/in *in*))
+ (vars/bindRoot sci/out *out*)
+ (vars/bindRoot sci/err *err*)))))
(defn bb-native [input & args]
(let-programs [bb "./bb"]
@@ -49,3 +58,20 @@
(if jvm?
(println "==== Testing JVM version")
(println "==== Testing native version"))
+
+(defn socket-loop [^java.net.ServerSocket server]
+ (with-open [listener server]
+ (loop []
+ (with-open [socket (.accept listener)]
+ (let [input-stream (.getInputStream socket)]
+ (print (slurp input-stream))
+ (flush)))
+ (recur))))
+
+(defn start-server! [port]
+ (let [server (java.net.ServerSocket. port)]
+ (future (socket-loop server))
+ server))
+
+(defn stop-server! [^java.net.ServerSocket server]
+ (.close server))