diff --git a/.circleci/config.yml b/.circleci/config.yml index 5aeb3fe7..3dafd774 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -225,6 +225,95 @@ jobs: name: Publish artifact link to Slack command: | ./bb .circleci/script/publish_artifact.clj || true + linux-aarch64: + machine: + enabled: true + image: ubuntu-2004:202101-01 + resource_class: arm.large + working_directory: ~/repo + environment: + LEIN_ROOT: "true" + GRAALVM_HOME: /home/circleci/graalvm-ce-java11-21.0.0 + BABASHKA_PLATFORM: linux # used in release script + BABASHKA_ARCH: aarch64 + BABASHKA_TEST_ENV: native + BABASHKA_XMX: "-J-Xmx6500m" + steps: + - checkout + - run: + name: "Pull Submodules" + command: | + git submodule init + git submodule update + - run: + name: "Short circuit on SNAPSHOT" + command: | + VERSION=$(cat resources/BABASHKA_VERSION) + if [[ "$VERSION" == *-SNAPSHOT ]] + then + circleci task halt + fi + - restore_cache: + keys: + - linux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} + - run: + name: Install Leiningen + command: | + sudo script/install-leiningen + - run: + name: Install Clojure + command: | + wget https://download.clojure.org/install/linux-install-1.10.1.447.sh + chmod +x linux-install-1.10.1.447.sh + sudo ./linux-install-1.10.1.447.sh + - run: + name: Install lsof + command: | + sudo apt-get install lsof + - run: + name: Install native dev tools + command: | + sudo apt-get update + sudo apt-get -y install gcc g++ zlib1g-dev + - run: + name: Download GraalVM + command: | + cd ~ + if ! [ -d graalvm-ce-java11-21.0.0 ]; then + curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0/graalvm-ce-java11-linux-aarch64-21.0.0.tar.gz + tar xzf graalvm-ce-java11-linux-aarch64-21.0.0.tar.gz + fi + - run: + name: Build binary + command: | + script/uberjar + script/compile + no_output_timeout: 30m + - run: + name: Run tests + command: | + script/test + script/run_lib_tests + - run: + name: Release + command: | + .circleci/script/release + - persist_to_workspace: + root: /tmp + paths: + - release + - save_cache: + paths: + - ~/.m2 + - ~/graalvm-ce-java11-21.0.0 + key: linux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} + - store_artifacts: + path: /tmp/release + destination: release + - run: + name: Publish artifact link to Slack + command: | + ./bb .circleci/script/publish_artifact.clj || true mac: macos: xcode: "12.0.0" @@ -336,6 +425,7 @@ workflows: - linux - linux-static - mac + - linux-aarch64 - deploy: filters: branches: diff --git a/.circleci/script/docker b/.circleci/script/docker index 6291560e..1d75214c 100755 --- a/.circleci/script/docker +++ b/.circleci/script/docker @@ -17,12 +17,12 @@ fi if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ]; then echo "Building Docker image $image_name:$image_tag" echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin - unzip "/tmp/release/babashka-${image_tag}-linux-amd64.zip" + tar zxvf "/tmp/release/babashka-${image_tag}-linux-amd64.tar.gz" docker build -t "$image_name" -f Dockerfile.ci . docker tag "$image_name:$latest_tag" "$image_name:$image_tag" rm -f bb if [[ $snapshot == "false" ]]; then - unzip "/tmp/release/babashka-${image_tag}-linux-static-amd64.zip" + tar zxvf "/tmp/release/babashka-${image_tag}-linux-static-amd64.tar.gz" docker build -t "$image_name:alpine" -f Dockerfile.alpine . docker tag "$image_name:alpine" "$image_name:$image_tag-alpine" fi diff --git a/.circleci/script/publish_artifact.clj b/.circleci/script/publish_artifact.clj index 3ac5ff32..c7a9acca 100755 --- a/.circleci/script/publish_artifact.clj +++ b/.circleci/script/publish_artifact.clj @@ -16,7 +16,7 @@ (curl/post slack-hook-url {:headers {"content-type" "application/json"} :body json})))) -(def release-text (format "[%s - %s@%s]: https://%s-201467090-gh.circle-artifacts.com/0/release/babashka-%s-%s-amd64.zip" +(def release-text (format "[%s - %s@%s]: https://%s-201467090-gh.circle-artifacts.com/0/release/babashka-%s-%s-amd64.tar.gz" (System/getenv "BABASHKA_PLATFORM") (System/getenv "CIRCLE_BRANCH") (System/getenv "CIRCLE_SHA1") diff --git a/.circleci/script/release b/.circleci/script/release index 5e6325bf..092d7931 100755 --- a/.circleci/script/release +++ b/.circleci/script/release @@ -11,9 +11,11 @@ cd /tmp/release mkdir -p /tmp/bb_size ./bb '(spit "/tmp/bb_size/size" (.length (io/file "bb")))' -## release binary as zip archive +## release binary as tar.gz archive -zip "babashka-$VERSION-$BABASHKA_PLATFORM-amd64.zip" bb # bbk +arch=${BABASHKA_ARCH:-amd64} + +tar zcvf "babashka-$VERSION-$BABASHKA_PLATFORM-$arch.tar.gz" bb # bbk ## cleanup diff --git a/CHANGELOG.md b/CHANGELOG.md index 65c4c233..2afb7f07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,24 +2,126 @@ For a list of breaking changes, check [here](#breaking-changes). -## Unreleased +## 0.3.1 + +Babashka proper: + +- Support `bb.edn` project config with `:paths` and `:deps`. See [docs](https://book.babashka.org/index.html#_bb_edn). +- Rewrite CLI arg parsing to to subcommand style invocations: `bb --uberjar` becomes `bb uberjar` +- Support fully qualified symbol in `--main` option [#758](https://github.com/babashka/babashka/issues/758). See [docs](https://book.babashka.org/index.html#_invoking_a_main_function ). +- Support new `doc` option to retrieve a docstring from the command line + +Babashka.fs: + +- Create target dir automatically in `copy-tree` + +Babashka.nrepl: + +- Implement `cider-nrepl` `info` / `lookup` op [#30](https://github.com/babashka/babashka.nrepl/issues/30) [(@brdloush)](https://github.com/brdloush) + +Babashka.process: + +- Support tokenizing single string [#39](https://github.com/babashka/process/issues/39) +- Support `:extra-env` option [#40](https://github.com/babashka/process/issues/40) + +Deps.clj: + +- Catch up with Clojure CLI 1.10.3.814 [#40](https://github.com/borkdude/deps.clj/issues/40) + +Sci: + +- Support new kwargs handling from 1.11.0 [#553](https://github.com/borkdude/sci/issues/553) +- Allow dynamic `:doc` on `def`/`defn` [#554](https://github.com/borkdude/sci/issues/554) + +## 0.3.0 + +### New + +- Linux support for AArch64 [#241](https://github.com/babashka/babashka/issues/241). This means you can now run babashka on Raspberry Pi 64bit and Chromebooks with ARM 64-bit processors! + +A major thanks to [CircleCI](https://circleci.com/) for enabling AArch64 support +in the babashka organization and [GraalVM](http://graalvm.org/) for supporting this platform. + +### Enhancements / fixes + +- Fix `print-method` when writing to stdout [#667](https://github.com/babashka/babashka/issues/667) +- Fix interop with `System/out` [#754](https://github.com/babashka/babashka/issues/754) +- Support [version-clj](https://github.com/xsc/version-clj) v2.0.1 by adding `java.util.regex.Matcher` to the reflection config +- Distribute linux and macOS archives as `tar.gz`. The reason is that `unzip` is + not pre-installed on most unix-y systems. ([@grazfather](https://github.com/grazfather)) + +Babashka.fs: + +- Fix globbing on Windows +- Fix Windows tests +- Fix issue with `copy-tree` when dest dir doesn't exist yet + +Thanks [@lread](https://github.com/lread) for his help on fixing issues with Windows. + +Sci: + +- Support `:reload-all` [#552](https://github.com/borkdude/sci/issues/552) +- Narrow `reify` to just one class. See discussion in + [sci#549](https://github.com/borkdude/sci/issues/549). +- Add preliminary support for `proxy` (mainly to support pathom3 smart maps) + [sci#550](https://github.com/borkdude/sci/issues/550). + +Thanks to [@wilkerlucio](https://github.com/wilkerlucio) and + [@GreshamDanielStephens](https://github.com/GreshamDanielStephens) for their + help and discussions. + +## v0.2.13 + +### Enhancements / fixes + +- Add more interfaces to be used with `reify` ([@wilkerlucio](https://github.com/wilkerlucio)) (mostly to support smart maps with [pathom3](https://github.com/wilkerlucio/pathom3)) + +Babashka.curl: + +- Use `--data-binary` when sending files or streams [#35](https://github.com/babashka/babashka.curl/issues/35) + +Babashka.fs: + +- Add `create-link` and `split-paths` ([@eamonnsullivan](https://github.com/eamonnsullivan)) +- Add `split-ext` and `extension` ([@kiramclean](https://github.com/kiramclean)) +- Add `regular-file?`([@tekacs](https://github.com/tekacs)) +- Globbing is always recursive but should not be [#18](https://github.com/babashka/fs/issues/18) + +Sci: + +- Allow combinations of interfaces and protocols in `reify` [#540](https://github.com/borkdude/sci/issues/540) + ([@GreshamDanielStephens](https://github.com/GreshamDanielStephens)) +- Fix metadata on non-constant map literal expression [#546](https://github.com/borkdude/sci/issues/546) + +## 0.2.12 + +### Enhancements / fixes + +- Fix false positive cyclic dep problem with doric lib [#741](https://github.com/babashka/babashka/issues/741) + +## 0.2.11 ### Enhancements / fixes - Use default `*print-right-margin*` value from `clojure.pprint` +- Upgrade httpkit to 2.5.3 [#738](https://github.com/babashka/babashka/issues/738) +- Upgrade tools.cli to 1.0.206 +- Add several classes to be used with `defprotocol` (`PersistentVector`, `PersistentHashSet`, ...) +- Support reifying `clojure.lang.IFn` and `clojure.lang.ILookup` Sci: -- Detect cyclic load dependencies [#531](https://github.com/babashka/babashka/issues/531) -- Pick fn arity independent of written order [#532](https://github.com/babashka/babashka/issues/532) ([@GreshamDanielStephens](https://github.com/GreshamDanielStephens)) - -Babashka.fs: - -- Add `create-link` and `split-paths` +- Detect cyclic load dependencies [#531](https://github.com/borkdude/sci/issues/531) +- Pick fn arity independent of written order [#532](https://github.com/borkdude/sci/issues/532) ([@GreshamDanielStephens](https://github.com/GreshamDanielStephens)) +- `(instance? clojure.lang.IAtom 1)` returns `true` [#537](https://github.com/borkdude/sci/issues/537) +- Add `dissoc!`([@wilkerlucio](https://github.com/wilkerlucio)) +- Add `force` +- Fix `ns-unmap` on referred var [#539](https://github.com/borkdude/sci/issues/539) Babashka.nrepl: - Fix printing in lazy value [#36](https://github.com/babashka/babashka.nrepl/issues/36) +- Update link in nREPL server message [#37](https://github.com/babashka/babashka.nrepl/issues/37) ## 0.2.10 @@ -44,11 +146,11 @@ Babashka.nrepl: Sci: -- Fix error reporting in case of arity error [#518](https://github.com/babashka/babashka/issues/518) -- Shadowing record field names in protocol functions [#513](https://github.com/babashka/babashka/issues/513) -- Fix destructuring in protocol method for record [#512](https://github.com/babashka/babashka/issues/512) -- Faster processing of maps, sets and vectors [#482](https://github.com/babashka/babashka/issues/482) -- Prioritize current namespace vars in syntax quote [#509](https://github.com/babashka/babashka/issues/509) +- Fix error reporting in case of arity error [#518](https://github.com/borkdude/sci/issues/518) +- Shadowing record field names in protocol functions [#513](https://github.com/borkdude/sci/issues/513) +- Fix destructuring in protocol method for record [#512](https://github.com/borkdude/sci/issues/512) +- Faster processing of maps, sets and vectors [#482](https://github.com/borkdude/sci/issues/482) +- Prioritize current namespace vars in syntax quote [#509](https://github.com/borkdude/sci/issues/509) - Fix ns-publics to not include refers [#520](https://github.com/borkdude/sci/issues/520) - Add `refer-clojure` macro [#519](https://github.com/borkdude/sci/issues/519) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 63cf4561..2d931ad0 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -14,7 +14,7 @@ RUN echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswit RUN bb -e "(curl/get \"https://httpstat.us/200\")" # cURL http test RUN bb -e "(require '[org.httpkit.client :as http]) (when-let [error (:error @(http/get \"https://httpstat.us/200\"))] (throw error))" # JVM http test RUN bb -e "(.length \"Hello, Babashka\")" # Java interop test -RUN bb -e "(require '[babashka.pods :as pods]) (pods/load-pod 'org.babashka/sqlite3 \"0.0.1\") (require '[pod.babashka.sqlite3 :as sqlite]) (sqlite/execute! \"/tmp/foo.db\" [\"SELECT 1 + 1\"])" # Pod test +RUN bb -e "(require '[babashka.pods :as pods]) (pods/load-pod 'org.babashka/go-sqlite3 \"0.0.1\") (require '[pod.babashka.go-sqlite3 :as sqlite]) (sqlite/execute! \"/tmp/foo.db\" [\"SELECT 1 + 1\"])" # Pod test FROM alpine:3 diff --git a/README.md b/README.md index 47b6d4de..fa84f910 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ [](https://opencollective.com/babashka) [](https://clojars.org/babashka/babashka) [](https://twitter.com/search?q=%23babashka&src=typed_query&f=live) -A Clojure [babushka](https://en.wikipedia.org/wiki/Headscarf) for the grey areas of Bash. -
Life's too short to remember how to write Bash code. I feel liberated.
— @@ -15,8 +13,9 @@ A Clojure [babushka](https://en.wikipedia.org/wiki/Headscarf) for the grey areas ## Introduction -The main idea behind babashka is to leverage Clojure in places where you would -be using bash otherwise. +Babashka is a native Clojure interpreter for scripting with fast startup. Its +main goal is to leverage Clojure in places where you would be using bash +otherwise. As one user described it: diff --git a/babashka.curl b/babashka.curl index 33bfad1b..e1f33ffa 160000 --- a/babashka.curl +++ b/babashka.curl @@ -1 +1 @@ -Subproject commit 33bfad1b844927966a2a681d3e73e4d2a8eae54b +Subproject commit e1f33ffa22728553bf2242db5e2a4ca349fab04b diff --git a/babashka.nrepl b/babashka.nrepl index 15ff3c1b..3a790c3d 160000 --- a/babashka.nrepl +++ b/babashka.nrepl @@ -1 +1 @@ -Subproject commit 15ff3c1b43bfc30e806336547bdb0918f9e2521c +Subproject commit 3a790c3d378ddda190d650202cefba9b9930176c diff --git a/deps.clj b/deps.clj index 0b708449..ca0e98fc 160000 --- a/deps.clj +++ b/deps.clj @@ -1 +1 @@ -Subproject commit 0b70844983bf23fff4c2991dadb3fd14c4102b27 +Subproject commit ca0e98fcefb916c6c0e6fdac73800395b8923af8 diff --git a/deps.edn b/deps.edn index 50ef9d5d..64e7347b 100644 --- a/deps.edn +++ b/deps.edn @@ -13,13 +13,13 @@ "depstar/src" "process/src" "deps.clj/src" "deps.clj/resources" "resources" "sci/resources"], - :deps {org.clojure/clojure {:mvn/version "1.10.2"}, + :deps {org.clojure/clojure {:mvn/version "1.10.3"}, borkdude/sci {:local/root "sci"} babashka/babasha.curl {:local/root "babashka.curl"} babashka/fs {:local/root "fs"} borkdude/graal.locking {:mvn/version "0.0.2"}, org.clojure/core.async {:mvn/version "1.3.610"}, - org.clojure/tools.cli {:mvn/version "1.0.194"}, + org.clojure/tools.cli {:mvn/version "1.0.206"}, org.clojure/data.csv {:mvn/version "1.0.0"}, cheshire/cheshire {:mvn/version "5.10.0"} org.clojure/data.xml {:mvn/version "0.2.0-alpha6"} @@ -31,13 +31,12 @@ org.postgresql/postgresql {:mvn/version "42.2.18"} org.hsqldb/hsqldb {:mvn/version "2.5.1"} datascript/datascript {:mvn/version "1.0.1"} - http-kit/http-kit {:mvn/version "2.5.1"} + http-kit/http-kit {:mvn/version "2.5.3"} babashka/clojure-lanterna {:mvn/version "0.9.8-SNAPSHOT"} - org.clojure/math.combinatorics {:mvn/version "0.1.6"} org.clojure/core.match {:mvn/version "1.0.0"} hiccup/hiccup {:mvn/version "2.0.0-alpha2"} metosin/malli {:mvn/version "0.3.0-SNAPSHOT"}} - :aliases {:main + :aliases {:babashka/dev {:main-opts ["-m" "babashka.main"]} :profile {:extra-deps @@ -72,9 +71,12 @@ honeysql/honeysql {:mvn/version "1.0.444"} minimallist/minimallist {:mvn/version "0.0.6"} circleci/bond {:mvn/version "0.4.0"} - version-clj/version-clj {:mvn/version "0.1.2"} + version-clj/version-clj {:mvn/version "2.0.1"} gaka/gaka {:mvn/version "0.3.0"} - failjure/failjure {:mvn/version "2.1.1"}}} + failjure/failjure {:mvn/version "2.1.1"}} + :classpath-overrides {org.clojure/clojure nil + org.clojure/spec.alpha nil + org.clojure/core.specs.alpha nil}} :clj-nvd {:extra-deps {clj-nvd/clj-nvd {:git/url "https://github.com/miikka/clj-nvd.git" :sha "f2ec98699e057a379baf170cb49cf7ad76874a70"}} diff --git a/doc/build.md b/doc/build.md index b79f0dc4..78a2da8f 100644 --- a/doc/build.md +++ b/doc/build.md @@ -39,7 +39,7 @@ $ git clone https://github.com/babashka/babashka --recursive To update later on: ``` shellsession -$ git submodule update --recursive +$ git submodule update --init --recursive ``` ## Build diff --git a/doc/news.md b/doc/news.md index 1786d891..1cf80ec4 100644 --- a/doc/news.md +++ b/doc/news.md @@ -5,10 +5,19 @@ you have anything to add. Also see [#babashka](https://twitter.com/hashtag/babashka?src=hashtag_click&f=live) on Twitter. +## 2021-03 + +- A `python -m http.server` [replacement in babashka](https://gist.github.com/holyjak/36c6284c047ffb7573e8a34399de27d8) +- A [PR](https://github.com/ring-clojure/ring-codec/issues/26) to make `ring-codec` compatible with babashka +- The [stuartsierra/component](https://github.com/stuartsierra/component) library [seems to work with babashka](https://github.com/babashka/babashka/issues/742) +- [pathom3](https://pathom3.wsscode.com/docs/tutorials/babashka/) works with babashka! + ## 2021-02 -- Babashka 0.2.9 released +- Babashka 0.2.9 - 0.2.12 released - [babashka.fs](https://github.com/babashka/fs): utility library for dealing with files (based on java.nio). Bundled with bb 0.2.9. +- New [Youtube channel](https://www.youtube.com/channel/UCRCl_R1ihLJt7IOgICdb9Lw) with babashka related videos +- MS SQL support for the [babashka sql pods](https://github.com/babashka/babashka-sql-pods/) - [Clojure like its PHP](https://eccentric-j.com/blog/clojure-like-its-php.html): run babashka scripts as CGI scripts - [Automating Video Edits with Clojure and ffmpeg](https://youtu.be/Tmgy57R9HZM) by Adam James @@ -19,12 +28,20 @@ Twitter. lein imitation script built on deps.edn - [failjure](https://github.com/adambard/failjure) works with babashka. - A [script](https://gist.github.com/borkdude/58f099b2694d206e6eec18daedc5077b) to solve our mono-repo problem with deps.edn at work. +- [Single-script vega-lite plotter](https://gist.github.com/vdikan/6b6063d6e1b00a3cd79bc7b3ce3853d6/) +- [Find vars with the clj-kondo pod](https://gist.github.com/borkdude/841d85d5ad04c517337166b3928697bd). Also see [video](https://youtu.be/TvBmtGS0KJE). +- [Another setup babashka Github action](https://github.com/marketplace/actions/setup-babashka) +- [AWS Lambda + babashka + minimal container image](https://gist.github.com/lukaszkorecki/a1fe27bf08f9b98e9def9da4bcb3264e) +- [football script](https://gist.github.com/mmzsource/a732950aa43d19c5a9b63bbb7f20b7eb) +- [ffclj](https://github.com/luissantos/ffclj): Clojure ffmpeg wrapper +- [clj-lineart](https://github.com/eccentric-j/clj-lineart): Generative line art from a clojure-cgi script +- [bunpack](https://github.com/robertfw/bunpack): remembers how to unpack things, so you don't have to +- A script to download deps for [all `deps.edn` aliases](https://github.com/babashka/babashka/blob/master/examples/download-aliases.clj) ## 2021-01 -Babashka [0.2.8](https://github.com/babashka/babashka/blob/master/CHANGELOG.md#v028) released. This includes new libraries: hiccup, core.match and clojure.test.check. - -On 27th of February, Michiel (a.k.a. @borkdude) will do a talk about babashka at the [2021 GraalVM workshop](https://graalworkshop.github.io/2021/). +- Babashka [0.2.8](https://github.com/babashka/babashka/blob/master/CHANGELOG.md#v028) released. This includes new libraries: hiccup, core.match and clojure.test.check. +- On 27th of February, Michiel (a.k.a. @borkdude) will do a talk about babashka at the [2021 GraalVM workshop](https://graalworkshop.github.io/2021/). - First release of the [aws pod](https://github.com/babashka/pod-babashka-aws). - A [script](https://gist.github.com/borkdude/ba372c8cee311e31020b04063d88e1be) to print API breakage warnings. @@ -38,16 +55,16 @@ On 27th of February, Michiel (a.k.a. @borkdude) will do a talk about babashka at ## 2020-12 -A new babashka talk: [Babashka and sci +- A new babashka talk: [Babashka and sci internals](https://youtu.be/pgNp4Lk3gf0). Also see [slides](https://speakerdeck.com/babashka/babashka-and-sci-internals-at-london-clojurians-december-2020) and [REPL session](https://gist.github.com/borkdude/66a4d844668e12ae1a8277af10d6cc4b). -Babashka 0.2.6 released. See [release +- Babashka 0.2.6 released. See [release notes](https://github.com/babashka/babashka/blob/master/CHANGELOG.md#v026). -Babashka 0.2.5 released. See [release +- Babashka 0.2.5 released. See [release notes](https://github.com/babashka/babashka/blob/master/CHANGELOG.md#v025). - First release of the [sqlite pod](https://github.com/babashka/pod-babashka-sqlite3) diff --git a/doc/projects.md b/doc/projects.md index 271441c2..88d9c836 100644 --- a/doc/projects.md +++ b/doc/projects.md @@ -34,6 +34,8 @@ The following libraries and projects are known to work with babashka. - [environ](#environ) - [gaka](#gaka) - [failjure](#failjure) + - [pretty](#pretty) + - [clojure-term-colors](#clojure-term-colors) - [Pods](#pods) - [Projects](#projects-1) - [babashka-test-action](#babashka-test-action) @@ -450,6 +452,42 @@ Working with failed computations in Clojure. (f/fail "foo") ``` +### [pretty](https://github.com/AvisoNovate/pretty) + +The `io.aviso.ansi` namespace provides ANSI font and background color support. + +``` clojure +(require '[babashka.deps :as deps]) + +(deps/add-deps + '{:deps {io.aviso/pretty {:mvn/version "0.1.36"}}}) + +(require '[io.aviso.ansi :as ansi]) + +(println + (str "The following text will be " + ansi/bold-red-font "bold and red " + ansi/reset-font "but this text will not.")) +``` + +### [clojure-term-colors](https://github.com/trhura/clojure-term-colors) + +Clojure ASCII color formatting for terminal output. + +``` clojure +(require '[babashka.deps :as deps]) + +(deps/add-deps + '{:deps {clojure-term-colors/clojure-term-colors {:mvn/version "0.1.0"}}}) + +(require '[clojure.term.colors :as c]) + +(println + (c/yellow "Yellow") + (c/red "Red") + "No color") +``` + ## Pods [Babashka pods](https://github.com/babashka/babashka.pods) are programs that can diff --git a/examples/README.md b/examples/README.md index 4b55b68a..9eb0c0d1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,5 +1,37 @@ # Examples +- [Examples](#examples) + - [Delete a list of files returned by a Unix command](#delete-a-list-of-files-returned-by-a-unix-command) + - [Calculate aggregate size of directory](#calculate-aggregate-size-of-directory) + - [Shuffle the lines of a file](#shuffle-the-lines-of-a-file) + - [Fetch latest Github release tag](#fetch-latest-github-release-tag) + - [Generate deps.edn entry for a gitlib](#generate-depsedn-entry-for-a-gitlib) + - [View download statistics from Clojars](#view-download-statistics-from-clojars) + - [Portable tree command](#portable-tree-command) + - [List outdated maven dependencies](#list-outdated-maven-dependencies) + - [Convert project.clj to deps.edn](#convert-projectclj-to-depsedn) + - [Print current time in California](#print-current-time-in-california) + - [Tiny http server](#tiny-http-server) + - [Print random docstring](#print-random-docstring) + - [Cryptographic hash](#cryptographic-hash) + - [Package script as Docker image](#package-script-as-docker-image) + - [Extract single file from zip](#extract-single-file-from-zip) + - [Note taking app](#note-taking-app) + - [which](#which) + - [pom.xml version](#pomxml-version) + - [Whatsapp frequencies](#whatsapp-frequencies) + - [Find unused vars](#find-unused-vars) + - [List contents of jar file](#list-contents-of-jar-file) + - [Invoke vim inside a script](#invoke-vim-inside-a-script) + - [Portal](#portal) + - [Image viewer](#image-viewer) + - [File server](#file-server) + - [Torrent viewer](#torrent-viewer) + - [cprop.clj](#cpropclj) + - [fzf](#fzf) + - [digitalocean-ping.clj](#digitalocean-pingclj) + - [download-aliases.clj](#download-aliasesclj) + Here's a gallery of useful examples. Do you have a useful example? PR welcome! ## Delete a list of files returned by a Unix command @@ -161,7 +193,9 @@ See [examples/pst.clj](https://github.com/babashka/babashka/blob/master/examples ## Tiny http server -See [examples/http_server.clj](https://github.com/babashka/babashka/blob/master/examples/http_server.clj) +This implements an http server from scratch. Note that babashka comes with `org.httpkit.server` now, so you don't need to build an http server from scratch anymore. + +See [examples/http_server_from_scratch.clj](https://github.com/babashka/babashka/blob/master/examples/http_server_from_scratch.clj) Original by [@souenzzo](https://gist.github.com/souenzzo/a959a4c5b8c0c90df76fe33bb7dfe201) @@ -330,10 +364,22 @@ Opens browser window and lets user navigate through images of all sub-directorie Example usage: ``` shell -$ examples/image_viewer.clj +$ examples/image-viewer.clj ``` -See [image_viewer.clj](image_viewer.clj). +See [image-viewer.clj](image-viewer.clj). + +## File server + +Opens browser window and lets user navigate through filesystem. + +Example usage: + +``` shell +$ examples/file-server.clj +``` + +See [file-server.clj](file-server.clj). ## Torrent viewer @@ -370,18 +416,6 @@ Example usage: $ cat src/babashka/main.clj | bb examples/fzf.clj ``` -## [rofi](rofi.clj) - -Invoke [rofi](https://github.com/davatorium/rofi), a type-to-filter menu on linux, from babashka. - -See [rofi.clj](rofi.clj) - -Example usage: - -``` shell -$ cat src/babashka/main.clj | bb examples/rofi.clj -``` - ## [digitalocean-ping.clj](digitalocean-ping.clj) The script allows to define which DigitalOcean cloud datacenter (region) has best network performance (ping latency). diff --git a/examples/file-server.clj b/examples/file-server.clj new file mode 100755 index 00000000..d8fa3f72 --- /dev/null +++ b/examples/file-server.clj @@ -0,0 +1,196 @@ +#!/usr/bin/env bb +#_" -*- mode: clojure; -*-" +;; Source: https://gist.github.com/holyjak/36c6284c047ffb7573e8a34399de27d8 + +;; Based on https://github.com/babashka/babashka/blob/master/examples/image_viewer.clj +(ns file-server + (:require [babashka.fs :as fs] + [clojure.java.browse :as browse] + [clojure.string :as str] + [clojure.tools.cli :refer [parse-opts]] + [hiccup2.core :as html] + [org.httpkit.server :as server]) + (:import [java.net URLDecoder URLEncoder])) + +(def cli-options [["-p" "--port PORT" "Port for HTTP server" :default 8090 :parse-fn #(Integer/parseInt %)] + ["-d" "--dir DIR" "Directory to serve files from" :default "."] + ["-h" "--help" "Print usage info"]]) + +(def parsed-args (parse-opts *command-line-args* cli-options)) +(def opts (:options parsed-args)) + +(cond + (:help opts) + (do (println "Start a http server for static files in the given dir. Usage:\n" (:summary parsed-args)) + (System/exit 0)) + + (:errors parsed-args) + (do (println "Invalid arguments:\n" (str/join "\n" (:errors parsed-args))) + (System/exit 1)) + + :else + :continue) + + +(def port (:port opts)) +(def dir (fs/path (:dir opts))) + +(assert (fs/directory? dir) (str "The given dir `" dir "` is not a directory.")) + +;; A simple mime type utility from https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/util/mime_type.clj +(def ^{:doc "A map of file extensions to mime-types."} + default-mime-types + {"7z" "application/x-7z-compressed" + "aac" "audio/aac" + "ai" "application/postscript" + "appcache" "text/cache-manifest" + "asc" "text/plain" + "atom" "application/atom+xml" + "avi" "video/x-msvideo" + "bin" "application/octet-stream" + "bmp" "image/bmp" + "bz2" "application/x-bzip" + "class" "application/octet-stream" + "cer" "application/pkix-cert" + "crl" "application/pkix-crl" + "crt" "application/x-x509-ca-cert" + "css" "text/css" + "csv" "text/csv" + "deb" "application/x-deb" + "dart" "application/dart" + "dll" "application/octet-stream" + "dmg" "application/octet-stream" + "dms" "application/octet-stream" + "doc" "application/msword" + "dvi" "application/x-dvi" + "edn" "application/edn" + "eot" "application/vnd.ms-fontobject" + "eps" "application/postscript" + "etx" "text/x-setext" + "exe" "application/octet-stream" + "flv" "video/x-flv" + "flac" "audio/flac" + "gif" "image/gif" + "gz" "application/gzip" + "htm" "text/html" + "html" "text/html" + "ico" "image/x-icon" + "iso" "application/x-iso9660-image" + "jar" "application/java-archive" + "jpe" "image/jpeg" + "jpeg" "image/jpeg" + "jpg" "image/jpeg" + "js" "text/javascript" + "json" "application/json" + "lha" "application/octet-stream" + "lzh" "application/octet-stream" + "mov" "video/quicktime" + "m3u8" "application/x-mpegurl" + "m4v" "video/mp4" + "mjs" "text/javascript" + "mp3" "audio/mpeg" + "mp4" "video/mp4" + "mpd" "application/dash+xml" + "mpe" "video/mpeg" + "mpeg" "video/mpeg" + "mpg" "video/mpeg" + "oga" "audio/ogg" + "ogg" "audio/ogg" + "ogv" "video/ogg" + "pbm" "image/x-portable-bitmap" + "pdf" "application/pdf" + "pgm" "image/x-portable-graymap" + "png" "image/png" + "pnm" "image/x-portable-anymap" + "ppm" "image/x-portable-pixmap" + "ppt" "application/vnd.ms-powerpoint" + "ps" "application/postscript" + "qt" "video/quicktime" + "rar" "application/x-rar-compressed" + "ras" "image/x-cmu-raster" + "rb" "text/plain" + "rd" "text/plain" + "rss" "application/rss+xml" + "rtf" "application/rtf" + "sgm" "text/sgml" + "sgml" "text/sgml" + "svg" "image/svg+xml" + "swf" "application/x-shockwave-flash" + "tar" "application/x-tar" + "tif" "image/tiff" + "tiff" "image/tiff" + "ts" "video/mp2t" + "ttf" "font/ttf" + "txt" "text/plain" + "webm" "video/webm" + "wmv" "video/x-ms-wmv" + "woff" "font/woff" + "woff2" "font/woff2" + "xbm" "image/x-xbitmap" + "xls" "application/vnd.ms-excel" + "xml" "text/xml" + "xpm" "image/x-xpixmap" + "xwd" "image/x-xwindowdump" + "zip" "application/zip"}) + +;; https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/util/mime_type.clj +(defn- filename-ext + "Returns the file extension of a filename or filepath." + [filename] + (if-let [ext (second (re-find #"\.([^./\\]+)$" filename))] + (str/lower-case ext))) + +;; https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/util/mime_type.clj +(defn ext-mime-type + "Get the mimetype from the filename extension. Takes an optional map of + extensions to mimetypes that overrides values in the default-mime-types map." + ([filename] + (ext-mime-type filename {})) + ([filename mime-types] + (let [mime-types (merge default-mime-types mime-types)] + (mime-types (filename-ext filename))))) + +(defn index [f] + (let [files (map #(str (.relativize dir %)) + (fs/list-dir f))] + {:body (-> [:html + [:head + [:meta {:charset "UTF-8"}] + [:title (str "Index of `" f "`")]] + [:body + [:h1 "Index of " [:code (str f)]] + [:ul + (for [child files] + [:li [:a {:href (URLEncoder/encode (str child))} child + (when (fs/directory? (fs/path dir child)) "/")]])] + [:hr] + [:footer {:style {"text-aling" "center"}} "Served by http-server.clj"]]] + html/html + str)})) + +(defn body [path] + {:headers {"Content-Type" (ext-mime-type (fs/file-name path))} + :body (fs/file path)}) + +(server/run-server + (fn [{:keys [:uri]}] + (let [f (fs/path dir (str/replace-first (URLDecoder/decode uri) #"^/" "")) + index-file (fs/path f "index.html")] + (cond + (and (fs/directory? f) (fs/readable? index-file)) + (body index-file) + + (fs/directory? f) + (index f) + + (fs/readable? f) + (body f) + + :else + {:status 404 :body (str "Not found `" f "` in " dir)}))) + {:port port}) + +(println "Starting http server at " port "for" (str dir)) +(browse/browse-url (format "http://localhost:%s/" port)) + +@(promise) diff --git a/examples/http_server.clj b/examples/http_server_from_scratch.clj similarity index 92% rename from examples/http_server.clj rename to examples/http_server_from_scratch.clj index 213f6d5e..0109f59d 100755 --- a/examples/http_server.clj +++ b/examples/http_server_from_scratch.clj @@ -1,10 +1,13 @@ #!/usr/bin/env bb -;; This example creates a file serving web server +;; This example creates a file serving web server from scratch. ;; It accepts a single connection from a browser and serves content to the connected browser ;; after the connection times out, this script will serve no more. ;; Also see notes.clj for another web app example. +;; Note that babashka comes with org.httpkit.server now, so you don't need to +;; build an http server from scratch anymore. We leave this script here for educational purposes. + (import (java.net ServerSocket)) (require '[clojure.java.io :as io] '[clojure.string :as string]) diff --git a/examples/image_viewer.clj b/examples/image-viewer.clj similarity index 100% rename from examples/image_viewer.clj rename to examples/image-viewer.clj diff --git a/examples/notes.clj b/examples/notes.clj index c23769ef..c108e2b0 100755 --- a/examples/notes.clj +++ b/examples/notes.clj @@ -1,8 +1,9 @@ #!/usr/bin/env bb -(import (java.net ServerSocket)) (require '[clojure.java.io :as io] - '[clojure.string :as str]) + '[clojure.pprint :refer [pprint]] + '[clojure.string :as str] + '[org.httpkit.server :as server]) (def debug? true) (def user "admin") @@ -35,43 +36,23 @@ (str/join " " (map html v)) :else (str v))) -(defn write-response [out session-id status headers content] - (let [cookie-header (str "Set-Cookie: notes-id=" session-id) - headers (str/join "\r\n" (conj headers cookie-header)) - response (str "HTTP/1.1 " status "\r\n" - (str headers "\r\n") - "Content-Length: " (if content (count content) - 0) - "\r\n\r\n" - (when content - (str content)))] - (when debug? (println response)) - (binding [*out* out] - (print response) - (flush)))) - ;; the home page -(defn home-response [out session-id] - (let [body (str - "\n" - (html - [:html - [:head - [:title "Notes"]] - [:body - [:h1 "Notes"] - [:pre (when (.exists notes-file) - (slurp notes-file))] - [:form {:action "/" :method "post"} - [:input {:type "text" :name "note"}] - [:input {:type "submit" :value "Submit"}]]]]))] - (write-response out session-id "200 OK" nil body))) - -(defn basic-auth-response [out session-id] - (write-response out session-id - "401 Unauthorized" - ["WWW-Authenticate: Basic realm=\"notes\""] - nil)) +(defn home-response [session-id] + {:status 200 + :headers {"Set-Cookie" (str "notes-id=" session-id)} + :body (str + "\n" + (html + [:html + [:head + [:title "Notes"]] + [:body + [:h1 "Notes"] + [:pre (when (.exists notes-file) + (slurp notes-file))] + [:form {:action "/" :method "post"} + [:input {:type "text" :name "note"}] + [:input {:type "submit" :value "Submit"}]]]]))}) (def known-sessions (atom #{})) @@ -81,67 +62,54 @@ (swap! known-sessions conj uuid) uuid)) -(defn get-session-id [headers] - (if-let [cookie-header (first (filter #(str/starts-with? % "Cookie: ") headers))] - (let [parts (str/split cookie-header #"; ")] - (if-let [notes-id (first (filter #(str/starts-with? % "notes-id") parts))] - (str/replace notes-id "notes-id=" "") - (new-session!))) - (new-session!))) - -(defn basic-auth-header [headers] - (some #(str/starts-with? % "Basic-Auth: ") headers)) - (def authenticated-sessions (atom #{})) (defn authenticate! [session-id headers] (or (contains? @authenticated-sessions session-id) - (when (some #(= % (str "Authorization: Basic " base64)) headers) + (when (= (headers "authorization") (str "Basic " base64)) (swap! authenticated-sessions conj session-id) true))) +(defn parse-session-id [cookie] + (when cookie + (when-let [notes-id (first (filter #(str/starts-with? % "notes-id") + (str/split cookie #"; ")))] + (str/replace notes-id "notes-id=" "")))) + +(defn basic-auth-response [session-id] + {:status 401 + :headers {"WWW-Authenticate" "Basic realm=\"notes\"" + "Set-Cookie" (str "notes-id=" session-id)}}) + ;; run the server -(with-open [server-socket (let [s (new ServerSocket 8080)] - (println "Server started on port 8080.") - s)] - (loop [] - (let [client-socket (.accept server-socket)] - (future - (with-open [conn client-socket] - (try - (let [out (io/writer (.getOutputStream conn)) - is (.getInputStream conn) - in (io/reader is) - [_req & headers :as response] - (loop [headers []] - (let [line (.readLine in)] - (if (str/blank? line) - headers - (recur (conj headers line))))) - session-id (get-session-id headers) - form-data (let [sb (StringBuilder.)] - (loop [] - (when (.ready in) - (.append sb (char (.read in))) - (recur))) - (-> (str sb) - (java.net.URLDecoder/decode))) - _ (when debug? (println (str/join "\n" response))) - _ (when-not (str/blank? form-data) - (when debug? (println form-data)) - (let [note (str/replace form-data "note=" "")] - (write-note! note))) - _ (when debug? (println))] - (cond - ;; if we didn't see this session before, we want the user to re-authenticate - (not (contains? @known-sessions session-id)) - (let [uuid (new-session!)] - (basic-auth-response out uuid)) - (not (authenticate! session-id headers)) - (basic-auth-response out session-id) - :else (home-response out session-id))) - (catch Throwable t - (binding [*err* *out*] - (println t))))))) - (recur))) +(defn handler [req] + (when debug? + (println "Request:") + (pprint req)) + (let [body (some-> req :body slurp java.net.URLDecoder/decode) + session-id (parse-session-id (get-in req [:headers "cookie"])) + _ (when (and debug? body) + (println "Request body:" body)) + response (cond + ;; if we didn't see this session before, we want the user to + ;; re-authenticate + (not (contains? @known-sessions session-id)) + (basic-auth-response (new-session!)) + + (not (authenticate! session-id (:headers req))) + (basic-auth-response session-id) + + :else (do (when-not (str/blank? body) + (let [note (str/replace body "note=" "")] + (write-note! note))) + (home-response session-id)))] + (when debug? + (println "Response:") + (pprint (dissoc response :body)) + (println)) + response)) + +(server/run-server handler {:port 8080}) +(println "Server started on port 8080.") +@(promise) ;; wait until SIGINT diff --git a/examples/rofi.clj b/examples/rofi.clj deleted file mode 100644 index da7045e6..00000000 --- a/examples/rofi.clj +++ /dev/null @@ -1,12 +0,0 @@ -(require '[babashka.process :as p]) - -(defn rofi [s] - (let [proc (p/process - ["rofi" "-i" "-dmenu" "-mesg" "Select" "-sync" "-p" "*"] - {:in s :err :inherit - :out :string})] - (:out @proc))) - -(rofi (slurp *in*)) - -;; `echo "hi\nthere\nclj" | bb examples/rofi.clj` diff --git a/fs b/fs index caa1cb38..19c03977 160000 --- a/fs +++ b/fs @@ -1 +1 @@ -Subproject commit caa1cb383329c353114ae24954c52b9d6fa038ea +Subproject commit 19c03977282bd63a3ecfd3e6a261a433aa9e8745 diff --git a/install b/install index 155f4182..89bd1a5e 100755 --- a/install +++ b/install @@ -64,25 +64,41 @@ case "$(uname -s)" in Darwin*) platform=macos;; esac -download_url="https://github.com/babashka/babashka/releases/download/v$version/babashka-$version-$platform-amd64.zip" +case "$(uname -m)" in + aarch64) arch=aarch64;; +esac +arch=${arch:-amd64} + +# Ugly ugly conversion of version to a comparable number +IFS='.' read -ra VER <<< "$version" +vernum=$(printf "%03d%03d%03d" "${VER[0]}" "${VER[1]}" "${VER[2]}") + +if [[ $vernum -le 000002013 ]]; then + ext="zip" + util="$(which unzip) -qqo" +else + ext="tar.gz" + util="$(which tar) -zxf" +fi + +download_url="https://github.com/babashka/babashka/releases/download/v$version/babashka-$version-$platform-$arch."$ext mkdir -p "$download_dir" cd "$download_dir" echo -e "Downloading $download_url to $download_dir" -rm -rf "babashka-$version-$platform-amd64.zip" +rm -rf "babashka-$version-$platform-$arch."$ext rm -rf "bb" -curl -o "babashka-$version-$platform-amd64.zip" -sL "https://github.com/babashka/babashka/releases/download/v$version/babashka-$version-$platform-amd64.zip" -unzip -qqo "babashka-$version-$platform-amd64.zip" -rm "babashka-$version-$platform-amd64.zip" +curl -o "babashka-$version-$platform-$arch."$ext -sL $download_url +$util "babashka-$version-$platform-$arch."$ext +rm "babashka-$version-$platform-$arch."$ext if [ "$download_dir" != "$install_dir" ] then mkdir -p "$install_dir" - cd "$install_dir" - if [ -f bb ]; then + if [ -f "$install_dir/bb" ]; then echo "Moving $install_dir/bb to $install_dir/bb.old" fi - mv -f "$download_dir/bb" "$PWD/bb" + mv -f "$download_dir/bb" "$install_dir/bb" fi echo "Successfully installed bb in $install_dir" diff --git a/pods b/pods index 0bffce35..82aa3627 160000 --- a/pods +++ b/pods @@ -1 +1 @@ -Subproject commit 0bffce3573d0361cf2592d65cefc8a4048454ff9 +Subproject commit 82aa3627106181a0ad58c289cd45129603e4fa24 diff --git a/process b/process index f5b531f7..692195db 160000 --- a/process +++ b/process @@ -1 +1 @@ -Subproject commit f5b531f706fd2b3cfba93c15411d3308e25b5917 +Subproject commit 692195db27fe411b65e24dbacec75c9f4721a486 diff --git a/project.clj b/project.clj index c67f227a..6ecffac5 100644 --- a/project.clj +++ b/project.clj @@ -17,12 +17,11 @@ :dependencies [[org.clojure/clojure "1.10.2"] [borkdude/edamame "0.0.11-alpha.29"] [borkdude/graal.locking "0.0.2"] - [org.clojure/tools.cli "1.0.194"] + [org.clojure/tools.cli "1.0.206"] [cheshire "5.10.0"] [nrepl/bencode "1.1.0"] [borkdude/sci.impl.reflector "0.0.1"] - [org.clojure/test.check "1.1.0"] - [org.clojure/math.combinatorics "0.1.6"]] + [org.clojure/test.check "1.1.0"]] :profiles {:feature/xml {:source-paths ["feature-xml"] :dependencies [[org.clojure/data.xml "0.2.0-alpha6"]]} :feature/yaml {:source-paths ["feature-yaml"] @@ -42,9 +41,9 @@ :feature/datascript {:source-paths ["feature-datascript"] :dependencies [[datascript "1.0.1"]]} :feature/httpkit-client {:source-paths ["feature-httpkit-client"] - :dependencies [[http-kit "2.5.1"]]} + :dependencies [[http-kit "2.5.3"]]} :feature/httpkit-server {:source-paths ["feature-httpkit-server"] - :dependencies [[http-kit "2.5.1"]]} + :dependencies [[http-kit "2.5.3"]]} :feature/lanterna {:source-paths ["feature-lanterna"] :dependencies [[babashka/clojure-lanterna "0.9.8-SNAPSHOT"]]} :feature/core-match {:source-paths ["feature-core-match"] @@ -70,8 +69,7 @@ :feature/hiccup :feature/test-check :feature/spec-alpha - {:dependencies [[clj-commons/conch "0.9.2"] - [com.clojure-goes-fast/clj-async-profiler "0.4.1"] + {:dependencies [[com.clojure-goes-fast/clj-async-profiler "0.4.1"] [com.opentable.components/otj-pg-embedded "0.13.3"]]}] :uberjar {:global-vars {*assert* false} :jvm-opts ["-Dclojure.compiler.direct-linking=true" diff --git a/resources/BABASHKA_RELEASED_VERSION b/resources/BABASHKA_RELEASED_VERSION index d156ab46..a2268e2d 100644 --- a/resources/BABASHKA_RELEASED_VERSION +++ b/resources/BABASHKA_RELEASED_VERSION @@ -1 +1 @@ -0.2.10 \ No newline at end of file +0.3.1 \ No newline at end of file diff --git a/resources/BABASHKA_VERSION b/resources/BABASHKA_VERSION index 1dc792c4..41d76f43 100644 --- a/resources/BABASHKA_VERSION +++ b/resources/BABASHKA_VERSION @@ -1 +1 @@ -0.2.11-SNAPSHOT \ No newline at end of file +0.3.2-SNAPSHOT \ No newline at end of file diff --git a/sci b/sci index 043f5e60..106919aa 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit 043f5e60d674f5aeee0866e427cef58812ad5547 +Subproject commit 106919aad41062055828df832acb863080e4b5bd diff --git a/script/compile b/script/compile index 0d66c005..00de9aef 100755 --- a/script/compile +++ b/script/compile @@ -60,6 +60,7 @@ args=( "-jar" "$BABASHKA_JAR" "--no-server" "--report-unsupported-elements-at-runtime" "--initialize-at-run-time=org.postgresql.sspi.SSPIClient" + "--initialize-at-run-time=org.httpkit.client.ClientSslEngineFactory\$SSLHolder" "--native-image-info" "--verbose" "-H:ServiceLoaderFeatureExcludeServices=javax.sound.sampled.spi.AudioFileReader" diff --git a/script/compile.bat b/script/compile.bat index f0f13db2..7e62fa4e 100644 --- a/script/compile.bat +++ b/script/compile.bat @@ -34,6 +34,7 @@ call %GRAALVM_HOME%\bin\native-image.cmd ^ "-H:ReflectionConfigurationFiles=reflection.json" ^ "--initialize-at-build-time" ^ "--initialize-at-run-time=org.postgresql.sspi.SSPIClient" ^ + "--initialize-at-run-time=org.httpkit.client.ClientSslEngineFactory\$SSLHolder" ^ "-H:EnableURLProtocols=http,https,jar" ^ "--enable-all-security-services" ^ "-H:+JNI" ^ diff --git a/script/test b/script/test index 5ee24559..2d6317b9 100755 --- a/script/test +++ b/script/test @@ -14,7 +14,7 @@ unset BABASHKA_CLASSPATH unset BABASHKA_PRELOADS_TEST echo "running tests part 1" -lein test "$@" +lein "do" clean, test "$@" export BABASHKA_PRELOADS='(defn __bb__foo [] "foo") (defn __bb__bar [] "bar")' export BABASHKA_PRELOADS_TEST=true diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index 0ec769f1..9907d39d 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -100,6 +100,8 @@ java.io.OutputStream java.io.FileReader java.io.InputStreamReader + java.io.OutputStreamWriter + java.io.PrintStream java.io.PushbackInputStream java.io.Reader java.io.SequenceInputStream @@ -118,6 +120,7 @@ java.lang.Exception java.lang.Float java.lang.IllegalArgumentException + java.lang.IndexOutOfBoundsException java.lang.Integer java.lang.Iterable java.lang.Long @@ -216,7 +219,7 @@ java.util.jar.JarFile$JarFileEntry java.util.stream.Stream java.util.Random - ;; java.util.regex.Matcher + java.util.regex.Matcher java.util.regex.Pattern java.util.Base64 java.util.Base64$Decoder @@ -249,35 +252,58 @@ :methods [borkdude.graal.LockFix] ;; support for locking :fields [clojure.lang.PersistentQueue] - :instance-checks [clojure.lang.Cons + :instance-checks [clojure.lang.APersistentMap ;; for proxy + clojure.lang.AMapEntry ;; for proxy + clojure.lang.Associative + clojure.lang.Atom + clojure.lang.Cons + clojure.lang.Counted clojure.lang.Cycle clojure.lang.IObj clojure.lang.Fn ;; to distinguish fns from maps, etc. clojure.lang.IFn clojure.lang.IPending - ;; clojure.lang.IDeref - ;; clojure.lang.IAtom + ;; clojure.lang.IDeref ;; implemented as protocol in sci + ;; clojure.lang.IAtom ;; implemented as protocol in sci clojure.lang.IEditableCollection clojure.lang.IMapEntry + clojure.lang.IMeta clojure.lang.ILookup clojure.lang.IPersistentCollection clojure.lang.IPersistentMap clojure.lang.IPersistentSet - ;;clojure.lang.PersistentHashSet ;; temp for meander + clojure.lang.IPersistentStack clojure.lang.IPersistentVector clojure.lang.IRecord + clojure.lang.IReduce + clojure.lang.IReduceInit + clojure.lang.IKVReduce clojure.lang.IRef clojure.lang.ISeq + clojure.lang.Indexed clojure.lang.Iterate clojure.lang.LazySeq clojure.lang.Named clojure.lang.Keyword + clojure.lang.PersistentArrayMap + clojure.lang.PersistentHashMap + clojure.lang.PersistentHashSet + clojure.lang.PersistentList + clojure.lang.PersistentQueue + clojure.lang.PersistentStructMap + clojure.lang.PersistentTreeMap + clojure.lang.PersistentTreeSet + clojure.lang.PersistentVector clojure.lang.Ratio clojure.lang.Repeat + clojure.lang.Reversible clojure.lang.Symbol clojure.lang.Sequential clojure.lang.Seqable - java.util.List] + clojure.lang.Volatile + java.util.List + java.util.Iterator + java.util.Map$Entry] :custom ~custom-map}) (defmacro gen-class-map [] diff --git a/src/babashka/impl/classpath.clj b/src/babashka/impl/classpath.clj index 14a0339c..8c4cb1fe 100644 --- a/src/babashka/impl/classpath.clj +++ b/src/babashka/impl/classpath.clj @@ -28,17 +28,18 @@ resource-paths))) (defn path-from-jar - [^java.io.File jar-file resource-paths {:keys [:url?]}] - (with-open [jar (JarFile. jar-file)] - (some (fn [path] - (when-let [entry (.getEntry jar path)] - (if url? - ;; manual conversion, faster than going through .toURI - (java.net.URL. "jar" nil - (str "file:" (.getAbsolutePath jar-file) "!/" path)) - {:file path - :source (slurp (.getInputStream jar entry))}))) - resource-paths))) + [^java.io.File jar-file resource-paths opts] + (let [url? (:url? opts)] + (with-open [jar (JarFile. jar-file)] + (some (fn [path] + (when-let [entry (.getEntry jar path)] + (if url? + ;; manual conversion, faster than going through .toURI + (java.net.URL. "jar" nil + (str "file:" (.getAbsolutePath jar-file) "!/" path)) + {:file path + :source (slurp (.getInputStream jar entry))}))) + resource-paths)))) (deftype JarFileResolver [jar-file] IResourceResolver @@ -57,8 +58,10 @@ (getResources [_ resource-paths opts] (keep #(getResource % resource-paths opts) entries))) +(def path-sep (System/getProperty "path.separator")) + (defn loader [^String classpath] - (let [parts (.split classpath (System/getProperty "path.separator")) + (let [parts (.split classpath path-sep) entries (map part->entry parts)] (Loader. entries))) @@ -88,7 +91,7 @@ (fn [{:keys [:cp]}] (let [new-cp (if-not cp extra-classpath - (str cp (System/getProperty "path.separator") extra-classpath))] + (str cp path-sep extra-classpath))] {:loader (loader new-cp) :cp new-cp}))) nil) @@ -96,7 +99,7 @@ (defn split-classpath "Returns the classpath as a seq of strings, split by the platform specific path separator." - ([^String cp] (vec (.split cp (System/getProperty "path.separator"))))) + ([^String cp] (vec (.split cp path-sep)))) (defn get-classpath "Returns the current classpath as set by --classpath, BABASHKA_CLASSPATH and add-classpath." diff --git a/src/babashka/impl/clojure/core.clj b/src/babashka/impl/clojure/core.clj index 0ef0c81b..a0f3e946 100644 --- a/src/babashka/impl/clojure/core.clj +++ b/src/babashka/impl/clojure/core.clj @@ -20,6 +20,8 @@ ret#)) (def data-readers (sci/new-dynamic-var '*data-readers* nil)) +(def command-line-args (sci/new-dynamic-var '*command-line-args* nil)) +(def warn-on-reflection (sci/new-dynamic-var '*warn-on-reflection* false)) (defn read+string "Added for compatibility. Must be used with @@ -59,4 +61,6 @@ 'default-data-readers default-data-readers 'xml-seq (copy-core-var xml-seq) 'read+string (fn [& args] - (apply read+string @common/ctx args))}) + (apply read+string @common/ctx args)) + '*command-line-args* command-line-args + '*warn-on-reflection* warn-on-reflection}) diff --git a/src/babashka/impl/deps.clj b/src/babashka/impl/deps.clj index ad642f3e..08d6ff20 100644 --- a/src/babashka/impl/deps.clj +++ b/src/babashka/impl/deps.clj @@ -57,11 +57,20 @@ then used to resolve dependencies in babashka." ([deps-map] (add-deps deps-map nil)) ([deps-map {:keys [:aliases]}] - (let [args ["-Spath" "-Sdeps" (str deps-map)] - args (cond-> args - aliases (conj (str "-A:" (str/join ":" aliases)))) - cp (with-out-str (apply deps/-main args))] - (cp/add-classpath cp)))) + (when-let [paths (:paths deps-map)] + (cp/add-classpath (str/join cp/path-sep paths))) + (when-let [deps-map (not-empty (dissoc deps-map :paths :tasks))] + (let [deps-map (assoc-in deps-map [:aliases :org.babashka/defaults] + '{:replace-paths [] ;; babashka sets paths manually + :classpath-overrides {org.clojure/clojure "" + org.clojure/spec.alpha "" + org.clojure/core.specs.alpha ""}}) + args ["-Spath" "-Sdeps" (str deps-map)] + args (conj args (str "-A:" (str/join ":" (cons ":org.babashka/defaults" aliases)))) + cp (with-out-str (apply deps/-main args)) + cp (str/trim cp) + cp (str/replace cp (re-pattern (str cp/path-sep "+$")) "")] + (cp/add-classpath cp))))) (defn clojure "Starts clojure similar to CLI. Use `rlwrap bb` for `clj`-like invocation. diff --git a/src/babashka/impl/fs.clj b/src/babashka/impl/fs.clj index 034738e6..996a55a6 100644 --- a/src/babashka/impl/fs.clj +++ b/src/babashka/impl/fs.clj @@ -25,6 +25,7 @@ 'create-dir (sci/copy-var fs/create-dir fns) 'create-dirs (sci/copy-var fs/create-dirs fns) 'create-file (sci/copy-var fs/create-file fns) + 'create-link (sci/copy-var fs/create-link fns) 'create-sym-link (sci/copy-var fs/create-sym-link fns) 'create-temp-dir (sci/copy-var fs/create-temp-dir fns) 'creation-time (sci/copy-var fs/creation-time fns) @@ -37,6 +38,7 @@ 'exec-paths (sci/copy-var fs/exec-paths fns) 'executable? (sci/copy-var fs/executable? fns) 'exists? (sci/copy-var fs/exists? fns) + 'extension (sci/copy-var fs/extension fns) 'file (sci/copy-var fs/file fns) 'file-name (sci/copy-var fs/file-name fns) 'file-separator (sci/copy-var fs/file-separator fns) @@ -62,6 +64,7 @@ 'read-attributes (sci/copy-var fs/read-attributes fns) 'readable? (sci/copy-var fs/readable? fns) 'real-path (sci/copy-var fs/real-path fns) + 'regular-file? (sci/copy-var fs/regular-file? fns) 'relative? (sci/copy-var fs/relative? fns) 'relativize (sci/copy-var fs/relativize fns) 'same-file? (sci/copy-var fs/same-file? fns) @@ -70,6 +73,7 @@ 'set-last-modified-time (sci/copy-var fs/set-last-modified-time fns) 'set-posix-file-permissions (sci/copy-var fs/set-posix-file-permissions fns) 'size (sci/copy-var fs/size fns) + 'split-ext (sci/copy-var fs/split-ext fns) 'split-paths (sci/copy-var fs/split-paths fns) 'starts-with? (sci/copy-var fs/starts-with? fns) 'str->posix (sci/copy-var fs/str->posix fns) diff --git a/src/babashka/impl/process.clj b/src/babashka/impl/process.clj index c31b172a..28aef471 100644 --- a/src/babashka/impl/process.clj +++ b/src/babashka/impl/process.clj @@ -22,6 +22,8 @@ 'start (copy-var process/start tns) 'pipeline (copy-var process/pipeline tns) '$ (copy-var process/$ tns) + 'sh (copy-var process/sh tns) + 'tokenize (copy-var process/tokenize tns) '*defaults* defaults 'destroy (copy-var process/destroy tns) 'destroy-tree (copy-var process/destroy-tree tns)}) diff --git a/src/babashka/impl/proxy.clj b/src/babashka/impl/proxy.clj new file mode 100644 index 00000000..4ebfba60 --- /dev/null +++ b/src/babashka/impl/proxy.clj @@ -0,0 +1,60 @@ +(ns babashka.impl.proxy + {:no-doc true} + (:require [sci.impl.types])) + +(set! *warn-on-reflection* false) + +(defn method-or-bust [methods k] + (or (get methods k) + (throw (UnsupportedOperationException. (str "Method not implemented: " k))))) + +(defn class-name [^Class clazz] + (.getName clazz)) + +(defn proxy-fn [{:keys [class interfaces protocols methods]}] + (let [interface-names (set (map class-name interfaces))] + (case [(class-name class) interface-names] + ;; This combination is used by pathom3 + ["clojure.lang.APersistentMap" #{"clojure.lang.IMeta" "clojure.lang.IObj"}] + (proxy [clojure.lang.APersistentMap clojure.lang.IMeta clojure.lang.IObj sci.impl.types.IReified] [] + (getInterfaces [] + interfaces) + (getMethods [] + methods) + (getProtocols [] + protocols) + (iterator [] ((method-or-bust methods 'iterator) this)) + (containsKey [k] ((method-or-bust methods 'containsKey) this k)) + (entryAt [k] ((method-or-bust methods 'entryAt) this k)) + (valAt + ([k] + ((method-or-bust methods 'valAt) this k)) + ([k default] ((method-or-bust methods 'valAt) this k default))) + (cons [v] + (if-let [m (get methods 'cons)] + (m this v) + (proxy-super cons v))) + (count [] ((method-or-bust methods 'count) this)) + (assoc [k v] ((method-or-bust methods 'assoc) this k v)) + (without [k] ((method-or-bust methods 'without) this k)) + (seq [] ((method-or-bust methods 'seq) this)) + + (equiv [other] + (if-let [m (get methods 'equiv)] + (m this other) + (proxy-super equiv other))) + (empty [] ((method-or-bust methods 'empty) this)) + + (meta [] ((method-or-bust methods 'meta) this)) + (withMeta [meta] ((method-or-bust methods 'withMeta) this meta)) + + (toString [] + (if-let [m (get methods 'toString)] + (m this) + (proxy-super toString)))) + ["clojure.lang.AMapEntry" #{}] + (proxy [clojure.lang.AMapEntry] [] + (key [] ((method-or-bust methods 'key) this)) + (val [] ((method-or-bust methods 'val) this)) + (getKey [] ((method-or-bust methods 'getKey) this)) + (getValue [] ((method-or-bust methods 'getValue) this)))))) diff --git a/src/babashka/impl/reify.clj b/src/babashka/impl/reify.clj index 11b9cb1f..fcf18c9b 100644 --- a/src/babashka/impl/reify.clj +++ b/src/babashka/impl/reify.clj @@ -1,40 +1,153 @@ (ns babashka.impl.reify {:no-doc true} - (:require [clojure.math.combinatorics :as combo])) + (:require [sci.impl.types])) (set! *warn-on-reflection* false) +;; Notes + +;; We abandoned the 'one reify object that implements all interfaces' approach +;; due to false positives. E.g. when you would print a reified object, you would +;; get: 'Not implemented: seq', because print-method thought this object was +;; seqable, while in fact, it wasn't. + +(defn method-or-bust [methods k] + (or (get methods k) + (throw (UnsupportedOperationException. "Method not implemented: " k)))) + (defmacro gen-reify-combos "Generates pre-compiled reify combinations" [methods] - (let [subsets (rest (combo/subsets (seq methods)))] - (reduce (fn [opts classes] - (assoc opts - (set (map (fn [[class _]] - (list 'quote class)) - classes)) - (list 'fn ['methods] - (list* 'reify - (mapcat - (fn [[clazz methods]] - (cons clazz - (map - (fn [[meth args]] - (list meth args - (list* - (list 'get-in 'methods - [(list 'quote clazz) (list 'quote meth)]) - args))) - methods))) - classes))))) - {} - subsets))) + (let [prelude '(reify + sci.impl.types.IReified + (getInterfaces [this] + interfaces) + (getMethods [this] + methods) + (getProtocols [this] + protocols))] + (list 'fn [{:keys '[interfaces methods protocols]}] + `(cond ~'(empty? interfaces) ~prelude ~'(> (count interfaces) + 1) (throw (new Exception "Babashka currently does not support reifying more than one interface.")) + :else + (case (.getName ~(with-meta '(first interfaces) + {:tag 'Class})) + ~@(mapcat + (fn [[clazz methods]] + (list + (str clazz) + (concat prelude + (cons clazz + (mapcat + (fn [[meth arities]] + (map + (fn [arity] + (list meth arity + (list* + (list 'or (list 'get 'methods (list 'quote meth)) + `(throw (new Exception (str "Not implemented: " + ~(str meth))))) + arity))) + arities)) + methods))))) + methods)))))) + +;; (require 'clojure.pprint) +;; (clojure.pprint/pprint +;; (macroexpand '(gen-reify-combos {java.nio.file.FileVisitor +;; {preVisitDirectory [[this p attrs]] +;; postVisitDirectory [[this p attrs]] +;; visitFile [[this p attrs]]}}))) #_:clj-kondo/ignore -(def reify-opts +(def reify-fn (gen-reify-combos - {java.nio.file.FileVisitor {preVisitDirectory [this p attrs] - postVisitDirectory [this p attrs] - visitFile [this p attrs]} - java.io.FileFilter {accept [this f]} - java.io.FilenameFilter {accept [this f s]}})) + {java.lang.Object + {toString [[this]]} + java.nio.file.FileVisitor + {preVisitDirectory [[this p attrs]] + postVisitDirectory [[this p attrs]] + visitFile [[this p attrs]]} + + java.io.FileFilter + {accept [[this f]]} + + java.io.FilenameFilter + {accept [[this f s]]} + + clojure.lang.Associative + {containsKey [[this k]] + entryAt [[this k]] + assoc [[this k v]]} + + clojure.lang.ILookup + {valAt [[this k] [this k default]]} + + java.util.Map$Entry + {getKey [[this]] + getValue [[this]]} + + clojure.lang.IFn + {applyTo [[this arglist]] + invoke [[this] + [this a1] + [this a1 a2] + [this a1 a2 a3] + [this a1 a2 a3 a4] + [this a1 a2 a3 a4 a5] + [this a1 a2 a3 a4 a5 a6] + [this a1 a2 a3 a4 a5 a6 a7] + [this a1 a2 a3 a4 a5 a6 a7 a8] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20] + [this a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16 a17 a18 a19 a20 varargs]]} + + clojure.lang.IPersistentCollection + {count [[this]] + cons [[this x]] + empty [[this]] + equiv [[this x]]} + + clojure.lang.IReduce + {reduce [[this f]]} + + clojure.lang.IReduceInit + {reduce [[this f initial]]} + + clojure.lang.IKVReduce + {kvreduce [[this f initial]]} + + clojure.lang.Indexed + {nth [[this n] [this n not-found]]} + + clojure.lang.IPersistentMap + {assocEx [[this k v]] + without [[this k]]} + + clojure.lang.IPersistentStack + {peek [[this]] + pop [[this]]} + + clojure.lang.Reversible + {rseq [[this]]} + + clojure.lang.Seqable + {seq [[this]]} + + java.lang.Iterable + {iterator [[this]] + forEach [[this action]]} + + java.util.Iterator + {hasNext [[this]] + next [[this]]}})) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 035f7217..bf790630 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -2,6 +2,7 @@ {:no-doc true} (:refer-clojure :exclude [error-handler]) (:require + [babashka.fs :as fs] [babashka.impl.bencode :refer [bencode-namespace]] [babashka.impl.cheshire :refer [cheshire-core-namespace]] [babashka.impl.classes :as classes] @@ -26,7 +27,8 @@ [babashka.impl.pprint :refer [pprint-namespace]] [babashka.impl.process :refer [process-namespace]] [babashka.impl.protocols :refer [protocols-namespace]] - [babashka.impl.reify :refer [reify-opts]] + [babashka.impl.proxy :refer [proxy-fn]] + [babashka.impl.reify :refer [reify-fn]] [babashka.impl.repl :as repl] [babashka.impl.socket-repl :as socket-repl] [babashka.impl.test :as t] @@ -73,185 +75,101 @@ ;; echo '1' | java -agentlib:native-image-agent=config-output-dir=/tmp -jar target/babashka-xxx-standalone.jar '...' ;; with the java provided by GraalVM. -(defn parse-opts [options] - (let [opts (loop [options options - opts-map {}] - (if options - (let [opt (first options)] - (case opt - ("--") (assoc opts-map :command-line-args (next options)) - ("--clojure") (assoc opts-map :clojure true - :opts (rest options)) - ("--version") {:version true} - ("--help" "-h" "-?") {:help? true} - ("--verbose")(recur (next options) - (assoc opts-map - :verbose? true)) - ("--describe") (recur (next options) - (assoc opts-map - :describe? true)) - ("--stream") (recur (next options) - (assoc opts-map - :stream? 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 - :shell-in true - :edn-out true)) - ("-Io") (recur (next options) - (assoc opts-map - :edn-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)))) - ("--uberjar") - (let [options (next options)] - (recur (next options) - (assoc opts-map - :uberjar (first options)))) - ("-f" "--file") - (let [options (next options)] - (recur (next options) - (assoc opts-map - :file (first options)))) - ("--jar" "-jar") - (let [options (next options)] - (recur (next options) - (assoc opts-map - :jar (first options)))) - ("--repl") - (let [options (next options)] - (recur (next options) - (assoc opts-map - :repl true))) - ("--socket-repl") - (let [options (next options) - opt (first options) - opt (when (and opt (not (str/starts-with? opt "-"))) - opt) - options (if opt (next options) - options)] - (recur options - (assoc opts-map - :socket-repl (or opt "1666")))) - ("--nrepl-server") - (let [options (next options) - opt (first options) - opt (when (and opt (not (str/starts-with? opt "-"))) - opt) - options (if opt (next options) - options)] - (recur options - (assoc opts-map - :nrepl (or opt "1667")))) - ("--eval", "-e") - (let [options (next options)] - (recur (next options) - (update opts-map :expressions (fnil conj []) (first options)))) - ("--main", "-m") - (let [options (next options)] - (recur (next options) - (assoc opts-map :main (first options)))) - (if (some opts-map [:file :jar :socket-repl :expressions :main]) - (assoc opts-map - :command-line-args options) - (let [trimmed-opt (str/triml opt) - c (.charAt trimmed-opt 0)] - (case c - (\( \{ \[ \* \@ \#) - (-> opts-map - (update :expressions (fnil conj []) (first options)) - (assoc :command-line-args (next options))) - (assoc opts-map - (if (str/ends-with? opt ".jar") - :jar :file) opt - :command-line-args (next options))))))) - opts-map))] - opts)) - (def version (str/trim (slurp (io/resource "BABASHKA_VERSION")))) (defn print-version [] (println (str "babashka v" version))) +(def bb-edn + (volatile! nil)) -(defn print-help [] +(defn command? [x] + (case x + ("clojure" + "version" + "help" + "doc" + "tasks" + "uberjar" + "uberscript" + "repl" + "socket-repl" + "nrepl-server" + "describe") true + false)) + +(defn print-error [& msgs] + (binding [*out* *err*] + (apply println msgs))) + +(defn print-help [_ctx _command-line-args] (println (str "Babashka v" version)) - ;; (println (str "sci v" (str/trim (slurp (io/resource "SCI_VERSION"))))) - (println) - (println "Options must appear in the order of groups mentioned below.") (println " -Help: +Usage: bb [classpath opts] [eval opts] [cmdline args] +or: bb [classpath opts] file [cmdline args] +or: bb [classpath opts] subcommand [subcommand opts] [cmdline args] - --help, -h or -? Print this help text. - --version Print the current version of babashka. - --describe Print an EDN map with information about this version of babashka. +Classpath: + + -cp, --classpath Classpath to use. Overrides bb.edn classpath. Evaluation: - -e, --evalEvaluate an expression. - -f, --file Evaluate a file. - -cp, --classpath Classpath to use. - -m, --main Call the -main function from namespace with args. - --verbose Print debug information and entire stacktrace in case of exception. + -e, --eval Evaluate an expression. + -f, --file Evaluate a file. + -m, --main Call the -main function from a namespace or call a fully qualified var. + --verbose Print debug information and entire stacktrace in case of exception. + +Help: + + help, -h or -? Print this help text. + version Print the current version of babashka. + describe Print an EDN map with information about this version of babashka. + doc Print docstring of var or namespace. Requires namespace if necessary. REPL: - --repl Start REPL. Use rlwrap for history. - --socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666). - --nrepl-server Start nREPL server. Specify port (e.g. 1667) or host and port separated by colon (e.g. 127.0.0.1:1667). - -In- and output flags: - - -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. - --stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration. - -Uberscript: - - --uberscript Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single file. - -Uberjar: - - --uberjar Similar to --uberscript but creates jar file. + repl Start REPL. Use rlwrap for history. + socket-repl [addr] Start a socket REPL. Addr opt defaults to localhost:1666. + nrepl-server [addr] Start nREPL server. Address option defaults to locahost:1667. Clojure: - --clojure [args...] Invokes clojure. Takes same args as the official clojure CLI. + clojure [args...] Invokes clojure. Takes same args as the official clojure CLI. -If the first argument is not any of the above options, then it treated as a file if it exists, or as an expression otherwise. -Everything after that is bound to *command-line-args*. +Packaging: + uberscript [eval-opt] Collect all required namespaces from the classpath into a single file. Accepts additional eval opts, like `-m`. + uberjar [eval-opt] Similar to uberscript but creates jar file. + +In- and output flags (only to be used with -e one-liners): + + -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. + --stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration. + +File names take precedence over subcommand names. +Remaining arguments are bound to *command-line-args*. Use -- to separate script command line args from bb command line args. -")) +When no eval opts or subcommand is provided, the implicit subcommand is repl.") + [nil 0]) + +(defn print-doc [ctx command-line-args] + (let [arg (first command-line-args)] + (if (sci/eval-string* ctx (format " +(when (or (resolve '%1$s) + (if (simple-symbol? '%1$s) + (try (require '%1$s) true + (catch Exception e nil)) + (try (requiring-resolve '%1$s) true + (catch Exception e nil)))) + (clojure.repl/doc %1$s) + true)" arg)) + [nil 0] + [nil 1])) + ,) (defn print-describe [] (println @@ -302,8 +220,6 @@ Use -- to separate script command line args from bb command line args. (str/replace x #"^#!.*" "")) (throw (Exception. (str "File does not exist: " file)))))) -(def reflection-var (sci/new-dynamic-var '*warn-on-reflection* false)) - (defn load-file* [f] (let [f (io/file f) s (slurp f)] @@ -320,7 +236,7 @@ Use -- to separate script command line args from bb command line args. nrepl-opts (assoc nrepl-opts :debug dev? :describe {"versions" {"babashka" version}} - :thread-bind [reflection-var])] + :thread-bind [core/warn-on-reflection])] (nrepl-server/start-server! ctx nrepl-opts) (binding [*out* *err*] (println "For more info visit: https://book.babashka.org/#_nrepl"))) @@ -444,6 +360,7 @@ Use -- to separate script command line args from bb command line args. Comparable java.lang.Comparable Double java.lang.Double Exception java.lang.Exception + IndexOutOfBoundsException java.lang.IndexOutOfBoundsException IllegalArgumentException java.lang.IllegalArgumentException Integer java.lang.Integer Iterable java.lang.Iterable @@ -483,27 +400,156 @@ Use -- to separate script command line args from bb command line args. (defn shell-seq [in] (line-seq (java.io.BufferedReader. in))) -(defn main - [& args] - (handle-pipe!) - (handle-sigint!) +(defn parse-opts [options] + (let [opt (first options)] + (cond (and (command? opt) + (not (fs/regular-file? opt))) + (recur (cons (str "--" opt) (next options))) + :else + (let [opts (loop [options options + opts-map {}] + (if options + (let [opt (first options)] + (case opt + ("--") (assoc opts-map :command-line-args (next options)) + ("--clojure") (assoc opts-map :clojure true + :command-line-args (rest options)) + ("--version") {:version true} + ("--help" "-h" "-?" "help") + {:help true + :command-line-args (rest options)} + ("--doc") + {:doc true + :command-line-args (rest options)} + ("--verbose") (recur (next options) + (assoc opts-map + :verbose? true)) + ("--describe") (recur (next options) + (assoc opts-map + :describe? true)) + ("--stream") (recur (next options) + (assoc opts-map + :stream? 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 + :shell-in true + :edn-out true)) + ("-Io") (recur (next options) + (assoc opts-map + :edn-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)))) + ("--uberjar") + (let [options (next options)] + (recur (next options) + (assoc opts-map + :uberjar (first options)))) + ("-f" "--file") + (let [options (next options)] + (recur (next options) + (assoc opts-map + :file (first options)))) + ("--jar" "-jar") + (let [options (next options)] + (recur (next options) + (assoc opts-map + :jar (first options)))) + ("--repl") + (let [options (next options)] + (recur (next options) + (assoc opts-map + :repl true))) + ("--socket-repl") + (let [options (next options) + opt (first options) + opt (when (and opt (not (str/starts-with? opt "-"))) + opt) + options (if opt (next options) + options)] + (recur options + (assoc opts-map + :socket-repl (or opt "1666")))) + ("--nrepl-server") + (let [options (next options) + opt (first options) + opt (when (and opt (not (str/starts-with? opt "-"))) + opt) + options (if opt (next options) + options)] + (recur options + (assoc opts-map + :nrepl (or opt "1667")))) + ("--eval", "-e") + (let [options (next options)] + (recur (next options) + (update opts-map :expressions (fnil conj []) (first options)))) + ("--main", "-m",) + (let [options (next options)] + (recur (next options) + (assoc opts-map :main (first options)))) + ;; fallback + (if (some opts-map [:file :jar :socket-repl :expressions :main]) + (assoc opts-map + :command-line-args options) + (let [trimmed-opt (str/triml opt) + c (.charAt trimmed-opt 0)] + (case c + (\( \{ \[ \* \@ \#) + (-> opts-map + (update :expressions (fnil conj []) (first options)) + (assoc :command-line-args (next options))) + (assoc opts-map + (if (str/ends-with? opt ".jar") + :jar :file) opt + :command-line-args (next options))))))) + opts-map))] + opts)))) + +(def env (atom {})) + +(defn exec [opts] (binding [*unrestricted* true] - (sci/binding [reflection-var false + (sci/binding [core/warn-on-reflection @core/warn-on-reflection core/data-readers @core/data-readers sci/ns @sci/ns] (let [{version-opt :version :keys [:shell-in :edn-in :shell-out :edn-out - :help? :file :command-line-args + :help :file :command-line-args :expressions :stream? :repl :socket-repl :nrepl :verbose? :classpath :main :uberscript :describe? - :jar :uberjar :clojure] :as opts} - (parse-opts args) - _ (when clojure - (if-let [proc (deps/clojure (:opts opts))] - (-> @proc :exit (System/exit)) - (System/exit 0))) + :jar :uberjar :clojure + :doc]} + opts _ (when verbose? (vreset! common/verbose? true)) _ (do ;; set properties (when main (System/setProperty "babashka.main" main)) @@ -522,11 +568,18 @@ Use -- to separate script command line args from bb command line args. :else (edn/read {:readers edn-readers} *in*)))))) uberscript-sources (atom ()) - env (atom {}) classpath (or classpath (System/getenv "BABASHKA_CLASSPATH")) - _ (when classpath - (cp/add-classpath classpath)) + _ (if classpath + (cp/add-classpath classpath) + ;; when classpath isn't set, we calculate it from bb.edn, if present + (let [bb-edn-file (or (System/getenv "BABASHKA_EDN") + "bb.edn")] + (when (fs/exists? bb-edn-file) + (let [edn (edn/read-string (slurp bb-edn-file))] + (vreset! bb-edn edn))) + ;; we mutate the atom from tests as well, so despite the above it can contain a bb.edn + (when-let [bb-edn @bb-edn] (deps/add-deps bb-edn)))) abs-path (when file (let [abs-path (.getAbsolutePath (io/file file))] (vars/bindRoot sci/file abs-path) @@ -552,15 +605,11 @@ Use -- to separate script command line args from bb command line args. ["META-INF/MANIFEST.MF"] {:url? true})] (cp/main-ns res)) main) - ;; TODO: pull more of these values to compile time opts {:aliases aliases :namespaces (-> namespaces (assoc 'clojure.core (assoc core-extras - '*command-line-args* - (sci/new-dynamic-var '*command-line-args* command-line-args) - '*warn-on-reflection* reflection-var 'load-file load-file*)) (assoc-in ['clojure.java.io 'resource] (fn [path] @@ -577,15 +626,23 @@ Use -- to separate script command line args from bb command line args. :load-fn load-fn :uberscript uberscript :readers core/data-readers - :reify reify-opts} + :reify-fn reify-fn + :proxy-fn proxy-fn} opts (addons/future opts) sci-ctx (sci/init opts) _ (vreset! common/ctx sci-ctx) preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim)) [expressions exit-code] (cond expressions [expressions nil] - main [[(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)" - main)] nil] + main + (let [sym (symbol main) + ns? (namespace sym) + ns (or ns? sym) + var-name (if ns? + (name sym) + "-main")] + [[(format "(ns user (:require [%1$s])) (apply %1$s/%2$s *command-line-args*)" + ns var-name)] nil]) file (try [[(read-file file)] nil] (catch Exception e (error-handler e {:expression expressions @@ -615,8 +672,8 @@ Use -- to separate script command line args from bb command line args. (second (cond version-opt [(print-version) 0] - help? - [(print-help) 0] + help (print-help sci-ctx command-line-args) + doc (print-doc sci-ctx command-line-args) describe? [(print-describe) 0] repl [(repl/start-repl! sci-ctx) 0] @@ -631,7 +688,8 @@ Use -- to separate script command line args from bb command line args. [nil 0] ;; done streaming (let [res [(let [res (sci/binding [sci/file (or @sci/file " ") - input-var in] + input-var in + core/command-line-args command-line-args] (sci/eval-string* sci-ctx expression))] (when (some? res) (if-let [pr-f (cond shell-out println @@ -650,6 +708,9 @@ Use -- to separate script command line args from bb command line args. :verbose? verbose? :preloads preloads :loader (:loader @cp/cp-state)})))) + clojure [nil (if-let [proc (deps/clojure command-line-args)] + (-> @proc :exit) + 0)] uberscript [nil 0] :else [(repl/start-repl! sci-ctx) 0])) 1)] @@ -669,8 +730,14 @@ Use -- to separate script command line args from bb command line args. :verbose verbose?})) exit-code)))) +(defn main [& args] + (let [opts (parse-opts args)] + (exec opts))) + (defn -main [& args] + (handle-pipe!) + (handle-sigint!) (if-let [dev-opts (System/getenv "BABASHKA_DEV")] (let [{:keys [:n]} (if (= "true" dev-opts) {:n 1} (edn/read-string dev-opts)) diff --git a/test-resources/bb-edn/user.clj b/test-resources/bb-edn/user.clj new file mode 100644 index 00000000..c8726eb6 --- /dev/null +++ b/test-resources/bb-edn/user.clj @@ -0,0 +1,10 @@ +(ns user + (:require [babashka.process :as p] + [clojure.string :as str])) + +(defn bash [& args] + ;; (prn :cmd *command-line-args*) + (-> (p/process ["bash" "-c" (str/join " " args)] + {:inherit true}) + p/check) + nil) diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj index 2bc95639..317ab5c7 100644 --- a/test-resources/lib_tests/babashka/run_all_libtests.clj +++ b/test-resources/lib_tests/babashka/run_all_libtests.clj @@ -6,10 +6,12 @@ (def status (atom {})) +(defn test-namespace? [ns] + (or (empty? ns-args) + (contains? ns-args ns))) + (defn test-namespaces [& namespaces] - (let [namespaces (if (seq ns-args) - (seq (keep ns-args namespaces)) - namespaces)] + (let [namespaces (seq (filter test-namespace? namespaces))] (when namespaces (doseq [ns namespaces] (require ns)) @@ -121,7 +123,14 @@ ;;;; doric -(test-namespaces 'doric.test.core) +(defn test-doric-cyclic-dep-problem + [] + (require '[doric.core :as d]) + ((resolve 'doric.core/table) [:a :b] [{:a 1 :b 2}])) + +(when (test-namespace? 'doric.test.core) + (test-doric-cyclic-dep-problem) + (test-namespaces 'doric.test.core)) ;;;; cljc-java-time diff --git a/test-resources/lib_tests/version_clj/compare_test.cljc b/test-resources/lib_tests/version_clj/compare_test.cljc index b4df372a..c7ac75df 100644 --- a/test-resources/lib_tests/version_clj/compare_test.cljc +++ b/test-resources/lib_tests/version_clj/compare_test.cljc @@ -1,9 +1,10 @@ (ns version-clj.compare-test - (:require [clojure.test :refer [deftest are]] + (:require #?(:clj [clojure.test :refer [deftest are]] + :cljs [cljs.test :refer-macros [deftest are]]) [version-clj.compare :refer [version-compare]])) (deftest t-version-compare - (are [v0 v1 r] (= (version-compare v0 v1) r) + (are [v0 v1 r] (= r (version-compare v0 v1)) ;; Numeric Comparison "1.0.0" "1.0.0" 0 "1.0.0" "1.0" 0 @@ -36,6 +37,19 @@ "1.0-RC5" "1.0-RC20" -1 "1.0-RC11" "1.0-RC6" 1 + ;; Comparison nested vs. value + "1.0.0" "1.0-1.0" -1 + "1.0-1.0" "1.0.0" 1 + "1.0-0.0" "1.0.0" 0 + "1.0.0" "1.0-0.0" 0 + "1.x.0" "1.x-1.0" -1 + "1.x-1.0" "1.x.0" 1 + "1.0-612" "1.0.613" -1 + + ;; Numbers are newer than strings + "1.x.1" "1.0.1" -1 + "1.0.1" "1.x.1" 1 + ;; Releases are newer than SNAPSHOTs "1.0.0" "1.0.0-SNAPSHOT" 1 "1.0.0-SNAPSHOT" "1.0.0-SNAPSHOT" 0 diff --git a/test-resources/lib_tests/version_clj/core_test.cljc b/test-resources/lib_tests/version_clj/core_test.cljc index 691aea8a..370e58a9 100644 --- a/test-resources/lib_tests/version_clj/core_test.cljc +++ b/test-resources/lib_tests/version_clj/core_test.cljc @@ -1,25 +1,81 @@ (ns version-clj.core-test - (:require [clojure.test :refer [deftest are]] - [version-clj.core :refer [snapshot? qualified?]])) + (:require #?(:clj [clojure.test :refer [deftest is are]] + :cljs [cljs.test :refer-macros [deftest is are]]) + [version-clj.core :as v])) -(deftest t-snapshot - (are [v r] (= (boolean (snapshot? v)) r) - "1.0.0" false - "SNAPSHOT" true - "1-SNAPSHOT" true - "1.0-SNAPSHOT" true - "1.0-SNAPSHOT.2" true - "1.0-NOSNAPSHOT" false)) +(deftest t-snapshot? + (are [v r] (= r (boolean (v/snapshot? v))) + "1.0.0" false + "SNAPSHOT" true + "1-SNAPSHOT" true + "1.0-SNAPSHOT" true + "1.0-SNAPSHOT.2" true + "1.0-NOSNAPSHOT" false)) -(deftest t-qualified - (are [v r] (= (boolean (qualified? v)) r) - "1.0.0" false - "SNAPSHOT" true - "1-SNAPSHOT" true - "1.0-SNAPSHOT" true - "1.0-SNAPSHOT.2" true - "1.0-NOSNAPSHOT" true - "1.x.2" false - "1.2y" true - "1.y2" false - "1.y" false)) +(deftest t-qualified? + (are [v r] (= r (boolean (v/qualified? v))) + "1.0.0" false + "SNAPSHOT" true + "SNAPSHOT2" true + "1-SNAPSHOT" true + "1.0-SNAPSHOT" true + "1.0-SNAPSHOT.2" true + "1.0-NOSNAPSHOT" true + "1.0-NOSNAPSHOT.1" true + "1.0-NOSNAPSHOT.1.1" true + "1.0-NOSNAPSHOT1.1" true + "0.5.3-alpha.1.pre.0" true + "1.x.2" false + "1.2y" false + "1.y2" false + "1.y" false)) + +(let [v0 "1.0.0-SNAPSHOT" + v1 "1.0.0" + v2 "1.0.1-RC" + v3 "1.0.1" + ordered [v0 v1 v2 v3]] + (deftest t-version-sort + (is (= ordered (v/version-sort (shuffle ordered)))) + (is (= (map v/version->seq ordered) + (v/version-seq-sort (map v/version->seq ordered))))) + + (deftest t-version-compare + (is (pos? (v/version-compare v1 v0))) + (is (neg? (v/version-compare v0 v1))) + (is (zero? (v/version-compare v0 v0))) + (is (v/older? v0 v1)) + (is (v/newer? v1 v0)) + (is (v/older-or-equal? v0 v1)) + (is (v/older-or-equal? v0 v0)) + (is (v/newer-or-equal? v1 v0)) + (is (v/newer-or-equal? v0 v0))) + + (deftest t-version-seq-compare + (is (pos? (v/version-seq-compare + (v/version->seq v1) + (v/version->seq v0)))) + (is (neg? (v/version-seq-compare + (v/version->seq v0) + (v/version->seq v1)))) + (is (zero? (v/version-seq-compare + (v/version->seq v0) + (v/version->seq v0)))))) + +(deftest t-parse + (let [s "1.0.1", version (v/parse s)] + (is (= [[1 0 1]] (:version version))) + (is (= #{} (:qualifiers version))) + (is (not (:snapshot? version))) + (is (not (:qualified? version)))) + (let [s "1.0.1-RC1-SNAPSHOT", version (v/parse s)] + (is (= [[1 0 1] [["rc" 1] "snapshot"]] (:version version))) + (is (= #{"rc" "snapshot"} (:qualifiers version))) + (is (:snapshot? version)) + (is (:qualified? version))) + (let [s "1.0.1"] + (is (= (v/parse s) (v/parse (v/version->seq s)))))) + +(deftest t-version-and-qualifier-data + (is (= [1 0 1] (v/version-data "1.0.1-RC"))) + (is (= ["rc"] (v/qualifier-data "1.0.1-RC")))) diff --git a/test-resources/lib_tests/version_clj/split_test.cljc b/test-resources/lib_tests/version_clj/split_test.cljc index 1216b134..51cac090 100644 --- a/test-resources/lib_tests/version_clj/split_test.cljc +++ b/test-resources/lib_tests/version_clj/split_test.cljc @@ -1,22 +1,72 @@ (ns version-clj.split-test - (:require [clojure.test :refer [deftest are is]] + (:require #?(:clj [clojure.test :refer [deftest testing are is]] + :cljs [cljs.test :refer-macros [deftest testing are is]]) [version-clj.split :refer [version->seq]])) +(deftest t-split-once-sanity-check + (let [split-once @#'version-clj.split/split-once + rx #"(^|(?<=\d)|-)(?=alpha)"] + (are [in out] (= out (split-once rx in)) + "1-alpha2.2" ["1" "alpha2.2"] + "alpha" ["" "alpha"] + "1alpha" ["1" "alpha"] + "0.0.3-alpha.8+oryOS.15" ["0.0.3" "alpha.8+oryOS.15"]))) + (deftest t-split - (are [version v] (= (version->seq version) v) - "1.0.0" [[1 0 0]] - "1.0" [[1 0]] - "1" [[1]] - "1a" [[1] ["a"]] - "1-a" [[1] ["a"]] - "1.0.1-SNAPSHOT" [[1 0 1] ["snapshot"]] - "1.0.1-alpha2" [[1 0 1] ["alpha" 2]] - "11.2.0.3.0" [[11 2 0 3 0]] - "1.0-1-0.2-RC" [[1 [0 1 0] 2] ["rc"]])) + (are [version v] (= v (version->seq version)) + "1.0.0" [[1 0 0]] + "1.0" [[1 0]] + "1" [[1]] + "1a" [[1] ["a"]] + "1-a" [[1] ["a"]] + "1.0.1-SNAPSHOT" [[1 0 1] ["snapshot"]] + "1.0.1-alpha2" [[1 0 1] ["alpha" 2]] + "11.2.0.3.0" [[11 2 0 3 0]] + "1.0-1-0.2-RC" [[1 [0 1 0] 2] ["rc"]] + "1.0-612" [[1 0] [612]] + "alpha" [[] ["alpha"]] + "alpha-2" [[] ["alpha" 2]] + "1.alpha" [[1] ["alpha"]] + "1.alpha.2" [[1] ["alpha" 2]] + "1-alpha.2" [[1] ["alpha" 2]] + "1-alpha.2.2" [[1] ["alpha" 2 2]] + "1-alpha2.2" [[1] [["alpha" 2] 2]] + "1.alpha-1.0" [[1] ["alpha" [1 0]]] + "0.5.0-alpha.1" [[0 5 0] ["alpha" 1]] + "0.5.0-alpha.1" [[0 5 0] ["alpha" 1]] + "0.0.3-alpha.8+oryOS.15" [[0 0 3] ["alpha" [8 "+oryos"] 15]] + )) + +(deftest t-split-without-qualifiers + (testing "well-behaving." + (are [version] (= (version->seq version) + (version->seq version {:qualifiers {}})) + "1.0.0" + "1.0" + "1" + "1-a" + "1.0.1-SNAPSHOT" + "1.0.1-alpha2" + "11.2.0.3.0" + "1.0-1-0.2-RC" + "1-alpha.2" + "1-alpha.2.2" + "1-alpha2.2" + "0.5.0-alpha.1" + "0.5.0-alpha.1" + "0.0.3-alpha.8+oryOS.15")) + (testing "deviants." + (are [version v] (= v (version->seq version {:qualifiers {}})) + "alpha" [["alpha"]] + "alpha-2" [["alpha"] [2]] + "1a" [[1 "a"]] + "1.alpha" [[1 "alpha"]] + "1.alpha.2" [[1 "alpha" 2]] + "1.alpha-1.0" [[1 ["alpha" 1] 0]]))) (deftest t-split-with-large-number - (is (= (version->seq "0.0.1-20141002100138") - [[0 0 1] [20141002100138]])) + (is (= [[0 0 1] [20141002100138]] + (version->seq "0.0.1-20141002100138"))) #?(:clj (let [v (str Long/MAX_VALUE "12345")] (is (= (version->seq v) [[(bigint v)]]))))) diff --git a/test-resources/task_scripts/tasks.clj b/test-resources/task_scripts/tasks.clj new file mode 100644 index 00000000..2fadb15d --- /dev/null +++ b/test-resources/task_scripts/tasks.clj @@ -0,0 +1,11 @@ +(ns tasks + "This is task ns docstring.") + +(defn -main + "Main docstring" + [& args] + args) + +(defn foo + "Foo docstring" + []) diff --git a/test/babashka/bb_edn_test.clj b/test/babashka/bb_edn_test.clj new file mode 100644 index 00000000..b0488b4f --- /dev/null +++ b/test/babashka/bb_edn_test.clj @@ -0,0 +1,51 @@ +(ns babashka.bb-edn-test + (:require + [babashka.fs :as fs] + [babashka.test-utils :as test-utils] + [clojure.edn :as edn] + [clojure.string :as str] + [clojure.test :as test :refer [deftest is testing]])) + +(defn bb [& args] + (edn/read-string + {:readers *data-readers* + :eof nil} + (apply test-utils/bb nil (map str args)))) + +(defmacro with-config [cfg & body] + `(let [temp-dir# (fs/create-temp-dir) + bb-edn-file# (fs/file temp-dir# "bb.edn")] + (binding [*print-meta* true] + (spit bb-edn-file# ~cfg)) + (binding [test-utils/*bb-edn-path* (str bb-edn-file#)] + ~@body))) + +(deftest doc-test + (with-config {:paths ["test-resources/task_scripts"]} + (is (str/includes? (apply test-utils/bb nil + (map str ["doc" "tasks"])) + "This is task ns docstring.")) + (is (str/includes? (apply test-utils/bb nil + (map str ["doc" "tasks/foo"])) + "Foo docstring")) + (is (str/includes? (apply test-utils/bb nil + (map str ["doc" "tasks/-main"])) + "Main docstring")))) + +(deftest deps-test + (with-config '{:deps {medley/medley {:mvn/version "1.3.0"}}} + (is (= '{1 {:id 1}, 2 {:id 2}} + (bb "-e" "(require 'medley.core)" "-e" "(medley.core/index-by :id [{:id 1} {:id 2}])")))) + (testing "--classpath option overrides bb.edn" + (with-config '{:deps {medley/medley {:mvn/version "1.3.0"}}} + (is (= "src" + (bb "-cp" "src" "-e" "(babashka.classpath/get-classpath)")))))) + +;; TODO: +;; Do we want to support the same parsing as the clj CLI? +;; Or do we want `--aliases :foo:bar` +;; Let's wait for a good use case +#_(deftest alias-deps-test + (with-config '{:aliases {:medley {:deps {medley/medley {:mvn/version "1.3.0"}}}}} + (is (= '{1 {:id 1}, 2 {:id 2}} + (bb "-A:medley" "-e" "(require 'medley.core)" "-e" "(medley.core/index-by :id [{:id 1} {:id 2}])"))))) diff --git a/test/babashka/impl/nrepl_server_test.clj b/test/babashka/impl/nrepl_server_test.clj index 9f18d0ca..08dddee0 100644 --- a/test/babashka/impl/nrepl_server_test.clj +++ b/test/babashka/impl/nrepl_server_test.clj @@ -195,7 +195,7 @@ :features #{:bb}}) nrepl-opts)] (reset! server-state server)) - (let [pb (ProcessBuilder. ["./bb" "--nrepl-server" "0.0.0.0:1668"]) + (let [pb (ProcessBuilder. ["./bb" "nrepl-server" "0.0.0.0:1668"]) _ (.redirectError pb ProcessBuilder$Redirect/INHERIT) ;; _ (.redirectOutput pb ProcessBuilder$Redirect/INHERIT) ;; env (.environment pb) diff --git a/test/babashka/impl/socket_repl_test.clj b/test/babashka/impl/socket_repl_test.clj index 7df4e857..8c99f64a 100644 --- a/test/babashka/impl/socket_repl_test.clj +++ b/test/babashka/impl/socket_repl_test.clj @@ -51,7 +51,7 @@ (vreset! common/ctx ctx) (start-repl! "0.0.0.0:1666" ctx)) (do (vreset! server-process - (p/process ["./bb" "--socket-repl" "localhost:1666"])) + (p/process ["./bb" "socket-repl" "localhost:1666"])) (w/wait-for-port "localhost" 1666))) (Thread/sleep 50) (is (socket-command "(+ 1 2 3)" "user=> 6")) diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index 7ec984c9..cc6931a5 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -7,21 +7,10 @@ [clojure.java.io :as io] [clojure.java.shell :refer [sh]] [clojure.string :as str] - [clojure.test :as test :refer [deftest is testing *report-counters*]] + [clojure.test :as test :refer [deftest is testing]] [flatland.ordered.map :refer [ordered-map]] [sci.core :as sci])) -(defmethod clojure.test/report :begin-test-var [m] - (println "===" (-> m :var meta :name)) - (println)) - -(defmethod clojure.test/report :end-test-var [_m] - (let [{:keys [:fail :error]} @*report-counters*] - (when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST")) - (or (pos? fail) (pos? error))) - (println "=== Failing fast") - (System/exit 1)))) - (defn bb [input & args] (edn/read-string {:readers *data-readers* @@ -363,16 +352,7 @@ (is (.exists f2)) (let [v (bb nil "-f" (.getPath (io/file "test-resources" "babashka" "glob.clj")))] (is (vector? v)) - (is (.exists (io/file (first v)))))) - (testing "reify can handle multiple classes at once" - (is (true? (bb nil " -(def filter-obj (reify java.io.FileFilter - (accept [this f] (prn (.getPath f)) true) - java.io.FilenameFilter - (accept [this f name] (prn name) true))) -(def s1 (with-out-str (.listFiles (clojure.java.io/file \".\") filter-obj))) -(def s2 (with-out-str (.list (clojure.java.io/file \".\") filter-obj))) -(and (pos? (count s1)) (pos? (count s2)))"))))) + (is (.exists (io/file (first v))))))) (deftest future-print-test (testing "the root binding of sci/*out*" @@ -574,7 +554,12 @@ (deftest var-print-method-test (when test-utils/native? - (is (bb nil "(defmethod print-method sci.lang.IVar [o w] (.write w (str :foo (symbol o)))) (def x 1) (= \":foouser/x\" (pr-str #'x))")))) + (is (bb nil "(defmethod print-method sci.lang.IVar [o w] (.write w (str :foo (symbol o)))) (def x 1) (= \":foouser/x\" (pr-str #'x))")) + (is (= :foouser/x (bb nil "(defmethod print-method sci.lang.IVar [o w] (.write w (str :foo (symbol o)))) (def x 1)"))))) + +(deftest stdout-interop-test + (when test-utils/native? + (is (= 'Something (bb nil "(.print (System/out) \"Something\")"))))) ;;;; Scratch diff --git a/test/babashka/proxy_test.clj b/test/babashka/proxy_test.clj new file mode 100644 index 00000000..496f6449 --- /dev/null +++ b/test/babashka/proxy_test.clj @@ -0,0 +1,80 @@ +(ns babashka.proxy-test + (:require + [babashka.test-utils :as test-utils] + [clojure.edn :as edn] + [clojure.test :as test :refer [deftest is]])) + +(defn bb [& args] + (edn/read-string + {:readers *data-readers* + :eof nil} + (apply test-utils/bb nil (map str args)))) + +(def code + '(do + (require '[clojure.core.protocols]) + (require '[clojure.datafy :as d]) + (defn auto-deref + "If value implements IDeref, deref it, otherwise return original." + [x] + (if (instance? clojure.lang.IDeref x) + @x + x)) + (defn proxy-deref-map + {:added "1.0"} + [m] + (proxy [clojure.lang.APersistentMap clojure.lang.IMeta clojure.lang.IObj clojure.core.protocols.Datafiable] + [] + (iterator [] + ::TODO) + (containsKey [k] (contains? m k)) + (entryAt [k] (when (contains? m k) (proxy [clojure.lang.AMapEntry] [] + (key [] k) + (val [] (auto-deref (get m k)))))) + (valAt ([k] (auto-deref (get m k))) + ([k default] (auto-deref (get m k default)))) + (cons [v] (proxy-deref-map (conj m v))) + (count [] (count m)) + (assoc [k v] (proxy-deref-map (assoc m k v))) + (without [k] (proxy-deref-map (dissoc m k))) + (seq [] (map (fn [[k v]](proxy [clojure.lang.AMapEntry] [] + (key [] k) + (val [] (auto-deref (get m k))))) m)) + (withMeta [md] (proxy-deref-map (with-meta m md))) + (meta [] (meta m)) + + (datafy [] {:datafied true}))) + (let [m (proxy-deref-map + {:a (delay 1) + :b (delay 2) + :c 3})] + [(:a m) + (:b m) + (:c m) + (contains? m :c) + (find m :c) + (-> (conj m [:d (delay 5)]) + :d) + (count m) + (-> (assoc m :d (delay 5)) + :d) + (-> (dissoc m :a) + (contains? :a)) + (seq m) + (meta (with-meta m {:a 1})) + (d/datafy m) + (instance? clojure.lang.APersistentMap m) + (instance? java.io.FilenameFilter m) + ,]))) + +(require 'clojure.pprint) + +(deftest APersistentMap-proxy-test + (is (= [1 2 3 true [:c 3] + 5 3 5 false + '([:a 1] [:b 2] [:c 3]) + {:a 1} + {:datafied true} + true + false] + (bb (with-out-str (clojure.pprint/pprint code)))))) diff --git a/test/babashka/reify_test.clj b/test/babashka/reify_test.clj new file mode 100644 index 00000000..1e094f5e --- /dev/null +++ b/test/babashka/reify_test.clj @@ -0,0 +1,67 @@ +(ns babashka.reify-test + (:require + [babashka.test-utils :as test-utils] + [clojure.edn :as edn] + [clojure.test :as test :refer [deftest is testing]])) + +(defn bb [input & args] + (edn/read-string + {:readers *data-readers* + :eof nil} + (apply test-utils/bb (when (some? input) (str input)) (map str args)))) + +(deftest file-filter-test + (is (true? (bb nil " +(def filter-obj (reify java.io.FileFilter + (accept [this f] (prn (.getPath f)) true))) +(def filename-filter-obj + (reify java.io.FilenameFilter + (accept [this f name] (prn name) true))) +(def s1 (with-out-str (.listFiles (clojure.java.io/file \".\") filter-obj))) +(def s2 (with-out-str (.listFiles (clojure.java.io/file \".\") filename-filter-obj))) +(and (pos? (count s1)) (pos? (count s2)))")))) + +(deftest reify-multiple-arities-test + (testing "ILookup" + (is (= ["->:foo" 10] + (bb nil " +(def m (reify clojure.lang.ILookup + (valAt [this x] (str \"->\" x)) + (valAt [this x y] y))) +[(:foo m) (:foo m 10)]")))) + (testing "IFn" + (is (= [:yo :three :six :twelve :eighteen :nineteen 19] + (bb nil " +(def m (reify clojure.lang.IFn + (invoke [this] :yo) + (invoke [this _ _ _] :three) + (invoke [this _ _ _ _ _ _] :six) + (invoke [this _ _ _ _ _ _ _ _ _ _ _ _] :twelve) + (invoke [this _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] :eighteen) + (invoke [this _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] :nineteen) + (applyTo [this args] (last args)))) +[ +(m) +(m 1 2 3) +(m 1 2 3 4 5 6) +(m 1 2 3 4 5 6 1 2 3 4 5 6) +(m 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6) +(m 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1) +(apply m (range 20)) +]"))))) + +(deftest reify-object + (testing "toString" + (is (= ":foo" + (bb nil " +(def m (reify Object + (toString [_] (str :foo)))) +(str m) +")))) + (testing "Hashcode still works when only overriding toString" + (is (number? + (bb nil " +(def m (reify Object + (toString [_] (str :foo)))) +(hash m) +"))))) diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj index 547d8a3b..5c4fe66a 100644 --- a/test/babashka/test_utils.clj +++ b/test/babashka/test_utils.clj @@ -2,14 +2,33 @@ (:require [babashka.impl.classpath :as cp] [babashka.main :as main] - [me.raynes.conch :refer [let-programs] :as sh] + [babashka.process :as p] + [clojure.edn :as edn] + [clojure.test :as test :refer [*report-counters*]] [sci.core :as sci] [sci.impl.vars :as vars])) (set! *warn-on-reflection* true) +(def ^:dynamic *bb-edn-path* nil) + +(defmethod clojure.test/report :begin-test-var [m] + (println "===" (-> m :var meta :name)) + (println)) + +(defmethod clojure.test/report :end-test-var [_m] + (let [{:keys [:fail :error]} @*report-counters*] + (when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST")) + (or (pos? fail) (pos? error))) + (println "=== Failing fast") + (System/exit 1)))) + (defn bb-jvm [input-or-opts & args] (reset! cp/cp-state nil) + (reset! main/env {}) + (if-let [path *bb-edn-path*] + (vreset! main/bb-edn (edn/read-string (slurp path))) + (vreset! main/bb-edn nil)) (let [os (java.io.StringWriter.) es (if-let [err (:err input-or-opts)] err (java.io.StringWriter.)) @@ -30,26 +49,33 @@ (if (string? input-or-opts) (with-in-str input-or-opts (apply main/main args)) (apply main/main args)))] + ;; (prn :err (str es)) (if (zero? res) (str os) - (throw (ex-info (str es) - {:stdout (str os) - :stderr (str es)}))))) + (do + (println (str os)) + (throw (ex-info (str es) + {:stdout (str os) + :stderr (str es)})))))) (finally (when (string? input-or-opts) (vars/bindRoot sci/in *in*)) (vars/bindRoot sci/out *out*) (vars/bindRoot sci/err *err*))))) (defn bb-native [input & args] - (let-programs [bb "./bb"] - (try (if input - (apply bb (conj (vec args) - {:in input})) - (apply bb args)) - (catch Exception e - (let [d (ex-data e) - err-msg (or (:stderr (ex-data e)) "")] - (throw (ex-info err-msg d))))))) + (let [res (p/process (into ["./bb"] args) + (cond-> {:in input + :out :string + :err :string} + *bb-edn-path* + (assoc + :env (assoc (into {} (System/getenv)) + "BABASHKA_EDN" *bb-edn-path*)))) + res (deref res) + exit (:exit res) + error? (pos? exit)] + (if error? (throw (ex-info (or (:err res) "") {})) + (:out res)))) (def bb (case (System/getenv "BABASHKA_TEST_ENV") diff --git a/test/babashka/uberjar_test.clj b/test/babashka/uberjar_test.clj index cbdd070f..5bb89dac 100644 --- a/test/babashka/uberjar_test.clj +++ b/test/babashka/uberjar_test.clj @@ -1,7 +1,6 @@ (ns babashka.uberjar-test (:require [babashka.test-utils :as tu] - [clojure.edn :as edn] [clojure.string :as str] [clojure.test :as t :refer [deftest is testing]])) @@ -10,7 +9,7 @@ path (.getPath tmp-file)] (.deleteOnExit tmp-file) (testing "uberjar" - (tu/bb nil "--classpath" "test-resources/babashka/uberjar/src" "-m" "my.main-main" "--uberjar" path) + (tu/bb nil "uberjar" path "--classpath" "test-resources/babashka/uberjar/src" "-m" "my.main-main") (is (= "(\"1\" \"2\" \"3\" \"4\")\n" (tu/bb nil "--jar" path "1" "2" "3" "4"))) (is (= "(\"1\" \"2\" \"3\" \"4\")\n" @@ -25,5 +24,5 @@ (let [tmp-file (java.io.File/createTempFile "uber" ".jar") path (.getPath tmp-file)] (.deleteOnExit tmp-file) - (tu/bb nil "--classpath" "test-resources/babashka/uberjar/src" "--uberjar" path) + (tu/bb nil "uberjar" path "--classpath" "test-resources/babashka/uberjar/src") (is (str/includes? (tu/bb "(+ 1 2 3)" path) "6")))))